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