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