]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
GetProgramCommandPrefix() -- allows me to have enhanced usage and such
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
1 string wordwrap_buffer;
2
3 void wordwrap_buffer_put(string s)
4 {
5         wordwrap_buffer = strcat(wordwrap_buffer, s);
6 }
7
8 string wordwrap(string s, float l)
9 {
10         string r;
11         wordwrap_buffer = "";
12         wordwrap_cb(s, l, wordwrap_buffer_put);
13         r = wordwrap_buffer;
14         wordwrap_buffer = "";
15         return r;
16 }
17
18 #ifndef MENUQC
19 #ifndef CSQC
20 void wordwrap_buffer_sprint(string s)
21 {
22         wordwrap_buffer = strcat(wordwrap_buffer, s);
23         if(s == "\n")
24         {
25                 sprint(self, wordwrap_buffer);
26                 wordwrap_buffer = "";
27         }
28 }
29
30 void wordwrap_sprint(string s, float l)
31 {
32         wordwrap_buffer = "";
33         wordwrap_cb(s, l, wordwrap_buffer_sprint);
34         if(wordwrap_buffer != "")
35                 sprint(self, strcat(wordwrap_buffer, "\n"));
36         wordwrap_buffer = "";
37         return;
38 }
39 #endif
40 #endif
41
42 string unescape(string in)
43 {
44         float i, len;
45         string str, s;
46
47         // but it doesn't seem to be necessary in my tests at least
48         in = strzone(in);
49
50         len = strlen(in);
51         str = "";
52         for(i = 0; i < len; ++i)
53         {
54                 s = substring(in, i, 1);
55                 if(s == "\\")
56                 {
57                         s = substring(in, i+1, 1);
58                         if(s == "n")
59                                 str = strcat(str, "\n");
60                         else if(s == "\\")
61                                 str = strcat(str, "\\");
62                         else
63                                 str = strcat(str, substring(in, i, 2));
64                         ++i;
65                 } else
66                         str = strcat(str, s);
67         }
68
69         strunzone(in);
70         return str;
71 }
72
73 void wordwrap_cb(string s, float l, void(string) callback)
74 {
75         string c;
76         float lleft, i, j, wlen;
77
78         s = strzone(s);
79         lleft = l;
80         for (i = 0;i < strlen(s);++i)
81         {
82                 if (substring(s, i, 2) == "\\n")
83                 {
84                         callback("\n");
85                         lleft = l;
86                         ++i;
87                 }
88                 else if (substring(s, i, 1) == "\n")
89                 {
90                         callback("\n");
91                         lleft = l;
92                 }
93                 else if (substring(s, i, 1) == " ")
94                 {
95                         if (lleft > 0)
96                         {
97                                 callback(" ");
98                                 lleft = lleft - 1;
99                         }
100                 }
101                 else
102                 {
103                         for (j = i+1;j < strlen(s);++j)
104                                 //    ^^ this skips over the first character of a word, which
105                                 //       is ALWAYS part of the word
106                                 //       this is safe since if i+1 == strlen(s), i will become
107                                 //       strlen(s)-1 at the end of this block and the function
108                                 //       will terminate. A space can't be the first character we
109                                 //       read here, and neither can a \n be the start, since these
110                                 //       two cases have been handled above.
111                         {
112                                 c = substring(s, j, 1);
113                                 if (c == " ")
114                                         break;
115                                 if (c == "\\")
116                                         break;
117                                 if (c == "\n")
118                                         break;
119                                 // we need to keep this tempstring alive even if substring is
120                                 // called repeatedly, so call strcat even though we're not
121                                 // doing anything
122                                 callback("");
123                         }
124                         wlen = j - i;
125                         if (lleft < wlen)
126                         {
127                                 callback("\n");
128                                 lleft = l;
129                         }
130                         callback(substring(s, i, wlen));
131                         lleft = lleft - wlen;
132                         i = j - 1;
133                 }
134         }
135         strunzone(s);
136 }
137
138 float dist_point_line(vector p, vector l0, vector ldir)
139 {
140         ldir = normalize(ldir);
141         
142         // remove the component in line direction
143         p = p - (p * ldir) * ldir;
144
145         // vlen of the remaining vector
146         return vlen(p);
147 }
148
149 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
150 {
151         entity e;
152         e = start;
153         funcPre(pass, e);
154         while(e.downleft)
155         {
156                 e = e.downleft;
157                 funcPre(pass, e);
158         }
159         funcPost(pass, e);
160         while(e != start)
161         {
162                 if(e.right)
163                 {
164                         e = e.right;
165                         funcPre(pass, e);
166                         while(e.downleft)
167                         {
168                                 e = e.downleft;
169                                 funcPre(pass, e);
170                         }
171                 }
172                 else
173                         e = e.up;
174                 funcPost(pass, e);
175         }
176 }
177
178 float median(float a, float b, float c)
179 {
180         if(a < c)
181                 return bound(a, b, c);
182         return bound(c, b, a);
183 }
184
185 // converts a number to a string with the indicated number of decimals
186 // works for up to 10 decimals!
187 string ftos_decimals(float number, float decimals)
188 {
189         // we have sprintf...
190         return sprintf("%.*f", decimals, number);
191 }
192
193 float time;
194 vector colormapPaletteColor(float c, float isPants)
195 {
196         switch(c)
197         {
198                 case  0: return '0.800000 0.800000 0.800000';
199                 case  1: return '0.600000 0.400000 0.000000';
200                 case  2: return '0.000000 1.000000 0.501961';
201                 case  3: return '0.000000 1.000000 0.000000';
202                 case  4: return '1.000000 0.000000 0.000000';
203                 case  5: return '0.000000 0.658824 1.000000';
204                 case  6: return '0.000000 1.000000 1.000000';
205                 case  7: return '0.501961 1.000000 0.000000';
206                 case  8: return '0.501961 0.000000 1.000000';
207                 case  9: return '1.000000 0.000000 1.000000';
208                 case 10: return '1.000000 0.000000 0.501961';
209                 case 11: return '0.600000 0.600000 0.600000';
210                 case 12: return '1.000000 1.000000 0.000000';
211                 case 13: return '0.000000 0.313725 1.000000';
212                 case 14: return '1.000000 0.501961 0.000000';
213                 case 15:
214                         if(isPants)
215                                 return
216                                           '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
217                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
218                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
219                         else
220                                 return
221                                           '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
222                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
223                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
224                 default: return '0.000 0.000 0.000';
225         }
226 }
227
228 // unzone the string, and return it as tempstring. Safe to be called on string_null
229 string fstrunzone(string s)
230 {
231         string sc;
232         if not(s)
233                 return s;
234         sc = strcat(s, "");
235         strunzone(s);
236         return sc;
237 }
238
239 float fexists(string f)
240 {
241     float fh;
242     fh = fopen(f, FILE_READ);
243     if (fh < 0)
244         return FALSE;
245     fclose(fh);
246     return TRUE;
247 }
248
249 // Databases (hash tables)
250 #define DB_BUCKETS 8192
251 void db_save(float db, string pFilename)
252 {
253         float fh, i, n;
254         fh = fopen(pFilename, FILE_WRITE);
255         if(fh < 0) 
256         {
257                 print(strcat("^1Can't write DB to ", pFilename));
258                 return;
259         }
260         n = buf_getsize(db);
261         fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
262         for(i = 0; i < n; ++i)
263                 fputs(fh, strcat(bufstr_get(db, i), "\n"));
264         fclose(fh);
265 }
266
267 float db_create()
268 {
269         return buf_create();
270 }
271
272 float db_load(string pFilename)
273 {
274         float db, fh, i, j, n;
275         string l;
276         db = buf_create();
277         if(db < 0)
278                 return -1;
279         fh = fopen(pFilename, FILE_READ);
280         if(fh < 0)
281                 return db;
282         l = fgets(fh);
283         if(stof(l) == DB_BUCKETS)
284         {
285                 i = 0;
286                 while((l = fgets(fh)))
287                 {
288                         if(l != "")
289                                 bufstr_set(db, i, l);
290                         ++i;
291                 }
292         }
293         else
294         {
295                 // different count of buckets, or a dump?
296                 // need to reorganize the database then (SLOW)
297                 //
298                 // note: we also parse the first line (l) in case the DB file is
299                 // missing the bucket count
300                 do
301                 {
302                         n = tokenizebyseparator(l, "\\");
303                         for(j = 2; j < n; j += 2)
304                                 db_put(db, argv(j-1), uri_unescape(argv(j)));
305                 }
306                 while((l = fgets(fh)));
307         }
308         fclose(fh);
309         return db;
310 }
311
312 void db_dump(float db, string pFilename)
313 {
314         float fh, i, j, n, m;
315         fh = fopen(pFilename, FILE_WRITE);
316         if(fh < 0)
317                 error(strcat("Can't dump DB to ", pFilename));
318         n = buf_getsize(db);
319         fputs(fh, "0\n");
320         for(i = 0; i < n; ++i)
321         {
322                 m = tokenizebyseparator(bufstr_get(db, i), "\\");
323                 for(j = 2; j < m; j += 2)
324                         fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
325         }
326         fclose(fh);
327 }
328
329 void db_close(float db)
330 {
331         buf_del(db);
332 }
333
334 string db_get(float db, string pKey)
335 {
336         float h;
337         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
338         return uri_unescape(infoget(bufstr_get(db, h), pKey));
339 }
340
341 void db_put(float db, string pKey, string pValue)
342 {
343         float h;
344         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
345         bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
346 }
347
348 void db_test()
349 {
350         float db, i;
351         print("LOAD...\n");
352         db = db_load("foo.db");
353         print("LOADED. FILL...\n");
354         for(i = 0; i < DB_BUCKETS; ++i)
355                 db_put(db, ftos(random()), "X");
356         print("FILLED. SAVE...\n");
357         db_save(db, "foo.db");
358         print("SAVED. CLOSE...\n");
359         db_close(db);
360         print("CLOSED.\n");
361 }
362
363 // Multiline text file buffers
364 float buf_load(string pFilename)
365 {
366         float buf, fh, i;
367         string l;
368         buf = buf_create();
369         if(buf < 0)
370                 return -1;
371         fh = fopen(pFilename, FILE_READ);
372         if(fh < 0)
373         {
374                 buf_del(buf);
375                 return -1;
376         }
377         i = 0;
378         while((l = fgets(fh)))
379         {
380                 bufstr_set(buf, i, l);
381                 ++i;
382         }
383         fclose(fh);
384         return buf;
385 }
386
387 void buf_save(float buf, string pFilename)
388 {
389         float fh, i, n;
390         fh = fopen(pFilename, FILE_WRITE);
391         if(fh < 0)
392                 error(strcat("Can't write buf to ", pFilename));
393         n = buf_getsize(buf);
394         for(i = 0; i < n; ++i)
395                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
396         fclose(fh);
397 }
398
399 string GametypeNameFromType(float g)
400 {
401         if      (g == GAME_DEATHMATCH) return "dm";
402         else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
403         else if (g == GAME_DOMINATION) return "dom";
404         else if (g == GAME_CTF) return "ctf";
405         else if (g == GAME_RUNEMATCH) return "rune";
406         else if (g == GAME_LMS) return "lms";
407         else if (g == GAME_ARENA) return "arena";
408         else if (g == GAME_CA) return "ca";
409         else if (g == GAME_KEYHUNT) return "kh";
410         else if (g == GAME_ONSLAUGHT) return "ons";
411         else if (g == GAME_ASSAULT) return "as";
412         else if (g == GAME_RACE) return "rc";
413         else if (g == GAME_NEXBALL) return "nexball";
414         else if (g == GAME_CTS) return "cts";
415         else if (g == GAME_FREEZETAG) return "freezetag";
416         else if (g == GAME_KEEPAWAY) return "ka";
417         return "dm";
418 }
419
420 string mmsss(float tenths)
421 {
422         float minutes;
423         string s;
424         tenths = floor(tenths + 0.5);
425         minutes = floor(tenths / 600);
426         tenths -= minutes * 600;
427         s = ftos(1000 + tenths);
428         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
429 }
430
431 string mmssss(float hundredths)
432 {
433         float minutes;
434         string s;
435         hundredths = floor(hundredths + 0.5);
436         minutes = floor(hundredths / 6000);
437         hundredths -= minutes * 6000;
438         s = ftos(10000 + hundredths);
439         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
440 }
441
442 string ScoreString(float pFlags, float pValue)
443 {
444         string valstr;
445         float l;
446
447         pValue = floor(pValue + 0.5); // round
448
449         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
450                 valstr = "";
451         else if(pFlags & SFL_RANK)
452         {
453                 valstr = ftos(pValue);
454                 l = strlen(valstr);
455                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
456                         valstr = strcat(valstr, "th");
457                 else if(substring(valstr, l - 1, 1) == "1")
458                         valstr = strcat(valstr, "st");
459                 else if(substring(valstr, l - 1, 1) == "2")
460                         valstr = strcat(valstr, "nd");
461                 else if(substring(valstr, l - 1, 1) == "3")
462                         valstr = strcat(valstr, "rd");
463                 else
464                         valstr = strcat(valstr, "th");
465         }
466         else if(pFlags & SFL_TIME)
467                 valstr = TIME_ENCODED_TOSTRING(pValue);
468         else
469                 valstr = ftos(pValue);
470         
471         return valstr;
472 }
473
474 vector cross(vector a, vector b)
475 {
476         return
477                 '1 0 0' * (a_y * b_z - a_z * b_y)
478         +       '0 1 0' * (a_z * b_x - a_x * b_z)
479         +       '0 0 1' * (a_x * b_y - a_y * b_x);
480 }
481
482 // compressed vector format:
483 // like MD3, just even shorter
484 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
485 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
486 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
487 //     length = 2^(length_encoded/8) / 8
488 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
489 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
490 // the special value 0 indicates the zero vector
491
492 float lengthLogTable[128];
493
494 float invertLengthLog(float x)
495 {
496         float l, r, m, lerr, rerr;
497
498         if(x >= lengthLogTable[127])
499                 return 127;
500         if(x <= lengthLogTable[0])
501                 return 0;
502
503         l = 0;
504         r = 127;
505
506         while(r - l > 1)
507         {
508                 m = floor((l + r) / 2);
509                 if(lengthLogTable[m] < x)
510                         l = m;
511                 else
512                         r = m;
513         }
514
515         // now: r is >=, l is <
516         lerr = (x - lengthLogTable[l]);
517         rerr = (lengthLogTable[r] - x);
518         if(lerr < rerr)
519                 return l;
520         return r;
521 }
522
523 vector decompressShortVector(float data)
524 {
525         vector out;
526         float p, y, len;
527         if(data == 0)
528                 return '0 0 0';
529         p   = (data & 0xF000) / 0x1000;
530         y   = (data & 0x0F80) / 0x80;
531         len = (data & 0x007F);
532
533         //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
534
535         if(p == 0)
536         {
537                 out_x = 0;
538                 out_y = 0;
539                 if(y == 31)
540                         out_z = -1;
541                 else
542                         out_z = +1;
543         }
544         else
545         {
546                 y   = .19634954084936207740 * y;
547                 p = .19634954084936207740 * p - 1.57079632679489661922;
548                 out_x = cos(y) *  cos(p);
549                 out_y = sin(y) *  cos(p);
550                 out_z =          -sin(p);
551         }
552
553         //print("decompressed: ", vtos(out), "\n");
554
555         return out * lengthLogTable[len];
556 }
557
558 float compressShortVector(vector vec)
559 {
560         vector ang;
561         float p, y, len;
562         if(vlen(vec) == 0)
563                 return 0;
564         //print("compress: ", vtos(vec), "\n");
565         ang = vectoangles(vec);
566         ang_x = -ang_x;
567         if(ang_x < -90)
568                 ang_x += 360;
569         if(ang_x < -90 && ang_x > +90)
570                 error("BOGUS vectoangles");
571         //print("angles: ", vtos(ang), "\n");
572
573         p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
574         if(p == 0)
575         {
576                 if(vec_z < 0)
577                         y = 31;
578                 else
579                         y = 30;
580         }
581         else
582                 y = floor(0.5 + ang_y * 32 / 360)          & 31; // 0..360 to 0..32
583         len = invertLengthLog(vlen(vec));
584
585         //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
586
587         return (p * 0x1000) + (y * 0x80) + len;
588 }
589
590 void compressShortVector_init()
591 {
592         float l, f, i;
593         l = 1;
594         f = pow(2, 1/8);
595         for(i = 0; i < 128; ++i)
596         {
597                 lengthLogTable[i] = l;
598                 l *= f;
599         }
600
601         if(cvar("developer"))
602         {
603                 print("Verifying vector compression table...\n");
604                 for(i = 0x0F00; i < 0xFFFF; ++i)
605                         if(i != compressShortVector(decompressShortVector(i)))
606                         {
607                                 print("BROKEN vector compression: ", ftos(i));
608                                 print(" -> ", vtos(decompressShortVector(i)));
609                                 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
610                                 print("\n");
611                                 error("b0rk");
612                         }
613                 print("Done.\n");
614         }
615 }
616
617 #ifndef MENUQC
618 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
619 {
620         traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
621         traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
622         traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623         traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
624         traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
625         traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
626         traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
627         traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
628         traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
629         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
630         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
631         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
632         return 1;
633 }
634 #endif
635
636 string fixPriorityList(string order, float from, float to, float subtract, float complete)
637 {
638         string neworder;
639         float i, n, w;
640
641         n = tokenize_console(order);
642         neworder = "";
643         for(i = 0; i < n; ++i)
644         {
645                 w = stof(argv(i));
646                 if(w == floor(w))
647                 {
648                         if(w >= from && w <= to)
649                                 neworder = strcat(neworder, ftos(w), " ");
650                         else
651                         {
652                                 w -= subtract;
653                                 if(w >= from && w <= to)
654                                         neworder = strcat(neworder, ftos(w), " ");
655                         }
656                 }
657         }
658
659         if(complete)
660         {
661                 n = tokenize_console(neworder);
662                 for(w = to; w >= from; --w)
663                 {
664                         for(i = 0; i < n; ++i)
665                                 if(stof(argv(i)) == w)
666                                         break;
667                         if(i == n) // not found
668                                 neworder = strcat(neworder, ftos(w), " ");
669                 }
670         }
671         
672         return substring(neworder, 0, strlen(neworder) - 1);
673 }
674
675 string mapPriorityList(string order, string(string) mapfunc)
676 {
677         string neworder;
678         float i, n;
679
680         n = tokenize_console(order);
681         neworder = "";
682         for(i = 0; i < n; ++i)
683                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
684         
685         return substring(neworder, 0, strlen(neworder) - 1);
686 }
687
688 string swapInPriorityList(string order, float i, float j)
689 {
690         string s;
691         float w, n;
692
693         n = tokenize_console(order);
694
695         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
696         {
697                 s = "";
698                 for(w = 0; w < n; ++w)
699                 {
700                         if(w == i)
701                                 s = strcat(s, argv(j), " ");
702                         else if(w == j)
703                                 s = strcat(s, argv(i), " ");
704                         else
705                                 s = strcat(s, argv(w), " ");
706                 }
707                 return substring(s, 0, strlen(s) - 1);
708         }
709         
710         return order;
711 }
712
713 float cvar_value_issafe(string s)
714 {
715         if(strstrofs(s, "\"", 0) >= 0)
716                 return 0;
717         if(strstrofs(s, "\\", 0) >= 0)
718                 return 0;
719         if(strstrofs(s, ";", 0) >= 0)
720                 return 0;
721         if(strstrofs(s, "$", 0) >= 0)
722                 return 0;
723         if(strstrofs(s, "\r", 0) >= 0)
724                 return 0;
725         if(strstrofs(s, "\n", 0) >= 0)
726                 return 0;
727         return 1;
728 }
729
730 #ifndef MENUQC
731 void get_mi_min_max(float mode)
732 {
733         vector mi, ma;
734
735         if(mi_shortname)
736                 strunzone(mi_shortname);
737         mi_shortname = mapname;
738         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
739                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
740         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
741                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
742         mi_shortname = strzone(mi_shortname);
743
744 #ifdef CSQC
745         mi = world.mins;
746         ma = world.maxs;
747 #else
748         mi = world.absmin;
749         ma = world.absmax;
750 #endif
751
752         mi_min = mi;
753         mi_max = ma;
754         MapInfo_Get_ByName(mi_shortname, 0, 0);
755         if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
756         {
757                 mi_min = MapInfo_Map_mins;
758                 mi_max = MapInfo_Map_maxs;
759         }
760         else
761         {
762                 // not specified
763                 if(mode)
764                 {
765                         // be clever
766                         tracebox('1 0 0' * mi_x,
767                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
768                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
769                                          '1 0 0' * ma_x,
770                                          MOVE_WORLDONLY,
771                                          world);
772                         if(!trace_startsolid)
773                                 mi_min_x = trace_endpos_x;
774
775                         tracebox('0 1 0' * mi_y,
776                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
777                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
778                                          '0 1 0' * ma_y,
779                                          MOVE_WORLDONLY,
780                                          world);
781                         if(!trace_startsolid)
782                                 mi_min_y = trace_endpos_y;
783
784                         tracebox('0 0 1' * mi_z,
785                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
786                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
787                                          '0 0 1' * ma_z,
788                                          MOVE_WORLDONLY,
789                                          world);
790                         if(!trace_startsolid)
791                                 mi_min_z = trace_endpos_z;
792
793                         tracebox('1 0 0' * ma_x,
794                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
795                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
796                                          '1 0 0' * mi_x,
797                                          MOVE_WORLDONLY,
798                                          world);
799                         if(!trace_startsolid)
800                                 mi_max_x = trace_endpos_x;
801
802                         tracebox('0 1 0' * ma_y,
803                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
804                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
805                                          '0 1 0' * mi_y,
806                                          MOVE_WORLDONLY,
807                                          world);
808                         if(!trace_startsolid)
809                                 mi_max_y = trace_endpos_y;
810
811                         tracebox('0 0 1' * ma_z,
812                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
813                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
814                                          '0 0 1' * mi_z,
815                                          MOVE_WORLDONLY,
816                                          world);
817                         if(!trace_startsolid)
818                                 mi_max_z = trace_endpos_z;
819                 }
820         }
821 }
822
823 void get_mi_min_max_texcoords(float mode)
824 {
825         vector extend;
826
827         get_mi_min_max(mode);
828
829         mi_picmin = mi_min;
830         mi_picmax = mi_max;
831
832         // extend mi_picmax to get a square aspect ratio
833         // center the map in that area
834         extend = mi_picmax - mi_picmin;
835         if(extend_y > extend_x)
836         {
837                 mi_picmin_x -= (extend_y - extend_x) * 0.5;
838                 mi_picmax_x += (extend_y - extend_x) * 0.5;
839         }
840         else
841         {
842                 mi_picmin_y -= (extend_x - extend_y) * 0.5;
843                 mi_picmax_y += (extend_x - extend_y) * 0.5;
844         }
845
846         // add another some percent
847         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
848         mi_picmin -= extend;
849         mi_picmax += extend;
850
851         // calculate the texcoords
852         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
853         // first the two corners of the origin
854         mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
855         mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
856         mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
857         mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
858         // then the other corners
859         mi_pictexcoord1_x = mi_pictexcoord0_x;
860         mi_pictexcoord1_y = mi_pictexcoord2_y;
861         mi_pictexcoord3_x = mi_pictexcoord2_x;
862         mi_pictexcoord3_y = mi_pictexcoord0_y;
863 }
864 #endif
865
866 float cvar_settemp(string tmp_cvar, string tmp_value)
867 {
868         float created_saved_value;
869         entity e;
870         
871         if not(tmp_cvar || tmp_value)
872         {
873                 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
874                 return FALSE;
875         }
876         
877         for(e = world; (e = find(e, classname, "saved_cvar_value")); )
878                 if(e.netname == tmp_cvar)
879                         goto saved; // skip creation
880                         
881         // creating a new entity to keep track of this cvar
882         e = spawn();
883         e.classname = "saved_cvar_value";
884         e.netname = strzone(tmp_cvar);
885         e.message = strzone(cvar_string(tmp_cvar));
886         created_saved_value = TRUE;
887         
888         // an entity for this cvar already exists
889         :saved
890         
891         // update the cvar to the value given
892         cvar_set(tmp_cvar, tmp_value);
893         
894         return created_saved_value;
895 }
896
897 float cvar_settemp_restore()
898 {
899         float i;
900         entity e;
901         while((e = find(world, classname, "saved_cvar_value")))
902         {
903                 cvar_set(e.netname, e.message);
904                 remove(e);
905         }
906         
907         return i;
908 }
909
910 float almost_equals(float a, float b)
911 {
912         float eps;
913         eps = (max(a, -a) + max(b, -b)) * 0.001;
914         if(a - b < eps && b - a < eps)
915                 return TRUE;
916         return FALSE;
917 }
918
919 float almost_in_bounds(float a, float b, float c)
920 {
921         float eps;
922         eps = (max(a, -a) + max(c, -c)) * 0.001;
923         return b == median(a - eps, b, c + eps);
924 }
925
926 float power2of(float e)
927 {
928         return pow(2, e);
929 }
930 float log2of(float x)
931 {
932         // NOTE: generated code
933         if(x > 2048)
934                 if(x > 131072)
935                         if(x > 1048576)
936                                 if(x > 4194304)
937                                         return 23;
938                                 else
939                                         if(x > 2097152)
940                                                 return 22;
941                                         else
942                                                 return 21;
943                         else
944                                 if(x > 524288)
945                                         return 20;
946                                 else
947                                         if(x > 262144)
948                                                 return 19;
949                                         else
950                                                 return 18;
951                 else
952                         if(x > 16384)
953                                 if(x > 65536)
954                                         return 17;
955                                 else
956                                         if(x > 32768)
957                                                 return 16;
958                                         else
959                                                 return 15;
960                         else
961                                 if(x > 8192)
962                                         return 14;
963                                 else
964                                         if(x > 4096)
965                                                 return 13;
966                                         else
967                                                 return 12;
968         else
969                 if(x > 32)
970                         if(x > 256)
971                                 if(x > 1024)
972                                         return 11;
973                                 else
974                                         if(x > 512)
975                                                 return 10;
976                                         else
977                                                 return 9;
978                         else
979                                 if(x > 128)
980                                         return 8;
981                                 else
982                                         if(x > 64)
983                                                 return 7;
984                                         else
985                                                 return 6;
986                 else
987                         if(x > 4)
988                                 if(x > 16)
989                                         return 5;
990                                 else
991                                         if(x > 8)
992                                                 return 4;
993                                         else
994                                                 return 3;
995                         else
996                                 if(x > 2)
997                                         return 2;
998                                 else
999                                         if(x > 1)
1000                                                 return 1;
1001                                         else
1002                                                 return 0;
1003 }
1004
1005 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1006 {
1007         if(mi == ma)
1008                 return 0;
1009         else if(ma == rgb_x)
1010         {
1011                 if(rgb_y >= rgb_z)
1012                         return (rgb_y - rgb_z) / (ma - mi);
1013                 else
1014                         return (rgb_y - rgb_z) / (ma - mi) + 6;
1015         }
1016         else if(ma == rgb_y)
1017                 return (rgb_z - rgb_x) / (ma - mi) + 2;
1018         else // if(ma == rgb_z)
1019                 return (rgb_x - rgb_y) / (ma - mi) + 4;
1020 }
1021
1022 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1023 {
1024         vector rgb;
1025
1026         hue -= 6 * floor(hue / 6);
1027
1028         //else if(ma == rgb_x)
1029         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1030         if(hue <= 1)
1031         {
1032                 rgb_x = ma;
1033                 rgb_y = hue * (ma - mi) + mi;
1034                 rgb_z = mi;
1035         }
1036         //else if(ma == rgb_y)
1037         //      hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1038         else if(hue <= 2)
1039         {
1040                 rgb_x = (2 - hue) * (ma - mi) + mi;
1041                 rgb_y = ma;
1042                 rgb_z = mi;
1043         }
1044         else if(hue <= 3)
1045         {
1046                 rgb_x = mi;
1047                 rgb_y = ma;
1048                 rgb_z = (hue - 2) * (ma - mi) + mi;
1049         }
1050         //else // if(ma == rgb_z)
1051         //      hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1052         else if(hue <= 4)
1053         {
1054                 rgb_x = mi;
1055                 rgb_y = (4 - hue) * (ma - mi) + mi;
1056                 rgb_z = ma;
1057         }
1058         else if(hue <= 5)
1059         {
1060                 rgb_x = (hue - 4) * (ma - mi) + mi;
1061                 rgb_y = mi;
1062                 rgb_z = ma;
1063         }
1064         //else if(ma == rgb_x)
1065         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1066         else // if(hue <= 6)
1067         {
1068                 rgb_x = ma;
1069                 rgb_y = mi;
1070                 rgb_z = (6 - hue) * (ma - mi) + mi;
1071         }
1072
1073         return rgb;
1074 }
1075
1076 vector rgb_to_hsv(vector rgb)
1077 {
1078         float mi, ma;
1079         vector hsv;
1080
1081         mi = min(rgb_x, rgb_y, rgb_z);
1082         ma = max(rgb_x, rgb_y, rgb_z);
1083
1084         hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1085         hsv_z = ma;
1086
1087         if(ma == 0)
1088                 hsv_y = 0;
1089         else
1090                 hsv_y = 1 - mi/ma;
1091         
1092         return hsv;
1093 }
1094
1095 vector hsv_to_rgb(vector hsv)
1096 {
1097         return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1098 }
1099
1100 vector rgb_to_hsl(vector rgb)
1101 {
1102         float mi, ma;
1103         vector hsl;
1104
1105         mi = min(rgb_x, rgb_y, rgb_z);
1106         ma = max(rgb_x, rgb_y, rgb_z);
1107
1108         hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1109         
1110         hsl_z = 0.5 * (mi + ma);
1111         if(mi == ma)
1112                 hsl_y = 0;
1113         else if(hsl_z <= 0.5)
1114                 hsl_y = (ma - mi) / (2*hsl_z);
1115         else // if(hsl_z > 0.5)
1116                 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1117         
1118         return hsl;
1119 }
1120
1121 vector hsl_to_rgb(vector hsl)
1122 {
1123         float mi, ma, maminusmi;
1124
1125         if(hsl_z <= 0.5)
1126                 maminusmi = hsl_y * 2 * hsl_z;
1127         else
1128                 maminusmi = hsl_y * (2 - 2 * hsl_z);
1129         
1130         // hsl_z     = 0.5 * mi + 0.5 * ma
1131         // maminusmi =     - mi +       ma
1132         mi = hsl_z - 0.5 * maminusmi;
1133         ma = hsl_z + 0.5 * maminusmi;
1134
1135         return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1136 }
1137
1138 string rgb_to_hexcolor(vector rgb)
1139 {
1140         return
1141                 strcat(
1142                         "^x",
1143                         DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1144                         DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1145                         DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1146                 );
1147 }
1148
1149 // requires that m2>m1 in all coordinates, and that m4>m3
1150 float boxesoverlap(vector m1, vector m2, vector m3, vector m4) {return m2_x >= m3_x && m1_x <= m4_x && m2_y >= m3_y && m1_y <= m4_y && m2_z >= m3_z && m1_z <= m4_z;}
1151
1152 // requires the same, but is a stronger condition
1153 float boxinsidebox(vector smins, vector smaxs, vector bmins, vector bmaxs) {return smins_x >= bmins_x && smaxs_x <= bmaxs_x && smins_y >= bmins_y && smaxs_y <= bmaxs_y && smins_z >= bmins_z && smaxs_z <= bmaxs_z;}
1154
1155 #ifndef MENUQC
1156 #endif
1157
1158 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1159 {
1160         // STOP.
1161         // The following function is SLOW.
1162         // For your safety and for the protection of those around you...
1163         // DO NOT CALL THIS AT HOME.
1164         // No really, don't.
1165         if(w(theText, theSize) <= maxWidth)
1166                 return strlen(theText); // yeah!
1167
1168         // binary search for right place to cut string
1169         float ch;
1170         float left, right, middle; // this always works
1171         left = 0;
1172         right = strlen(theText); // this always fails
1173         do
1174         {
1175                 middle = floor((left + right) / 2);
1176                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1177                         left = middle;
1178                 else
1179                         right = middle;
1180         }
1181         while(left < right - 1);
1182
1183         if(w("^7", theSize) == 0) // detect color codes support in the width function
1184         {
1185                 // NOTE: when color codes are involved, this binary search is,
1186                 // mathematically, BROKEN. However, it is obviously guaranteed to
1187                 // terminate, as the range still halves each time - but nevertheless, it is
1188                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1189                 // range, and "right" is outside).
1190                 
1191                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1192                 // and decrease left on the basis of the chars detected of the truncated tag
1193                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1194                 // (sometimes too much but with a correct result)
1195                 // it fixes also ^[0-9]
1196                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1197                         left-=1;
1198
1199                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1200                         left-=2;
1201                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1202                         {
1203                                 ch = str2chr(theText, left-1);
1204                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1205                                         left-=3;
1206                         }
1207                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1208                         {
1209                                 ch = str2chr(theText, left-2);
1210                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1211                                 {
1212                                         ch = str2chr(theText, left-1);
1213                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1214                                                 left-=4;
1215                                 }
1216                         }
1217         }
1218         
1219         return left;
1220 }
1221
1222 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1223 {
1224         // STOP.
1225         // The following function is SLOW.
1226         // For your safety and for the protection of those around you...
1227         // DO NOT CALL THIS AT HOME.
1228         // No really, don't.
1229         if(w(theText) <= maxWidth)
1230                 return strlen(theText); // yeah!
1231
1232         // binary search for right place to cut string
1233         float ch;
1234         float left, right, middle; // this always works
1235         left = 0;
1236         right = strlen(theText); // this always fails
1237         do
1238         {
1239                 middle = floor((left + right) / 2);
1240                 if(w(substring(theText, 0, middle)) <= maxWidth)
1241                         left = middle;
1242                 else
1243                         right = middle;
1244         }
1245         while(left < right - 1);
1246
1247         if(w("^7") == 0) // detect color codes support in the width function
1248         {
1249                 // NOTE: when color codes are involved, this binary search is,
1250                 // mathematically, BROKEN. However, it is obviously guaranteed to
1251                 // terminate, as the range still halves each time - but nevertheless, it is
1252                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1253                 // range, and "right" is outside).
1254                 
1255                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1256                 // and decrease left on the basis of the chars detected of the truncated tag
1257                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1258                 // (sometimes too much but with a correct result)
1259                 // it fixes also ^[0-9]
1260                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1261                         left-=1;
1262
1263                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1264                         left-=2;
1265                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1266                         {
1267                                 ch = str2chr(theText, left-1);
1268                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1269                                         left-=3;
1270                         }
1271                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1272                         {
1273                                 ch = str2chr(theText, left-2);
1274                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1275                                 {
1276                                         ch = str2chr(theText, left-1);
1277                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1278                                                 left-=4;
1279                                 }
1280                         }
1281         }
1282         
1283         return left;
1284 }
1285
1286 string find_last_color_code(string s)
1287 {
1288         float start, len, i, carets;
1289         start = strstrofs(s, "^", 0);
1290         if (start == -1) // no caret found
1291                 return "";
1292         len = strlen(s)-1;
1293         for(i = len; i >= start; --i)
1294         {
1295                 if(substring(s, i, 1) != "^")
1296                         continue;
1297
1298                 carets = 1;
1299                 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1300                         ++carets;
1301
1302                 // check if carets aren't all escaped
1303                 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1304                 {
1305                         if(i+1 <= len)
1306                         if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1307                                 return substring(s, i, 2);
1308
1309                         if(i+4 <= len)
1310                         if(substring(s, i+1, 1) == "x")
1311                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1312                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1313                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1314                                 return substring(s, i, 5);
1315                 }
1316                 i -= carets; // this also skips one char before the carets
1317         }
1318
1319         return "";
1320 }
1321
1322 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1323 {
1324         float cantake;
1325         float take;
1326         string s;
1327
1328         s = getWrappedLine_remaining;
1329         
1330         if(w <= 0)
1331         {
1332                 getWrappedLine_remaining = string_null;
1333                 return s; // the line has no size ANYWAY, nothing would be displayed.
1334         }
1335
1336         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1337         if(cantake > 0 && cantake < strlen(s))
1338         {
1339                 take = cantake - 1;
1340                 while(take > 0 && substring(s, take, 1) != " ")
1341                         --take;
1342                 if(take == 0)
1343                 {
1344                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1345                         if(getWrappedLine_remaining == "")
1346                                 getWrappedLine_remaining = string_null;
1347                         else if (tw("^7", theFontSize) == 0)
1348                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1349                         return substring(s, 0, cantake);
1350                 }
1351                 else
1352                 {
1353                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1354                         if(getWrappedLine_remaining == "")
1355                                 getWrappedLine_remaining = string_null;
1356                         else if (tw("^7", theFontSize) == 0)
1357                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1358                         return substring(s, 0, take);
1359                 }
1360         }
1361         else
1362         {
1363                 getWrappedLine_remaining = string_null;
1364                 return s;
1365         }
1366 }
1367
1368 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1369 {
1370         float cantake;
1371         float take;
1372         string s;
1373
1374         s = getWrappedLine_remaining;
1375         
1376         if(w <= 0)
1377         {
1378                 getWrappedLine_remaining = string_null;
1379                 return s; // the line has no size ANYWAY, nothing would be displayed.
1380         }
1381
1382         cantake = textLengthUpToLength(s, w, tw);
1383         if(cantake > 0 && cantake < strlen(s))
1384         {
1385                 take = cantake - 1;
1386                 while(take > 0 && substring(s, take, 1) != " ")
1387                         --take;
1388                 if(take == 0)
1389                 {
1390                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1391                         if(getWrappedLine_remaining == "")
1392                                 getWrappedLine_remaining = string_null;
1393                         else if (tw("^7") == 0)
1394                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1395                         return substring(s, 0, cantake);
1396                 }
1397                 else
1398                 {
1399                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1400                         if(getWrappedLine_remaining == "")
1401                                 getWrappedLine_remaining = string_null;
1402                         else if (tw("^7") == 0)
1403                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1404                         return substring(s, 0, take);
1405                 }
1406         }
1407         else
1408         {
1409                 getWrappedLine_remaining = string_null;
1410                 return s;
1411         }
1412 }
1413
1414 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1415 {
1416         if(tw(theText, theFontSize) <= maxWidth)
1417                 return theText;
1418         else
1419                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1420 }
1421
1422 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1423 {
1424         if(tw(theText) <= maxWidth)
1425                 return theText;
1426         else
1427                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1428 }
1429
1430 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1431 {
1432         string subpattern, subpattern2, subpattern3, subpattern4;
1433         subpattern = strcat(",", GametypeNameFromType(gt), ",");
1434         if(tp)
1435                 subpattern2 = ",teams,";
1436         else
1437                 subpattern2 = ",noteams,";
1438         if(ts)
1439                 subpattern3 = ",teamspawns,";
1440         else
1441                 subpattern3 = ",noteamspawns,";
1442         if(gt == GAME_RACE || gt == GAME_CTS)
1443                 subpattern4 = ",race,";
1444         else
1445                 subpattern4 = string_null;
1446
1447         if(substring(pattern, 0, 1) == "-")
1448         {
1449                 pattern = substring(pattern, 1, strlen(pattern) - 1);
1450                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1451                         return 0;
1452                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1453                         return 0;
1454                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1455                         return 0;
1456                 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1457                         return 0;
1458         }
1459         else
1460         {
1461                 if(substring(pattern, 0, 1) == "+")
1462                         pattern = substring(pattern, 1, strlen(pattern) - 1);
1463                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1464                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1465                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1466                 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1467                         return 0;
1468         }
1469         return 1;
1470 }
1471
1472 void shuffle(float n, swapfunc_t swap, entity pass)
1473 {
1474         float i, j;
1475         for(i = 1; i < n; ++i)
1476         {
1477                 // swap i-th item at a random position from 0 to i
1478                 // proof for even distribution:
1479                 //   n = 1: obvious
1480                 //   n -> n+1:
1481                 //     item n+1 gets at any position with chance 1/(n+1)
1482                 //     all others will get their 1/n chance reduced by factor n/(n+1)
1483                 //     to be on place n+1, their chance will be 1/(n+1)
1484                 //     1/n * n/(n+1) = 1/(n+1)
1485                 //     q.e.d.
1486                 j = floor(random() * (i + 1));
1487                 if(j != i)
1488                         swap(j, i, pass);
1489         }
1490 }
1491
1492 string substring_range(string s, float b, float e)
1493 {
1494         return substring(s, b, e - b);
1495 }
1496
1497 string swapwords(string str, float i, float j)
1498 {
1499         float n;
1500         string s1, s2, s3, s4, s5;
1501         float si, ei, sj, ej, s0, en;
1502         n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1503         si = argv_start_index(i);
1504         sj = argv_start_index(j);
1505         ei = argv_end_index(i);
1506         ej = argv_end_index(j);
1507         s0 = argv_start_index(0);
1508         en = argv_end_index(n-1);
1509         s1 = substring_range(str, s0, si);
1510         s2 = substring_range(str, si, ei);
1511         s3 = substring_range(str, ei, sj);
1512         s4 = substring_range(str, sj, ej);
1513         s5 = substring_range(str, ej, en);
1514         return strcat(s1, s4, s3, s2, s5);
1515 }
1516
1517 string _shufflewords_str;
1518 void _shufflewords_swapfunc(float i, float j, entity pass)
1519 {
1520         _shufflewords_str = swapwords(_shufflewords_str, i, j);
1521 }
1522 string shufflewords(string str)
1523 {
1524         float n;
1525         _shufflewords_str = str;
1526         n = tokenizebyseparator(str, " ");
1527         shuffle(n, _shufflewords_swapfunc, world);
1528         str = _shufflewords_str;
1529         _shufflewords_str = string_null;
1530         return str;
1531 }
1532
1533 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1534 {
1535         vector v;
1536         float D;
1537         v = '0 0 0';
1538         if(a == 0)
1539         {
1540                 if(b != 0)
1541                 {
1542                         v_x = v_y = -c / b;
1543                         v_z = 1;
1544                 }
1545                 else
1546                 {
1547                         if(c == 0)
1548                         {
1549                                 // actually, every number solves the equation!
1550                                 v_z = 1;
1551                         }
1552                 }
1553         }
1554         else
1555         {
1556                 D = b*b - 4*a*c;
1557                 if(D >= 0)
1558                 {
1559                         D = sqrt(D);
1560                         if(a > 0) // put the smaller solution first
1561                         {
1562                                 v_x = ((-b)-D) / (2*a);
1563                                 v_y = ((-b)+D) / (2*a);
1564                         }
1565                         else
1566                         {
1567                                 v_x = (-b+D) / (2*a);
1568                                 v_y = (-b-D) / (2*a);
1569                         }
1570                         v_z = 1;
1571                 }
1572                 else
1573                 {
1574                         // complex solutions!
1575                         D = sqrt(-D);
1576                         v_x = -b / (2*a);
1577                         if(a > 0)
1578                                 v_y =  D / (2*a);
1579                         else
1580                                 v_y = -D / (2*a);
1581                         v_z = 0;
1582                 }
1583         }
1584         return v;
1585 }
1586
1587 void check_unacceptable_compiler_bugs()
1588 {
1589         if(cvar("_allow_unacceptable_compiler_bugs"))
1590                 return;
1591         tokenize_console("foo bar");
1592         if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1593                 error("fteqcc bug introduced with revision 3178 detected. Please upgrade fteqcc to a later revision, downgrade fteqcc to revision 3177, or pester Spike until he fixes it. You can set _allow_unacceptable_compiler_bugs 1 to skip this check, but expect stuff to be horribly broken then.");
1594 }
1595
1596 float compressShotOrigin(vector v)
1597 {
1598         float x, y, z;
1599         x = rint(v_x * 2);
1600         y = rint(v_y * 4) + 128;
1601         z = rint(v_z * 4) + 128;
1602         if(x > 255 || x < 0)
1603         {
1604                 print("shot origin ", vtos(v), " x out of bounds\n");
1605                 x = bound(0, x, 255);
1606         }
1607         if(y > 255 || y < 0)
1608         {
1609                 print("shot origin ", vtos(v), " y out of bounds\n");
1610                 y = bound(0, y, 255);
1611         }
1612         if(z > 255 || z < 0)
1613         {
1614                 print("shot origin ", vtos(v), " z out of bounds\n");
1615                 z = bound(0, z, 255);
1616         }
1617         return x * 0x10000 + y * 0x100 + z;
1618 }
1619 vector decompressShotOrigin(float f)
1620 {
1621         vector v;
1622         v_x = ((f & 0xFF0000) / 0x10000) / 2;
1623         v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1624         v_z = ((f & 0xFF) - 128) / 4;
1625         return v;
1626 }
1627
1628 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1629 {
1630         float start, end, root, child;
1631
1632         // heapify
1633         start = floor((n - 2) / 2);
1634         while(start >= 0)
1635         {
1636                 // siftdown(start, count-1);
1637                 root = start;
1638                 while(root * 2 + 1 <= n-1)
1639                 {
1640                         child = root * 2 + 1;
1641                         if(child < n-1)
1642                                 if(cmp(child, child+1, pass) < 0)
1643                                         ++child;
1644                         if(cmp(root, child, pass) < 0)
1645                         {
1646                                 swap(root, child, pass);
1647                                 root = child;
1648                         }
1649                         else
1650                                 break;
1651                 }
1652                 // end of siftdown
1653                 --start;
1654         }
1655
1656         // extract
1657         end = n - 1;
1658         while(end > 0)
1659         {
1660                 swap(0, end, pass);
1661                 --end;
1662                 // siftdown(0, end);
1663                 root = 0;
1664                 while(root * 2 + 1 <= end)
1665                 {
1666                         child = root * 2 + 1;
1667                         if(child < end && cmp(child, child+1, pass) < 0)
1668                                 ++child;
1669                         if(cmp(root, child, pass) < 0)
1670                         {
1671                                 swap(root, child, pass);
1672                                 root = child;
1673                         }
1674                         else
1675                                 break;
1676                 }
1677                 // end of siftdown
1678         }
1679 }
1680
1681 void RandomSelection_Init()
1682 {
1683         RandomSelection_totalweight = 0;
1684         RandomSelection_chosen_ent = world;
1685         RandomSelection_chosen_float = 0;
1686         RandomSelection_chosen_string = string_null;
1687         RandomSelection_best_priority = -1;
1688 }
1689 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1690 {
1691         if(priority > RandomSelection_best_priority)
1692         {
1693                 RandomSelection_best_priority = priority;
1694                 RandomSelection_chosen_ent = e;
1695                 RandomSelection_chosen_float = f;
1696                 RandomSelection_chosen_string = s;
1697                 RandomSelection_totalweight = weight;
1698         }
1699         else if(priority == RandomSelection_best_priority)
1700         {
1701                 RandomSelection_totalweight += weight;
1702                 if(random() * RandomSelection_totalweight <= weight)
1703                 {
1704                         RandomSelection_chosen_ent = e;
1705                         RandomSelection_chosen_float = f;
1706                         RandomSelection_chosen_string = s;
1707                 }
1708         }
1709 }
1710
1711 vector healtharmor_maxdamage(float h, float a, float armorblock)
1712 {
1713         // NOTE: we'll always choose the SMALLER value...
1714         float healthdamage, armordamage, armorideal;
1715         vector v;
1716         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1717         armordamage = a + (h - 1); // damage we can take if we could use more armor
1718         armorideal = healthdamage * armorblock;
1719         v_y = armorideal;
1720         if(armordamage < healthdamage)
1721         {
1722                 v_x = armordamage;
1723                 v_z = 1;
1724         }
1725         else
1726         {
1727                 v_x = healthdamage;
1728                 v_z = 0;
1729         }
1730         return v;
1731 }
1732
1733 vector healtharmor_applydamage(float a, float armorblock, float damage)
1734 {
1735         vector v;
1736         v_y = bound(0, damage * armorblock, a); // save
1737         v_x = bound(0, damage - v_y, damage); // take
1738         v_z = 0;
1739         return v;
1740 }
1741
1742 string getcurrentmod()
1743 {
1744         float n;
1745         string m;
1746         m = cvar_string("fs_gamedir");
1747         n = tokenize_console(m);
1748         if(n == 0)
1749                 return "data";
1750         else
1751                 return argv(n - 1);
1752 }
1753
1754 #ifndef MENUQC
1755 #ifdef CSQC
1756 float ReadInt24_t()
1757 {
1758         float v;
1759         v = ReadShort() * 256; // note: this is signed
1760         v += ReadByte(); // note: this is unsigned
1761         return v;
1762 }
1763 #else
1764 void WriteInt24_t(float dst, float val)
1765 {
1766         float v;
1767         WriteShort(dst, (v = floor(val / 256)));
1768         WriteByte(dst, val - v * 256); // 0..255
1769 }
1770 #endif
1771 #endif
1772
1773 float float2range11(float f)
1774 {
1775         // continuous function mapping all reals into -1..1
1776         return f / (fabs(f) + 1);
1777 }
1778
1779 float float2range01(float f)
1780 {
1781         // continuous function mapping all reals into 0..1
1782         return 0.5 + 0.5 * float2range11(f);
1783 }
1784
1785 // from the GNU Scientific Library
1786 float gsl_ran_gaussian_lastvalue;
1787 float gsl_ran_gaussian_lastvalue_set;
1788 float gsl_ran_gaussian(float sigma)
1789 {
1790         float a, b;
1791         if(gsl_ran_gaussian_lastvalue_set)
1792         {
1793                 gsl_ran_gaussian_lastvalue_set = 0;
1794                 return sigma * gsl_ran_gaussian_lastvalue;
1795         }
1796         else
1797         {
1798                 a = random() * 2 * M_PI;
1799                 b = sqrt(-2 * log(random()));
1800                 gsl_ran_gaussian_lastvalue = cos(a) * b;
1801                 gsl_ran_gaussian_lastvalue_set = 1;
1802                 return sigma * sin(a) * b;
1803         }
1804 }
1805
1806 string car(string s)
1807 {
1808         float o;
1809         o = strstrofs(s, " ", 0);
1810         if(o < 0)
1811                 return s;
1812         return substring(s, 0, o);
1813 }
1814 string cdr(string s)
1815 {
1816         float o;
1817         o = strstrofs(s, " ", 0);
1818         if(o < 0)
1819                 return string_null;
1820         return substring(s, o + 1, strlen(s) - (o + 1));
1821 }
1822 float matchacl(string acl, string str)
1823 {
1824         string t, s;
1825         float r, d;
1826         r = 0;
1827         while(acl)
1828         {
1829                 t = car(acl); acl = cdr(acl);
1830                 d = 1;
1831                 if(substring(t, 0, 1) == "-")
1832                 {
1833                         d = -1;
1834                         t = substring(t, 1, strlen(t) - 1);
1835                 }
1836                 else if(substring(t, 0, 1) == "+")
1837                         t = substring(t, 1, strlen(t) - 1);
1838                 if(substring(t, -1, 1) == "*")
1839                 {
1840                         t = substring(t, 0, strlen(t) - 1);
1841                         s = substring(s, 0, strlen(t));
1842                 }
1843                 else
1844                         s = str;
1845
1846                 if(s == t)
1847                 {
1848                         r = d;
1849                 }
1850         }
1851         return r;
1852 }
1853 float startsWith(string haystack, string needle)
1854 {
1855         return substring(haystack, 0, strlen(needle)) == needle;
1856 }
1857 float startsWithNocase(string haystack, string needle)
1858 {
1859         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1860 }
1861
1862 string get_model_datafilename(string m, float sk, string fil)
1863 {
1864         if(m)
1865                 m = strcat(m, "_");
1866         else
1867                 m = "models/player/*_";
1868         if(sk >= 0)
1869                 m = strcat(m, ftos(sk));
1870         else
1871                 m = strcat(m, "*");
1872         return strcat(m, ".", fil);
1873 }
1874
1875 float get_model_parameters(string m, float sk)
1876 {
1877         string fn, s, c;
1878         float fh;
1879
1880         get_model_parameters_modelname = string_null;
1881         get_model_parameters_modelskin = -1;
1882         get_model_parameters_name = string_null;
1883         get_model_parameters_species = -1;
1884         get_model_parameters_sex = string_null;
1885         get_model_parameters_weight = -1;
1886         get_model_parameters_age = -1;
1887         get_model_parameters_desc = string_null;
1888
1889         if not(m)
1890                 return 1;
1891         if(sk < 0)
1892         {
1893                 if(substring(m, -4, -1) != ".txt")
1894                         return 0;
1895                 if(substring(m, -6, 1) != "_")
1896                         return 0;
1897                 sk = stof(substring(m, -5, 1));
1898                 m = substring(m, 0, -7);
1899         }
1900
1901         fn = get_model_datafilename(m, sk, "txt");
1902         fh = fopen(fn, FILE_READ);
1903         if(fh < 0)
1904         {
1905                 sk = 0;
1906                 fn = get_model_datafilename(m, sk, "txt");
1907                 fh = fopen(fn, FILE_READ);
1908                 if(fh < 0)
1909                         return 0;
1910         }
1911
1912         get_model_parameters_modelname = m;
1913         get_model_parameters_modelskin = sk;
1914         while((s = fgets(fh)))
1915         {
1916                 if(s == "")
1917                         break; // next lines will be description
1918                 c = car(s);
1919                 s = cdr(s);
1920                 if(c == "name")
1921                         get_model_parameters_name = s;
1922                 if(c == "species")
1923                         switch(s)
1924                         {
1925                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
1926                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
1927                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1928                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1929                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1930                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
1931                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
1932                         }
1933                 if(c == "sex")
1934                         get_model_parameters_sex = s;
1935                 if(c == "weight")
1936                         get_model_parameters_weight = stof(s);
1937                 if(c == "age")
1938                         get_model_parameters_age = stof(s);
1939         }
1940
1941         while((s = fgets(fh)))
1942         {
1943                 if(get_model_parameters_desc)
1944                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1945                 if(s != "")
1946                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1947         }
1948
1949         fclose(fh);
1950
1951         return 1;
1952 }
1953
1954 vector vec2(vector v)
1955 {
1956         v_z = 0;
1957         return v;
1958 }
1959
1960 #ifndef MENUQC
1961 vector NearestPointOnBox(entity box, vector org)
1962 {
1963         vector m1, m2, nearest;
1964
1965         m1 = box.mins + box.origin;
1966         m2 = box.maxs + box.origin;
1967
1968         nearest_x = bound(m1_x, org_x, m2_x);
1969         nearest_y = bound(m1_y, org_y, m2_y);
1970         nearest_z = bound(m1_z, org_z, m2_z);
1971
1972         return nearest;
1973 }
1974 #endif
1975
1976 float vercmp_recursive(string v1, string v2)
1977 {
1978         float dot1, dot2;
1979         string s1, s2;
1980         float r;
1981
1982         dot1 = strstrofs(v1, ".", 0);
1983         dot2 = strstrofs(v2, ".", 0);
1984         if(dot1 == -1)
1985                 s1 = v1;
1986         else
1987                 s1 = substring(v1, 0, dot1);
1988         if(dot2 == -1)
1989                 s2 = v2;
1990         else
1991                 s2 = substring(v2, 0, dot2);
1992
1993         r = stof(s1) - stof(s2);
1994         if(r != 0)
1995                 return r;
1996
1997         r = strcasecmp(s1, s2);
1998         if(r != 0)
1999                 return r;
2000
2001         if(dot1 == -1)
2002                 if(dot2 == -1)
2003                         return 0;
2004                 else
2005                         return -1;
2006         else
2007                 if(dot2 == -1)
2008                         return 1;
2009                 else
2010                         return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2011 }
2012
2013 float vercmp(string v1, string v2)
2014 {
2015         if(strcasecmp(v1, v2) == 0) // early out check
2016                 return 0;
2017
2018         // "git" beats all
2019         if(v1 == "git")
2020                 return 1;
2021         if(v2 == "git")
2022                 return -1;
2023
2024         return vercmp_recursive(v1, v2);
2025 }
2026
2027 float u8_strsize(string s)
2028 {
2029         float l, i, c;
2030         l = 0;
2031         for(i = 0; ; ++i)
2032         {
2033                 c = str2chr(s, i);
2034                 if(c <= 0)
2035                         break;
2036                 ++l;
2037                 if(c >= 0x80)
2038                         ++l;
2039                 if(c >= 0x800)
2040                         ++l;
2041                 if(c >= 0x10000)
2042                         ++l;
2043         }
2044         return l;
2045 }
2046
2047 // translation helpers
2048 string language_filename(string s)
2049 {
2050         string fn;
2051         float fh;
2052         fn = prvm_language;
2053         if(fn == "" || fn == "dump")
2054                 return s;
2055         fn = strcat(s, ".", fn);
2056         if((fh = fopen(fn, FILE_READ)) >= 0)
2057         {
2058                 fclose(fh);
2059                 return fn;
2060         }
2061         return s;
2062 }
2063 string CTX(string s)
2064 {
2065         float p = strstrofs(s, "^", 0);
2066         if(p < 0)
2067                 return s;
2068         return substring(s, p+1, -1);
2069 }
2070
2071 // x-encoding (encoding as zero length invisible string)
2072 const string XENCODE_2  = "xX";
2073 const string XENCODE_22 = "0123456789abcdefABCDEF";
2074 string xencode(float f)
2075 {
2076         float a, b, c, d;
2077         d = mod(f, 22); f = floor(f / 22);
2078         c = mod(f, 22); f = floor(f / 22);
2079         b = mod(f, 22); f = floor(f / 22);
2080         a = mod(f,  2); // f = floor(f /  2);
2081         return strcat(
2082                 "^",
2083                 substring(XENCODE_2,  a, 1),
2084                 substring(XENCODE_22, b, 1),
2085                 substring(XENCODE_22, c, 1),
2086                 substring(XENCODE_22, d, 1)
2087         );
2088 }
2089 float xdecode(string s)
2090 {
2091         float a, b, c, d;
2092         if(substring(s, 0, 1) != "^")
2093                 return -1;
2094         if(strlen(s) < 5)
2095                 return -1;
2096         a = strstrofs(XENCODE_2,  substring(s, 1, 1), 0);
2097         b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2098         c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2099         d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2100         if(a < 0 || b < 0 || c < 0 || d < 0)
2101                 return -1;
2102         return ((a * 22 + b) * 22 + c) * 22 + d;
2103 }
2104
2105 float lowestbit(float f)
2106 {
2107         f &~= f * 2;
2108         f &~= f * 4;
2109         f &~= f * 16;
2110         f &~= f * 256;
2111         f &~= f * 65536;
2112         return f;
2113 }
2114
2115 /*
2116 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2117 {
2118         if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2119                 return input;
2120         else
2121                 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2122 }*/
2123
2124 // escape the string to make it safe for consoles
2125 string MakeConsoleSafe(string input)
2126 {
2127         input = strreplace("\n", "", input);
2128         input = strreplace("\\", "\\\\", input);
2129         input = strreplace("$", "$$", input);
2130         input = strreplace("\"", "\\\"", input);
2131         return input;
2132 }
2133
2134 #ifndef MENUQC
2135 // get true/false value of a string with multiple different inputs
2136 float InterpretBoolean(string input)
2137 {
2138         switch(strtolower(input))
2139         {
2140                 case "yes":
2141                 case "true":
2142                 case "on":
2143                         return TRUE;
2144                 
2145                 case "no":
2146                 case "false":
2147                 case "off":
2148                         return FALSE;
2149                 
2150                 default: return stof(input);
2151         }
2152 }
2153 #endif
2154
2155 #ifdef CSQC
2156 entity ReadCSQCEntity()
2157 {
2158         float f;
2159         f = ReadShort();
2160         if(f == 0)
2161                 return world;
2162         return findfloat(world, entnum, f);
2163 }
2164 #endif
2165
2166 float shutdown_running;
2167 #ifdef SVQC
2168 void SV_Shutdown()
2169 #endif
2170 #ifdef CSQC
2171 void CSQC_Shutdown()
2172 #endif
2173 #ifdef MENUQC
2174 void m_shutdown()
2175 #endif
2176 {
2177         if(shutdown_running)
2178         {
2179                 print("Recursive shutdown detected! Only restoring cvars...\n");
2180         }
2181         else
2182         {
2183                 shutdown_running = 1;
2184                 Shutdown();
2185         }
2186         cvar_settemp_restore(); // this must be done LAST, but in any case
2187 }