]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
#include this
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
1 #if defined(CSQC)
2         #include "../dpdefs/csprogsdefs.qc"
3     #include "../client/Defs.qc"
4     #include "constants.qh"
5     #include "../warpzonelib/mathlib.qh"
6     #include "util.qh"
7     #include "mapinfo.qh"
8     #include "notifications.qh"
9     #include "deathtypes.qh"
10 #elif defined(MENUQC)
11 #elif defined(SVQC)
12         #include "../dpdefs/progsdefs.qc"
13     #include "../dpdefs/dpextensions.qc"
14     #include "../server/sys-post.qh"
15     #include "../warpzonelib/mathlib.qh"
16     #include "constants.qh"
17     #include "util.qh"
18     #include "../server/autocvars.qh"
19     #include "../server/defs.qh"
20     #include "notifications.qh"
21     #include "deathtypes.qh"
22     #include "mapinfo.qh"
23 #endif
24
25 string wordwrap_buffer;
26
27 void wordwrap_buffer_put(string s)
28 {
29         wordwrap_buffer = strcat(wordwrap_buffer, s);
30 }
31
32 string wordwrap(string s, float l)
33 {
34         string r;
35         wordwrap_buffer = "";
36         wordwrap_cb(s, l, wordwrap_buffer_put);
37         r = wordwrap_buffer;
38         wordwrap_buffer = "";
39         return r;
40 }
41
42 #ifndef MENUQC
43 #ifndef CSQC
44 void wordwrap_buffer_sprint(string s)
45 {
46         wordwrap_buffer = strcat(wordwrap_buffer, s);
47         if(s == "\n")
48         {
49                 sprint(self, wordwrap_buffer);
50                 wordwrap_buffer = "";
51         }
52 }
53
54 void wordwrap_sprint(string s, float l)
55 {
56         wordwrap_buffer = "";
57         wordwrap_cb(s, l, wordwrap_buffer_sprint);
58         if(wordwrap_buffer != "")
59                 sprint(self, strcat(wordwrap_buffer, "\n"));
60         wordwrap_buffer = "";
61         return;
62 }
63 #endif
64 #endif
65
66 #ifndef SVQC
67 string draw_UseSkinFor(string pic)
68 {
69         if(substring(pic, 0, 1) == "/")
70                 return substring(pic, 1, strlen(pic)-1);
71         else
72                 return strcat(draw_currentSkin, "/", pic);
73 }
74 #endif
75
76 string unescape(string in)
77 {
78         float i, len;
79         string str, s;
80
81         // but it doesn't seem to be necessary in my tests at least
82         in = strzone(in);
83
84         len = strlen(in);
85         str = "";
86         for(i = 0; i < len; ++i)
87         {
88                 s = substring(in, i, 1);
89                 if(s == "\\")
90                 {
91                         s = substring(in, i+1, 1);
92                         if(s == "n")
93                                 str = strcat(str, "\n");
94                         else if(s == "\\")
95                                 str = strcat(str, "\\");
96                         else
97                                 str = strcat(str, substring(in, i, 2));
98                         ++i;
99                 } else
100                         str = strcat(str, s);
101         }
102
103         strunzone(in);
104         return str;
105 }
106
107 void wordwrap_cb(string s, float l, void(string) callback)
108 {
109         string c;
110         float lleft, i, j, wlen;
111
112         s = strzone(s);
113         lleft = l;
114         for (i = 0;i < strlen(s);++i)
115         {
116                 if (substring(s, i, 2) == "\\n")
117                 {
118                         callback("\n");
119                         lleft = l;
120                         ++i;
121                 }
122                 else if (substring(s, i, 1) == "\n")
123                 {
124                         callback("\n");
125                         lleft = l;
126                 }
127                 else if (substring(s, i, 1) == " ")
128                 {
129                         if (lleft > 0)
130                         {
131                                 callback(" ");
132                                 lleft = lleft - 1;
133                         }
134                 }
135                 else
136                 {
137                         for (j = i+1;j < strlen(s);++j)
138                                 //    ^^ this skips over the first character of a word, which
139                                 //       is ALWAYS part of the word
140                                 //       this is safe since if i+1 == strlen(s), i will become
141                                 //       strlen(s)-1 at the end of this block and the function
142                                 //       will terminate. A space can't be the first character we
143                                 //       read here, and neither can a \n be the start, since these
144                                 //       two cases have been handled above.
145                         {
146                                 c = substring(s, j, 1);
147                                 if (c == " ")
148                                         break;
149                                 if (c == "\\")
150                                         break;
151                                 if (c == "\n")
152                                         break;
153                                 // we need to keep this tempstring alive even if substring is
154                                 // called repeatedly, so call strcat even though we're not
155                                 // doing anything
156                                 callback("");
157                         }
158                         wlen = j - i;
159                         if (lleft < wlen)
160                         {
161                                 callback("\n");
162                                 lleft = l;
163                         }
164                         callback(substring(s, i, wlen));
165                         lleft = lleft - wlen;
166                         i = j - 1;
167                 }
168         }
169         strunzone(s);
170 }
171
172 float dist_point_line(vector p, vector l0, vector ldir)
173 {
174         ldir = normalize(ldir);
175
176         // remove the component in line direction
177         p = p - (p * ldir) * ldir;
178
179         // vlen of the remaining vector
180         return vlen(p);
181 }
182
183 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
184 {
185         entity e;
186         e = start;
187         funcPre(pass, e);
188         while(e.downleft)
189         {
190                 e = e.downleft;
191                 funcPre(pass, e);
192         }
193         funcPost(pass, e);
194         while(e != start)
195         {
196                 if(e.right)
197                 {
198                         e = e.right;
199                         funcPre(pass, e);
200                         while(e.downleft)
201                         {
202                                 e = e.downleft;
203                                 funcPre(pass, e);
204                         }
205                 }
206                 else
207                         e = e.up;
208                 funcPost(pass, e);
209         }
210 }
211
212 float median(float a, float b, float c)
213 {
214         if(a < c)
215                 return bound(a, b, c);
216         return bound(c, b, a);
217 }
218
219 // converts a number to a string with the indicated number of decimals
220 // works for up to 10 decimals!
221 string ftos_decimals(float number, float decimals)
222 {
223         // inhibit stupid negative zero
224         if(number == 0)
225                 number = 0;
226         // we have sprintf...
227         return sprintf("%.*f", decimals, number);
228 }
229
230 vector colormapPaletteColor(float c, float isPants)
231 {
232         switch(c)
233         {
234                 case  0: return '1.000000 1.000000 1.000000';
235                 case  1: return '1.000000 0.333333 0.000000';
236                 case  2: return '0.000000 1.000000 0.501961';
237                 case  3: return '0.000000 1.000000 0.000000';
238                 case  4: return '1.000000 0.000000 0.000000';
239                 case  5: return '0.000000 0.666667 1.000000';
240                 case  6: return '0.000000 1.000000 1.000000';
241                 case  7: return '0.501961 1.000000 0.000000';
242                 case  8: return '0.501961 0.000000 1.000000';
243                 case  9: return '1.000000 0.000000 1.000000';
244                 case 10: return '1.000000 0.000000 0.501961';
245                 case 11: return '0.000000 0.000000 1.000000';
246                 case 12: return '1.000000 1.000000 0.000000';
247                 case 13: return '0.000000 0.333333 1.000000';
248                 case 14: return '1.000000 0.666667 0.000000';
249                 case 15:
250                         if(isPants)
251                                 return
252                                           '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
253                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
254                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
255                         else
256                                 return
257                                           '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
258                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
259                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
260                 default: return '0.000 0.000 0.000';
261         }
262 }
263
264 // unzone the string, and return it as tempstring. Safe to be called on string_null
265 string fstrunzone(string s)
266 {
267         string sc;
268         if (!s)
269                 return s;
270         sc = strcat(s, "");
271         strunzone(s);
272         return sc;
273 }
274
275 bool fexists(string f)
276 {
277     int fh = fopen(f, FILE_READ);
278     if (fh < 0)
279         return false;
280     fclose(fh);
281     return true;
282 }
283
284 // Databases (hash tables)
285 const float DB_BUCKETS = 8192;
286 void db_save(float db, string pFilename)
287 {
288         float fh, i, n;
289         fh = fopen(pFilename, FILE_WRITE);
290         if(fh < 0)
291         {
292                 print(strcat("^1Can't write DB to ", pFilename));
293                 return;
294         }
295         n = buf_getsize(db);
296         fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
297         for(i = 0; i < n; ++i)
298                 fputs(fh, strcat(bufstr_get(db, i), "\n"));
299         fclose(fh);
300 }
301
302 int db_create()
303 {
304         return buf_create();
305 }
306
307 float db_load(string pFilename)
308 {
309         float db, fh, i, j, n;
310         string l;
311         db = buf_create();
312         if(db < 0)
313                 return -1;
314         fh = fopen(pFilename, FILE_READ);
315         if(fh < 0)
316                 return db;
317         l = fgets(fh);
318         if(stof(l) == DB_BUCKETS)
319         {
320                 i = 0;
321                 while((l = fgets(fh)))
322                 {
323                         if(l != "")
324                                 bufstr_set(db, i, l);
325                         ++i;
326                 }
327         }
328         else
329         {
330                 // different count of buckets, or a dump?
331                 // need to reorganize the database then (SLOW)
332                 //
333                 // note: we also parse the first line (l) in case the DB file is
334                 // missing the bucket count
335                 do
336                 {
337                         n = tokenizebyseparator(l, "\\");
338                         for(j = 2; j < n; j += 2)
339                                 db_put(db, argv(j-1), uri_unescape(argv(j)));
340                 }
341                 while((l = fgets(fh)));
342         }
343         fclose(fh);
344         return db;
345 }
346
347 void db_dump(float db, string pFilename)
348 {
349         float fh, i, j, n, m;
350         fh = fopen(pFilename, FILE_WRITE);
351         if(fh < 0)
352                 error(strcat("Can't dump DB to ", pFilename));
353         n = buf_getsize(db);
354         fputs(fh, "0\n");
355         for(i = 0; i < n; ++i)
356         {
357                 m = tokenizebyseparator(bufstr_get(db, i), "\\");
358                 for(j = 2; j < m; j += 2)
359                         fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
360         }
361         fclose(fh);
362 }
363
364 void db_close(float db)
365 {
366         buf_del(db);
367 }
368
369 string db_get(float db, string pKey)
370 {
371         float h;
372         h = crc16(false, pKey) % DB_BUCKETS;
373         return uri_unescape(infoget(bufstr_get(db, h), pKey));
374 }
375
376 void db_put(float db, string pKey, string pValue)
377 {
378         float h;
379         h = crc16(false, pKey) % DB_BUCKETS;
380         bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
381 }
382
383 void db_test()
384 {
385         float db, i;
386         print("LOAD...\n");
387         db = db_load("foo.db");
388         print("LOADED. FILL...\n");
389         for(i = 0; i < DB_BUCKETS; ++i)
390                 db_put(db, ftos(random()), "X");
391         print("FILLED. SAVE...\n");
392         db_save(db, "foo.db");
393         print("SAVED. CLOSE...\n");
394         db_close(db);
395         print("CLOSED.\n");
396 }
397
398 // Multiline text file buffers
399 float buf_load(string pFilename)
400 {
401         float buf, fh, i;
402         string l;
403         buf = buf_create();
404         if(buf < 0)
405                 return -1;
406         fh = fopen(pFilename, FILE_READ);
407         if(fh < 0)
408         {
409                 buf_del(buf);
410                 return -1;
411         }
412         i = 0;
413         while((l = fgets(fh)))
414         {
415                 bufstr_set(buf, i, l);
416                 ++i;
417         }
418         fclose(fh);
419         return buf;
420 }
421
422 void buf_save(float buf, string pFilename)
423 {
424         float fh, i, n;
425         fh = fopen(pFilename, FILE_WRITE);
426         if(fh < 0)
427                 error(strcat("Can't write buf to ", pFilename));
428         n = buf_getsize(buf);
429         for(i = 0; i < n; ++i)
430                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
431         fclose(fh);
432 }
433
434 string format_time(float seconds)
435 {
436         float days, hours, minutes;
437         seconds = floor(seconds + 0.5);
438         days = floor(seconds / 864000);
439         seconds -= days * 864000;
440         hours = floor(seconds / 36000);
441         seconds -= hours * 36000;
442         minutes = floor(seconds / 600);
443         seconds -= minutes * 600;
444         if (days > 0)
445                 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
446         else
447                 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
448 }
449
450 string mmsss(float tenths)
451 {
452         float minutes;
453         string s;
454         tenths = floor(tenths + 0.5);
455         minutes = floor(tenths / 600);
456         tenths -= minutes * 600;
457         s = ftos(1000 + tenths);
458         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
459 }
460
461 string mmssss(float hundredths)
462 {
463         float minutes;
464         string s;
465         hundredths = floor(hundredths + 0.5);
466         minutes = floor(hundredths / 6000);
467         hundredths -= minutes * 6000;
468         s = ftos(10000 + hundredths);
469         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
470 }
471
472 string ScoreString(int pFlags, float pValue)
473 {
474         string valstr;
475         float l;
476
477         pValue = floor(pValue + 0.5); // round
478
479         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
480                 valstr = "";
481         else if(pFlags & SFL_RANK)
482         {
483                 valstr = ftos(pValue);
484                 l = strlen(valstr);
485                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
486                         valstr = strcat(valstr, "th");
487                 else if(substring(valstr, l - 1, 1) == "1")
488                         valstr = strcat(valstr, "st");
489                 else if(substring(valstr, l - 1, 1) == "2")
490                         valstr = strcat(valstr, "nd");
491                 else if(substring(valstr, l - 1, 1) == "3")
492                         valstr = strcat(valstr, "rd");
493                 else
494                         valstr = strcat(valstr, "th");
495         }
496         else if(pFlags & SFL_TIME)
497                 valstr = TIME_ENCODED_TOSTRING(pValue);
498         else
499                 valstr = ftos(pValue);
500
501         return valstr;
502 }
503
504 float dotproduct(vector a, vector b)
505 {
506         return a.x * b.x + a.y * b.y + a.z * b.z;
507 }
508
509 vector cross(vector a, vector b)
510 {
511         return
512                 '1 0 0' * (a.y * b.z - a.z * b.y)
513         +       '0 1 0' * (a.z * b.x - a.x * b.z)
514         +       '0 0 1' * (a.x * b.y - a.y * b.x);
515 }
516
517 // compressed vector format:
518 // like MD3, just even shorter
519 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
520 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
521 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
522 //     length = 2^(length_encoded/8) / 8
523 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
524 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
525 // the special value 0 indicates the zero vector
526
527 float lengthLogTable[128];
528
529 float invertLengthLog(float x)
530 {
531         int l, r, m;
532
533         if(x >= lengthLogTable[127])
534                 return 127;
535         if(x <= lengthLogTable[0])
536                 return 0;
537
538         l = 0;
539         r = 127;
540
541         while(r - l > 1)
542         {
543                 m = floor((l + r) / 2);
544                 if(lengthLogTable[m] < x)
545                         l = m;
546                 else
547                         r = m;
548         }
549
550         // now: r is >=, l is <
551         float lerr = (x - lengthLogTable[l]);
552         float rerr = (lengthLogTable[r] - x);
553         if(lerr < rerr)
554                 return l;
555         return r;
556 }
557
558 vector decompressShortVector(int data)
559 {
560         vector out;
561         if(data == 0)
562                 return '0 0 0';
563         float p = (data & 0xF000) / 0x1000;
564         float y = (data & 0x0F80) / 0x80;
565         int len = (data & 0x007F);
566
567         //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
568
569         if(p == 0)
570         {
571                 out_x = 0;
572                 out_y = 0;
573                 if(y == 31)
574                         out_z = -1;
575                 else
576                         out_z = +1;
577         }
578         else
579         {
580                 y   = .19634954084936207740 * y;
581                 p = .19634954084936207740 * p - 1.57079632679489661922;
582                 out_x = cos(y) *  cos(p);
583                 out_y = sin(y) *  cos(p);
584                 out_z =          -sin(p);
585         }
586
587         //print("decompressed: ", vtos(out), "\n");
588
589         return out * lengthLogTable[len];
590 }
591
592 float compressShortVector(vector vec)
593 {
594         vector ang;
595         float p, y, len;
596         if(vlen(vec) == 0)
597                 return 0;
598         //print("compress: ", vtos(vec), "\n");
599         ang = vectoangles(vec);
600         ang_x = -ang.x;
601         if(ang.x < -90)
602                 ang.x += 360;
603         if(ang.x < -90 && ang.x > +90)
604                 error("BOGUS vectoangles");
605         //print("angles: ", vtos(ang), "\n");
606
607         p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
608         if(p == 0)
609         {
610                 if(vec.z < 0)
611                         y = 31;
612                 else
613                         y = 30;
614         }
615         else
616                 y = floor(0.5 + ang.y * 32 / 360)          & 31; // 0..360 to 0..32
617         len = invertLengthLog(vlen(vec));
618
619         //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
620
621         return (p * 0x1000) + (y * 0x80) + len;
622 }
623
624 void compressShortVector_init()
625 {
626         float l = 1;
627         float f = pow(2, 1/8);
628         int i;
629         for(i = 0; i < 128; ++i)
630         {
631                 lengthLogTable[i] = l;
632                 l *= f;
633         }
634
635         if(cvar("developer"))
636         {
637                 print("Verifying vector compression table...\n");
638                 for(i = 0x0F00; i < 0xFFFF; ++i)
639                         if(i != compressShortVector(decompressShortVector(i)))
640                         {
641                                 print("BROKEN vector compression: ", ftos(i));
642                                 print(" -> ", vtos(decompressShortVector(i)));
643                                 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
644                                 print("\n");
645                                 error("b0rk");
646                         }
647                 print("Done.\n");
648         }
649 }
650
651 #ifndef MENUQC
652 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
653 {
654         traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
655         traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
656         traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
657         traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
658         traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
659         traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
660         traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
661         traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
662         traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
663         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
664         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
665         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
666         return 1;
667 }
668 #endif
669
670 string fixPriorityList(string order, float from, float to, float subtract, float complete)
671 {
672         string neworder;
673         float i, n, w;
674
675         n = tokenize_console(order);
676         neworder = "";
677         for(i = 0; i < n; ++i)
678         {
679                 w = stof(argv(i));
680                 if(w == floor(w))
681                 {
682                         if(w >= from && w <= to)
683                                 neworder = strcat(neworder, ftos(w), " ");
684                         else
685                         {
686                                 w -= subtract;
687                                 if(w >= from && w <= to)
688                                         neworder = strcat(neworder, ftos(w), " ");
689                         }
690                 }
691         }
692
693         if(complete)
694         {
695                 n = tokenize_console(neworder);
696                 for(w = to; w >= from; --w)
697                 {
698                         for(i = 0; i < n; ++i)
699                                 if(stof(argv(i)) == w)
700                                         break;
701                         if(i == n) // not found
702                                 neworder = strcat(neworder, ftos(w), " ");
703                 }
704         }
705
706         return substring(neworder, 0, strlen(neworder) - 1);
707 }
708
709 string mapPriorityList(string order, string(string) mapfunc)
710 {
711         string neworder;
712         float i, n;
713
714         n = tokenize_console(order);
715         neworder = "";
716         for(i = 0; i < n; ++i)
717                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
718
719         return substring(neworder, 0, strlen(neworder) - 1);
720 }
721
722 string swapInPriorityList(string order, float i, float j)
723 {
724         string s;
725         float w, n;
726
727         n = tokenize_console(order);
728
729         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
730         {
731                 s = "";
732                 for(w = 0; w < n; ++w)
733                 {
734                         if(w == i)
735                                 s = strcat(s, argv(j), " ");
736                         else if(w == j)
737                                 s = strcat(s, argv(i), " ");
738                         else
739                                 s = strcat(s, argv(w), " ");
740                 }
741                 return substring(s, 0, strlen(s) - 1);
742         }
743
744         return order;
745 }
746
747 float cvar_value_issafe(string s)
748 {
749         if(strstrofs(s, "\"", 0) >= 0)
750                 return 0;
751         if(strstrofs(s, "\\", 0) >= 0)
752                 return 0;
753         if(strstrofs(s, ";", 0) >= 0)
754                 return 0;
755         if(strstrofs(s, "$", 0) >= 0)
756                 return 0;
757         if(strstrofs(s, "\r", 0) >= 0)
758                 return 0;
759         if(strstrofs(s, "\n", 0) >= 0)
760                 return 0;
761         return 1;
762 }
763
764 #ifndef MENUQC
765 void get_mi_min_max(float mode)
766 {
767         vector mi, ma;
768
769         if(mi_shortname)
770                 strunzone(mi_shortname);
771         mi_shortname = mapname;
772         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
773                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
774         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
775                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
776         mi_shortname = strzone(mi_shortname);
777
778 #ifdef CSQC
779         mi = world.mins;
780         ma = world.maxs;
781 #else
782         mi = world.absmin;
783         ma = world.absmax;
784 #endif
785
786         mi_min = mi;
787         mi_max = ma;
788         MapInfo_Get_ByName(mi_shortname, 0, 0);
789         if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
790         {
791                 mi_min = MapInfo_Map_mins;
792                 mi_max = MapInfo_Map_maxs;
793         }
794         else
795         {
796                 // not specified
797                 if(mode)
798                 {
799                         // be clever
800                         tracebox('1 0 0' * mi.x,
801                                          '0 1 0' * mi.y + '0 0 1' * mi.z,
802                                          '0 1 0' * ma.y + '0 0 1' * ma.z,
803                                          '1 0 0' * ma.x,
804                                          MOVE_WORLDONLY,
805                                          world);
806                         if(!trace_startsolid)
807                                 mi_min_x = trace_endpos.x;
808
809                         tracebox('0 1 0' * mi.y,
810                                          '1 0 0' * mi.x + '0 0 1' * mi.z,
811                                          '1 0 0' * ma.x + '0 0 1' * ma.z,
812                                          '0 1 0' * ma.y,
813                                          MOVE_WORLDONLY,
814                                          world);
815                         if(!trace_startsolid)
816                                 mi_min_y = trace_endpos.y;
817
818                         tracebox('0 0 1' * mi.z,
819                                          '1 0 0' * mi.x + '0 1 0' * mi.y,
820                                          '1 0 0' * ma.x + '0 1 0' * ma.y,
821                                          '0 0 1' * ma.z,
822                                          MOVE_WORLDONLY,
823                                          world);
824                         if(!trace_startsolid)
825                                 mi_min_z = trace_endpos.z;
826
827                         tracebox('1 0 0' * ma.x,
828                                          '0 1 0' * mi.y + '0 0 1' * mi.z,
829                                          '0 1 0' * ma.y + '0 0 1' * ma.z,
830                                          '1 0 0' * mi.x,
831                                          MOVE_WORLDONLY,
832                                          world);
833                         if(!trace_startsolid)
834                                 mi_max_x = trace_endpos.x;
835
836                         tracebox('0 1 0' * ma.y,
837                                          '1 0 0' * mi.x + '0 0 1' * mi.z,
838                                          '1 0 0' * ma.x + '0 0 1' * ma.z,
839                                          '0 1 0' * mi.y,
840                                          MOVE_WORLDONLY,
841                                          world);
842                         if(!trace_startsolid)
843                                 mi_max_y = trace_endpos.y;
844
845                         tracebox('0 0 1' * ma.z,
846                                          '1 0 0' * mi.x + '0 1 0' * mi.y,
847                                          '1 0 0' * ma.x + '0 1 0' * ma.y,
848                                          '0 0 1' * mi.z,
849                                          MOVE_WORLDONLY,
850                                          world);
851                         if(!trace_startsolid)
852                                 mi_max_z = trace_endpos.z;
853                 }
854         }
855 }
856
857 void get_mi_min_max_texcoords(float mode)
858 {
859         vector extend;
860
861         get_mi_min_max(mode);
862
863         mi_picmin = mi_min;
864         mi_picmax = mi_max;
865
866         // extend mi_picmax to get a square aspect ratio
867         // center the map in that area
868         extend = mi_picmax - mi_picmin;
869         if(extend.y > extend.x)
870         {
871                 mi_picmin.x -= (extend.y - extend.x) * 0.5;
872                 mi_picmax.x += (extend.y - extend.x) * 0.5;
873         }
874         else
875         {
876                 mi_picmin.y -= (extend.x - extend.y) * 0.5;
877                 mi_picmax.y += (extend.x - extend.y) * 0.5;
878         }
879
880         // add another some percent
881         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
882         mi_picmin -= extend;
883         mi_picmax += extend;
884
885         // calculate the texcoords
886         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
887         // first the two corners of the origin
888         mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
889         mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
890         mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
891         mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
892         // then the other corners
893         mi_pictexcoord1_x = mi_pictexcoord0_x;
894         mi_pictexcoord1_y = mi_pictexcoord2_y;
895         mi_pictexcoord3_x = mi_pictexcoord2_x;
896         mi_pictexcoord3_y = mi_pictexcoord0_y;
897 }
898 #endif
899
900 float cvar_settemp(string tmp_cvar, string tmp_value)
901 {
902         float created_saved_value;
903         entity e;
904
905         created_saved_value = 0;
906
907         if (!(tmp_cvar || tmp_value))
908         {
909                 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
910                 return 0;
911         }
912
913         if(!cvar_type(tmp_cvar))
914         {
915                 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
916                 return 0;
917         }
918
919         for(e = world; (e = find(e, classname, "saved_cvar_value")); )
920                 if(e.netname == tmp_cvar)
921                         created_saved_value = -1; // skip creation
922
923         if(created_saved_value != -1)
924         {
925                 // creating a new entity to keep track of this cvar
926                 e = spawn();
927                 e.classname = "saved_cvar_value";
928                 e.netname = strzone(tmp_cvar);
929                 e.message = strzone(cvar_string(tmp_cvar));
930                 created_saved_value = 1;
931         }
932
933         // update the cvar to the value given
934         cvar_set(tmp_cvar, tmp_value);
935
936         return created_saved_value;
937 }
938
939 float cvar_settemp_restore()
940 {
941         float i = 0;
942         entity e = world;
943         while((e = find(e, classname, "saved_cvar_value")))
944         {
945                 if(cvar_type(e.netname))
946                 {
947                         cvar_set(e.netname, e.message);
948                         remove(e);
949                         ++i;
950                 }
951                 else
952                         printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
953         }
954
955         return i;
956 }
957
958 float almost_equals(float a, float b)
959 {
960         float eps;
961         eps = (max(a, -a) + max(b, -b)) * 0.001;
962         if(a - b < eps && b - a < eps)
963                 return true;
964         return false;
965 }
966
967 float almost_in_bounds(float a, float b, float c)
968 {
969         float eps;
970         eps = (max(a, -a) + max(c, -c)) * 0.001;
971         if(a > c)
972                 eps = -eps;
973         return b == median(a - eps, b, c + eps);
974 }
975
976 float power2of(float e)
977 {
978         return pow(2, e);
979 }
980 float log2of(float x)
981 {
982         // NOTE: generated code
983         if(x > 2048)
984                 if(x > 131072)
985                         if(x > 1048576)
986                                 if(x > 4194304)
987                                         return 23;
988                                 else
989                                         if(x > 2097152)
990                                                 return 22;
991                                         else
992                                                 return 21;
993                         else
994                                 if(x > 524288)
995                                         return 20;
996                                 else
997                                         if(x > 262144)
998                                                 return 19;
999                                         else
1000                                                 return 18;
1001                 else
1002                         if(x > 16384)
1003                                 if(x > 65536)
1004                                         return 17;
1005                                 else
1006                                         if(x > 32768)
1007                                                 return 16;
1008                                         else
1009                                                 return 15;
1010                         else
1011                                 if(x > 8192)
1012                                         return 14;
1013                                 else
1014                                         if(x > 4096)
1015                                                 return 13;
1016                                         else
1017                                                 return 12;
1018         else
1019                 if(x > 32)
1020                         if(x > 256)
1021                                 if(x > 1024)
1022                                         return 11;
1023                                 else
1024                                         if(x > 512)
1025                                                 return 10;
1026                                         else
1027                                                 return 9;
1028                         else
1029                                 if(x > 128)
1030                                         return 8;
1031                                 else
1032                                         if(x > 64)
1033                                                 return 7;
1034                                         else
1035                                                 return 6;
1036                 else
1037                         if(x > 4)
1038                                 if(x > 16)
1039                                         return 5;
1040                                 else
1041                                         if(x > 8)
1042                                                 return 4;
1043                                         else
1044                                                 return 3;
1045                         else
1046                                 if(x > 2)
1047                                         return 2;
1048                                 else
1049                                         if(x > 1)
1050                                                 return 1;
1051                                         else
1052                                                 return 0;
1053 }
1054
1055 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1056 {
1057         if(mi == ma)
1058                 return 0;
1059         else if(ma == rgb.x)
1060         {
1061                 if(rgb.y >= rgb.z)
1062                         return (rgb.y - rgb.z) / (ma - mi);
1063                 else
1064                         return (rgb.y - rgb.z) / (ma - mi) + 6;
1065         }
1066         else if(ma == rgb.y)
1067                 return (rgb.z - rgb.x) / (ma - mi) + 2;
1068         else // if(ma == rgb_z)
1069                 return (rgb.x - rgb.y) / (ma - mi) + 4;
1070 }
1071
1072 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1073 {
1074         vector rgb;
1075
1076         hue -= 6 * floor(hue / 6);
1077
1078         //else if(ma == rgb_x)
1079         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1080         if(hue <= 1)
1081         {
1082                 rgb_x = ma;
1083                 rgb_y = hue * (ma - mi) + mi;
1084                 rgb_z = mi;
1085         }
1086         //else if(ma == rgb_y)
1087         //      hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1088         else if(hue <= 2)
1089         {
1090                 rgb_x = (2 - hue) * (ma - mi) + mi;
1091                 rgb_y = ma;
1092                 rgb_z = mi;
1093         }
1094         else if(hue <= 3)
1095         {
1096                 rgb_x = mi;
1097                 rgb_y = ma;
1098                 rgb_z = (hue - 2) * (ma - mi) + mi;
1099         }
1100         //else // if(ma == rgb_z)
1101         //      hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1102         else if(hue <= 4)
1103         {
1104                 rgb_x = mi;
1105                 rgb_y = (4 - hue) * (ma - mi) + mi;
1106                 rgb_z = ma;
1107         }
1108         else if(hue <= 5)
1109         {
1110                 rgb_x = (hue - 4) * (ma - mi) + mi;
1111                 rgb_y = mi;
1112                 rgb_z = ma;
1113         }
1114         //else if(ma == rgb_x)
1115         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1116         else // if(hue <= 6)
1117         {
1118                 rgb_x = ma;
1119                 rgb_y = mi;
1120                 rgb_z = (6 - hue) * (ma - mi) + mi;
1121         }
1122
1123         return rgb;
1124 }
1125
1126 vector rgb_to_hsv(vector rgb)
1127 {
1128         float mi, ma;
1129         vector hsv;
1130
1131         mi = min(rgb.x, rgb.y, rgb.z);
1132         ma = max(rgb.x, rgb.y, rgb.z);
1133
1134         hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1135         hsv_z = ma;
1136
1137         if(ma == 0)
1138                 hsv_y = 0;
1139         else
1140                 hsv_y = 1 - mi/ma;
1141
1142         return hsv;
1143 }
1144
1145 vector hsv_to_rgb(vector hsv)
1146 {
1147         return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1148 }
1149
1150 vector rgb_to_hsl(vector rgb)
1151 {
1152         float mi, ma;
1153         vector hsl;
1154
1155         mi = min(rgb.x, rgb.y, rgb.z);
1156         ma = max(rgb.x, rgb.y, rgb.z);
1157
1158         hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1159
1160         hsl_z = 0.5 * (mi + ma);
1161         if(mi == ma)
1162                 hsl_y = 0;
1163         else if(hsl.z <= 0.5)
1164                 hsl_y = (ma - mi) / (2*hsl.z);
1165         else // if(hsl_z > 0.5)
1166                 hsl_y = (ma - mi) / (2 - 2*hsl.z);
1167
1168         return hsl;
1169 }
1170
1171 vector hsl_to_rgb(vector hsl)
1172 {
1173         float mi, ma, maminusmi;
1174
1175         if(hsl.z <= 0.5)
1176                 maminusmi = hsl.y * 2 * hsl.z;
1177         else
1178                 maminusmi = hsl.y * (2 - 2 * hsl.z);
1179
1180         // hsl_z     = 0.5 * mi + 0.5 * ma
1181         // maminusmi =     - mi +       ma
1182         mi = hsl.z - 0.5 * maminusmi;
1183         ma = hsl.z + 0.5 * maminusmi;
1184
1185         return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1186 }
1187
1188 string rgb_to_hexcolor(vector rgb)
1189 {
1190         return
1191                 strcat(
1192                         "^x",
1193                         DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1194                         DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1195                         DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1196                 );
1197 }
1198
1199 // requires that m2>m1 in all coordinates, and that m4>m3
1200 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;}
1201
1202 // requires the same, but is a stronger condition
1203 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;}
1204
1205 #ifndef MENUQC
1206 #endif
1207
1208 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1209 {
1210         // STOP.
1211         // The following function is SLOW.
1212         // For your safety and for the protection of those around you...
1213         // DO NOT CALL THIS AT HOME.
1214         // No really, don't.
1215         if(w(theText, theSize) <= maxWidth)
1216                 return strlen(theText); // yeah!
1217
1218         // binary search for right place to cut string
1219         float ch;
1220         float left, right, middle; // this always works
1221         left = 0;
1222         right = strlen(theText); // this always fails
1223         do
1224         {
1225                 middle = floor((left + right) / 2);
1226                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1227                         left = middle;
1228                 else
1229                         right = middle;
1230         }
1231         while(left < right - 1);
1232
1233         if(w("^7", theSize) == 0) // detect color codes support in the width function
1234         {
1235                 // NOTE: when color codes are involved, this binary search is,
1236                 // mathematically, BROKEN. However, it is obviously guaranteed to
1237                 // terminate, as the range still halves each time - but nevertheless, it is
1238                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1239                 // range, and "right" is outside).
1240
1241                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1242                 // and decrease left on the basis of the chars detected of the truncated tag
1243                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1244                 // (sometimes too much but with a correct result)
1245                 // it fixes also ^[0-9]
1246                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1247                         left-=1;
1248
1249                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1250                         left-=2;
1251                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1252                         {
1253                                 ch = str2chr(theText, left-1);
1254                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1255                                         left-=3;
1256                         }
1257                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1258                         {
1259                                 ch = str2chr(theText, left-2);
1260                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1261                                 {
1262                                         ch = str2chr(theText, left-1);
1263                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1264                                                 left-=4;
1265                                 }
1266                         }
1267         }
1268
1269         return left;
1270 }
1271
1272 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1273 {
1274         // STOP.
1275         // The following function is SLOW.
1276         // For your safety and for the protection of those around you...
1277         // DO NOT CALL THIS AT HOME.
1278         // No really, don't.
1279         if(w(theText) <= maxWidth)
1280                 return strlen(theText); // yeah!
1281
1282         // binary search for right place to cut string
1283         float ch;
1284         float left, right, middle; // this always works
1285         left = 0;
1286         right = strlen(theText); // this always fails
1287         do
1288         {
1289                 middle = floor((left + right) / 2);
1290                 if(w(substring(theText, 0, middle)) <= maxWidth)
1291                         left = middle;
1292                 else
1293                         right = middle;
1294         }
1295         while(left < right - 1);
1296
1297         if(w("^7") == 0) // detect color codes support in the width function
1298         {
1299                 // NOTE: when color codes are involved, this binary search is,
1300                 // mathematically, BROKEN. However, it is obviously guaranteed to
1301                 // terminate, as the range still halves each time - but nevertheless, it is
1302                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1303                 // range, and "right" is outside).
1304
1305                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1306                 // and decrease left on the basis of the chars detected of the truncated tag
1307                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1308                 // (sometimes too much but with a correct result)
1309                 // it fixes also ^[0-9]
1310                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1311                         left-=1;
1312
1313                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1314                         left-=2;
1315                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1316                         {
1317                                 ch = str2chr(theText, left-1);
1318                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1319                                         left-=3;
1320                         }
1321                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1322                         {
1323                                 ch = str2chr(theText, left-2);
1324                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1325                                 {
1326                                         ch = str2chr(theText, left-1);
1327                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1328                                                 left-=4;
1329                                 }
1330                         }
1331         }
1332
1333         return left;
1334 }
1335
1336 string find_last_color_code(string s)
1337 {
1338         int start = strstrofs(s, "^", 0);
1339         if (start == -1) // no caret found
1340                 return "";
1341         int len = strlen(s)-1;
1342         int i;
1343         for(i = len; i >= start; --i)
1344         {
1345                 if(substring(s, i, 1) != "^")
1346                         continue;
1347
1348                 int carets = 1;
1349                 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1350                         ++carets;
1351
1352                 // check if carets aren't all escaped
1353                 if (carets & 1)
1354                 {
1355                         if(i+1 <= len)
1356                         if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1357                                 return substring(s, i, 2);
1358
1359                         if(i+4 <= len)
1360                         if(substring(s, i+1, 1) == "x")
1361                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1362                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1363                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1364                                 return substring(s, i, 5);
1365                 }
1366                 i -= carets; // this also skips one char before the carets
1367         }
1368
1369         return "";
1370 }
1371
1372 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1373 {
1374         float cantake;
1375         float take;
1376         string s;
1377
1378         s = getWrappedLine_remaining;
1379
1380         if(w <= 0)
1381         {
1382                 getWrappedLine_remaining = string_null;
1383                 return s; // the line has no size ANYWAY, nothing would be displayed.
1384         }
1385
1386         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1387         if(cantake > 0 && cantake < strlen(s))
1388         {
1389                 take = cantake - 1;
1390                 while(take > 0 && substring(s, take, 1) != " ")
1391                         --take;
1392                 if(take == 0)
1393                 {
1394                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1395                         if(getWrappedLine_remaining == "")
1396                                 getWrappedLine_remaining = string_null;
1397                         else if (tw("^7", theFontSize) == 0)
1398                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1399                         return substring(s, 0, cantake);
1400                 }
1401                 else
1402                 {
1403                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1404                         if(getWrappedLine_remaining == "")
1405                                 getWrappedLine_remaining = string_null;
1406                         else if (tw("^7", theFontSize) == 0)
1407                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1408                         return substring(s, 0, take);
1409                 }
1410         }
1411         else
1412         {
1413                 getWrappedLine_remaining = string_null;
1414                 return s;
1415         }
1416 }
1417
1418 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1419 {
1420         float cantake;
1421         float take;
1422         string s;
1423
1424         s = getWrappedLine_remaining;
1425
1426         if(w <= 0)
1427         {
1428                 getWrappedLine_remaining = string_null;
1429                 return s; // the line has no size ANYWAY, nothing would be displayed.
1430         }
1431
1432         cantake = textLengthUpToLength(s, w, tw);
1433         if(cantake > 0 && cantake < strlen(s))
1434         {
1435                 take = cantake - 1;
1436                 while(take > 0 && substring(s, take, 1) != " ")
1437                         --take;
1438                 if(take == 0)
1439                 {
1440                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1441                         if(getWrappedLine_remaining == "")
1442                                 getWrappedLine_remaining = string_null;
1443                         else if (tw("^7") == 0)
1444                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1445                         return substring(s, 0, cantake);
1446                 }
1447                 else
1448                 {
1449                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1450                         if(getWrappedLine_remaining == "")
1451                                 getWrappedLine_remaining = string_null;
1452                         else if (tw("^7") == 0)
1453                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1454                         return substring(s, 0, take);
1455                 }
1456         }
1457         else
1458         {
1459                 getWrappedLine_remaining = string_null;
1460                 return s;
1461         }
1462 }
1463
1464 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1465 {
1466         if(tw(theText, theFontSize) <= maxWidth)
1467                 return theText;
1468         else
1469                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1470 }
1471
1472 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1473 {
1474         if(tw(theText) <= maxWidth)
1475                 return theText;
1476         else
1477                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1478 }
1479
1480 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1481 {
1482         string subpattern, subpattern2, subpattern3, subpattern4;
1483         subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1484         if(tp)
1485                 subpattern2 = ",teams,";
1486         else
1487                 subpattern2 = ",noteams,";
1488         if(ts)
1489                 subpattern3 = ",teamspawns,";
1490         else
1491                 subpattern3 = ",noteamspawns,";
1492         if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1493                 subpattern4 = ",race,";
1494         else
1495                 subpattern4 = string_null;
1496
1497         if(substring(pattern, 0, 1) == "-")
1498         {
1499                 pattern = substring(pattern, 1, strlen(pattern) - 1);
1500                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1501                         return 0;
1502                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1503                         return 0;
1504                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1505                         return 0;
1506                 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1507                         return 0;
1508         }
1509         else
1510         {
1511                 if(substring(pattern, 0, 1) == "+")
1512                         pattern = substring(pattern, 1, strlen(pattern) - 1);
1513                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1514                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1515                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1516                 {
1517                         if (!subpattern4)
1518                                 return 0;
1519                         if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1520                                 return 0;
1521                 }
1522         }
1523         return 1;
1524 }
1525
1526 void shuffle(float n, swapfunc_t swap, entity pass)
1527 {
1528         float i, j;
1529         for(i = 1; i < n; ++i)
1530         {
1531                 // swap i-th item at a random position from 0 to i
1532                 // proof for even distribution:
1533                 //   n = 1: obvious
1534                 //   n -> n+1:
1535                 //     item n+1 gets at any position with chance 1/(n+1)
1536                 //     all others will get their 1/n chance reduced by factor n/(n+1)
1537                 //     to be on place n+1, their chance will be 1/(n+1)
1538                 //     1/n * n/(n+1) = 1/(n+1)
1539                 //     q.e.d.
1540                 j = floor(random() * (i + 1));
1541                 if(j != i)
1542                         swap(j, i, pass);
1543         }
1544 }
1545
1546 string substring_range(string s, float b, float e)
1547 {
1548         return substring(s, b, e - b);
1549 }
1550
1551 string swapwords(string str, float i, float j)
1552 {
1553         float n;
1554         string s1, s2, s3, s4, s5;
1555         float si, ei, sj, ej, s0, en;
1556         n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1557         si = argv_start_index(i);
1558         sj = argv_start_index(j);
1559         ei = argv_end_index(i);
1560         ej = argv_end_index(j);
1561         s0 = argv_start_index(0);
1562         en = argv_end_index(n-1);
1563         s1 = substring_range(str, s0, si);
1564         s2 = substring_range(str, si, ei);
1565         s3 = substring_range(str, ei, sj);
1566         s4 = substring_range(str, sj, ej);
1567         s5 = substring_range(str, ej, en);
1568         return strcat(s1, s4, s3, s2, s5);
1569 }
1570
1571 string _shufflewords_str;
1572 void _shufflewords_swapfunc(float i, float j, entity pass)
1573 {
1574         _shufflewords_str = swapwords(_shufflewords_str, i, j);
1575 }
1576 string shufflewords(string str)
1577 {
1578         float n;
1579         _shufflewords_str = str;
1580         n = tokenizebyseparator(str, " ");
1581         shuffle(n, _shufflewords_swapfunc, world);
1582         str = _shufflewords_str;
1583         _shufflewords_str = string_null;
1584         return str;
1585 }
1586
1587 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1588 {
1589         vector v;
1590         float D;
1591         v = '0 0 0';
1592         if(a == 0)
1593         {
1594                 if(b != 0)
1595                 {
1596                         v_x = v_y = -c / b;
1597                         v_z = 1;
1598                 }
1599                 else
1600                 {
1601                         if(c == 0)
1602                         {
1603                                 // actually, every number solves the equation!
1604                                 v_z = 1;
1605                         }
1606                 }
1607         }
1608         else
1609         {
1610                 D = b*b - 4*a*c;
1611                 if(D >= 0)
1612                 {
1613                         D = sqrt(D);
1614                         if(a > 0) // put the smaller solution first
1615                         {
1616                                 v_x = ((-b)-D) / (2*a);
1617                                 v_y = ((-b)+D) / (2*a);
1618                         }
1619                         else
1620                         {
1621                                 v_x = (-b+D) / (2*a);
1622                                 v_y = (-b-D) / (2*a);
1623                         }
1624                         v_z = 1;
1625                 }
1626                 else
1627                 {
1628                         // complex solutions!
1629                         D = sqrt(-D);
1630                         v_x = -b / (2*a);
1631                         if(a > 0)
1632                                 v_y =  D / (2*a);
1633                         else
1634                                 v_y = -D / (2*a);
1635                         v_z = 0;
1636                 }
1637         }
1638         return v;
1639 }
1640
1641 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1642 {
1643         vector ret;
1644
1645         // make origin and speed relative
1646         eorg -= myorg;
1647         if(newton_style)
1648                 evel -= myvel;
1649
1650         // now solve for ret, ret normalized:
1651         //   eorg + t * evel == t * ret * spd
1652         // or, rather, solve for t:
1653         //   |eorg + t * evel| == t * spd
1654         //   eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1655         //   t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1656         vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1657         // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1658         // q = (eorg * eorg) / (evel * evel - spd * spd)
1659         if(!solution.z) // no real solution
1660         {
1661                 // happens if D < 0
1662                 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1663                 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1664                 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1665                 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1666                 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1667                 // spd < |evel| * sin angle(evel, eorg)
1668                 return '0 0 0';
1669         }
1670         else if(solution.x > 0)
1671         {
1672                 // both solutions > 0: take the smaller one
1673                 // happens if p < 0 and q > 0
1674                 ret = normalize(eorg + solution.x * evel);
1675         }
1676         else if(solution.y > 0)
1677         {
1678                 // one solution > 0: take the larger one
1679                 // happens if q < 0 or q == 0 and p < 0
1680                 ret = normalize(eorg + solution.y * evel);
1681         }
1682         else
1683         {
1684                 // no solution > 0: reject
1685                 // happens if p > 0 and q >= 0
1686                 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1687                 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1688                 //
1689                 // |evel| >= spd
1690                 // eorg * evel > 0
1691                 //
1692                 // "Enemy is moving away from me at more than spd"
1693                 return '0 0 0';
1694         }
1695
1696         // NOTE: we always got a solution if spd > |evel|
1697
1698         if(newton_style == 2)
1699                 ret = normalize(ret * spd + myvel);
1700
1701         return ret;
1702 }
1703
1704 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1705 {
1706         if(!newton_style)
1707                 return spd * mydir;
1708
1709         if(newton_style == 2)
1710         {
1711                 // true Newtonian projectiles with automatic aim adjustment
1712                 //
1713                 // solve: |outspeed * mydir - myvel| = spd
1714                 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1715                 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1716                 // PLUS SIGN!
1717                 // not defined?
1718                 // then...
1719                 // myvel^2 - (mydir * myvel)^2 > spd^2
1720                 // velocity without mydir component > spd
1721                 // fire at smallest possible spd that works?
1722                 // |(mydir * myvel) * myvel - myvel| = spd
1723
1724                 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1725
1726                 float outspeed;
1727                 if(solution.z)
1728                         outspeed = solution.y; // the larger one
1729                 else
1730                 {
1731                         //outspeed = 0; // slowest possible shot
1732                         outspeed = solution.x; // the real part (that is, the average!)
1733                         //dprint("impossible shot, adjusting\n");
1734                 }
1735
1736                 outspeed = bound(spd * mi, outspeed, spd * ma);
1737                 return mydir * outspeed;
1738         }
1739
1740         // real Newtonian
1741         return myvel + spd * mydir;
1742 }
1743
1744 float compressShotOrigin(vector v)
1745 {
1746         float x, y, z;
1747         x = rint(v.x * 2);
1748         y = rint(v.y * 4) + 128;
1749         z = rint(v.z * 4) + 128;
1750         if(x > 255 || x < 0)
1751         {
1752                 print("shot origin ", vtos(v), " x out of bounds\n");
1753                 x = bound(0, x, 255);
1754         }
1755         if(y > 255 || y < 0)
1756         {
1757                 print("shot origin ", vtos(v), " y out of bounds\n");
1758                 y = bound(0, y, 255);
1759         }
1760         if(z > 255 || z < 0)
1761         {
1762                 print("shot origin ", vtos(v), " z out of bounds\n");
1763                 z = bound(0, z, 255);
1764         }
1765         return x * 0x10000 + y * 0x100 + z;
1766 }
1767 vector decompressShotOrigin(int f)
1768 {
1769         vector v;
1770         v_x = ((f & 0xFF0000) / 0x10000) / 2;
1771         v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1772         v_z = ((f & 0xFF) - 128) / 4;
1773         return v;
1774 }
1775
1776 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1777 {
1778         float start, end, root, child;
1779
1780         // heapify
1781         start = floor((n - 2) / 2);
1782         while(start >= 0)
1783         {
1784                 // siftdown(start, count-1);
1785                 root = start;
1786                 while(root * 2 + 1 <= n-1)
1787                 {
1788                         child = root * 2 + 1;
1789                         if(child < n-1)
1790                                 if(cmp(child, child+1, pass) < 0)
1791                                         ++child;
1792                         if(cmp(root, child, pass) < 0)
1793                         {
1794                                 swap(root, child, pass);
1795                                 root = child;
1796                         }
1797                         else
1798                                 break;
1799                 }
1800                 // end of siftdown
1801                 --start;
1802         }
1803
1804         // extract
1805         end = n - 1;
1806         while(end > 0)
1807         {
1808                 swap(0, end, pass);
1809                 --end;
1810                 // siftdown(0, end);
1811                 root = 0;
1812                 while(root * 2 + 1 <= end)
1813                 {
1814                         child = root * 2 + 1;
1815                         if(child < end && cmp(child, child+1, pass) < 0)
1816                                 ++child;
1817                         if(cmp(root, child, pass) < 0)
1818                         {
1819                                 swap(root, child, pass);
1820                                 root = child;
1821                         }
1822                         else
1823                                 break;
1824                 }
1825                 // end of siftdown
1826         }
1827 }
1828
1829 void RandomSelection_Init()
1830 {
1831         RandomSelection_totalweight = 0;
1832         RandomSelection_chosen_ent = world;
1833         RandomSelection_chosen_float = 0;
1834         RandomSelection_chosen_string = string_null;
1835         RandomSelection_best_priority = -1;
1836 }
1837 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1838 {
1839         if(priority > RandomSelection_best_priority)
1840         {
1841                 RandomSelection_best_priority = priority;
1842                 RandomSelection_chosen_ent = e;
1843                 RandomSelection_chosen_float = f;
1844                 RandomSelection_chosen_string = s;
1845                 RandomSelection_totalweight = weight;
1846         }
1847         else if(priority == RandomSelection_best_priority)
1848         {
1849                 RandomSelection_totalweight += weight;
1850                 if(random() * RandomSelection_totalweight <= weight)
1851                 {
1852                         RandomSelection_chosen_ent = e;
1853                         RandomSelection_chosen_float = f;
1854                         RandomSelection_chosen_string = s;
1855                 }
1856         }
1857 }
1858
1859 #ifndef MENUQC
1860 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1861 {
1862         // NOTE: we'll always choose the SMALLER value...
1863         float healthdamage, armordamage, armorideal;
1864         if (deathtype == DEATH_DROWN)  // Why should armor help here...
1865                 armorblock = 0;
1866         vector v;
1867         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1868         armordamage = a + (h - 1); // damage we can take if we could use more armor
1869         armorideal = healthdamage * armorblock;
1870         v_y = armorideal;
1871         if(armordamage < healthdamage)
1872         {
1873                 v_x = armordamage;
1874                 v_z = 1;
1875         }
1876         else
1877         {
1878                 v_x = healthdamage;
1879                 v_z = 0;
1880         }
1881         return v;
1882 }
1883
1884 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1885 {
1886         vector v;
1887         if (deathtype == DEATH_DROWN)  // Why should armor help here...
1888                 armorblock = 0;
1889         v_y = bound(0, damage * armorblock, a); // save
1890         v_x = bound(0, damage - v.y, damage); // take
1891         v_z = 0;
1892         return v;
1893 }
1894 #endif
1895
1896 string getcurrentmod()
1897 {
1898         float n;
1899         string m;
1900         m = cvar_string("fs_gamedir");
1901         n = tokenize_console(m);
1902         if(n == 0)
1903                 return "data";
1904         else
1905                 return argv(n - 1);
1906 }
1907
1908 #ifndef MENUQC
1909 #ifdef CSQC
1910 int ReadInt24_t()
1911 {
1912         float v;
1913         v = ReadShort() * 256; // note: this is signed
1914         v += ReadByte(); // note: this is unsigned
1915         return v;
1916 }
1917 vector ReadInt48_t()
1918 {
1919         vector v;
1920         v_x = ReadInt24_t();
1921         v_y = ReadInt24_t();
1922         v_z = 0;
1923         return v;
1924 }
1925 vector ReadInt72_t()
1926 {
1927         vector v;
1928         v_x = ReadInt24_t();
1929         v_y = ReadInt24_t();
1930         v_z = ReadInt24_t();
1931         return v;
1932 }
1933 #else
1934 void WriteInt24_t(float dst, float val)
1935 {
1936         float v;
1937         WriteShort(dst, (v = floor(val / 256)));
1938         WriteByte(dst, val - v * 256); // 0..255
1939 }
1940 void WriteInt48_t(float dst, vector val)
1941 {
1942         WriteInt24_t(dst, val.x);
1943         WriteInt24_t(dst, val.y);
1944 }
1945 void WriteInt72_t(float dst, vector val)
1946 {
1947         WriteInt24_t(dst, val.x);
1948         WriteInt24_t(dst, val.y);
1949         WriteInt24_t(dst, val.z);
1950 }
1951 #endif
1952 #endif
1953
1954 float float2range11(float f)
1955 {
1956         // continuous function mapping all reals into -1..1
1957         return f / (fabs(f) + 1);
1958 }
1959
1960 float float2range01(float f)
1961 {
1962         // continuous function mapping all reals into 0..1
1963         return 0.5 + 0.5 * float2range11(f);
1964 }
1965
1966 // from the GNU Scientific Library
1967 float gsl_ran_gaussian_lastvalue;
1968 float gsl_ran_gaussian_lastvalue_set;
1969 float gsl_ran_gaussian(float sigma)
1970 {
1971         float a, b;
1972         if(gsl_ran_gaussian_lastvalue_set)
1973         {
1974                 gsl_ran_gaussian_lastvalue_set = 0;
1975                 return sigma * gsl_ran_gaussian_lastvalue;
1976         }
1977         else
1978         {
1979                 a = random() * 2 * M_PI;
1980                 b = sqrt(-2 * log(random()));
1981                 gsl_ran_gaussian_lastvalue = cos(a) * b;
1982                 gsl_ran_gaussian_lastvalue_set = 1;
1983                 return sigma * sin(a) * b;
1984         }
1985 }
1986
1987 string car(string s)
1988 {
1989         float o;
1990         o = strstrofs(s, " ", 0);
1991         if(o < 0)
1992                 return s;
1993         return substring(s, 0, o);
1994 }
1995 string cdr(string s)
1996 {
1997         float o;
1998         o = strstrofs(s, " ", 0);
1999         if(o < 0)
2000                 return string_null;
2001         return substring(s, o + 1, strlen(s) - (o + 1));
2002 }
2003 float matchacl(string acl, string str)
2004 {
2005         string t, s;
2006         float r, d;
2007         r = 0;
2008         while(acl)
2009         {
2010                 t = car(acl); acl = cdr(acl);
2011
2012                 d = 1;
2013                 if(substring(t, 0, 1) == "-")
2014                 {
2015                         d = -1;
2016                         t = substring(t, 1, strlen(t) - 1);
2017                 }
2018                 else if(substring(t, 0, 1) == "+")
2019                         t = substring(t, 1, strlen(t) - 1);
2020
2021                 if(substring(t, -1, 1) == "*")
2022                 {
2023                         t = substring(t, 0, strlen(t) - 1);
2024                         s = substring(str, 0, strlen(t));
2025                 }
2026                 else
2027                         s = str;
2028
2029                 if(s == t)
2030                 {
2031                         r = d;
2032                 }
2033         }
2034         return r;
2035 }
2036 float startsWith(string haystack, string needle)
2037 {
2038         return substring(haystack, 0, strlen(needle)) == needle;
2039 }
2040 float startsWithNocase(string haystack, string needle)
2041 {
2042         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2043 }
2044
2045 string get_model_datafilename(string m, float sk, string fil)
2046 {
2047         if(m)
2048                 m = strcat(m, "_");
2049         else
2050                 m = "models/player/*_";
2051         if(sk >= 0)
2052                 m = strcat(m, ftos(sk));
2053         else
2054                 m = strcat(m, "*");
2055         return strcat(m, ".", fil);
2056 }
2057
2058 float get_model_parameters(string m, float sk)
2059 {
2060         string fn, s, c;
2061         float fh, i;
2062
2063         get_model_parameters_modelname = string_null;
2064         get_model_parameters_modelskin = -1;
2065         get_model_parameters_name = string_null;
2066         get_model_parameters_species = -1;
2067         get_model_parameters_sex = string_null;
2068         get_model_parameters_weight = -1;
2069         get_model_parameters_age = -1;
2070         get_model_parameters_desc = string_null;
2071         get_model_parameters_bone_upperbody = string_null;
2072         get_model_parameters_bone_weapon = string_null;
2073         for(i = 0; i < MAX_AIM_BONES; ++i)
2074         {
2075                 get_model_parameters_bone_aim[i] = string_null;
2076                 get_model_parameters_bone_aimweight[i] = 0;
2077         }
2078         get_model_parameters_fixbone = 0;
2079
2080         if (!m)
2081                 return 1;
2082
2083         if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2084                 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2085
2086         if(sk < 0)
2087         {
2088                 if(substring(m, -4, -1) != ".txt")
2089                         return 0;
2090                 if(substring(m, -6, 1) != "_")
2091                         return 0;
2092                 sk = stof(substring(m, -5, 1));
2093                 m = substring(m, 0, -7);
2094         }
2095
2096         fn = get_model_datafilename(m, sk, "txt");
2097         fh = fopen(fn, FILE_READ);
2098         if(fh < 0)
2099         {
2100                 sk = 0;
2101                 fn = get_model_datafilename(m, sk, "txt");
2102                 fh = fopen(fn, FILE_READ);
2103                 if(fh < 0)
2104                         return 0;
2105         }
2106
2107         get_model_parameters_modelname = m;
2108         get_model_parameters_modelskin = sk;
2109         while((s = fgets(fh)))
2110         {
2111                 if(s == "")
2112                         break; // next lines will be description
2113                 c = car(s);
2114                 s = cdr(s);
2115                 if(c == "name")
2116                         get_model_parameters_name = s;
2117                 if(c == "species")
2118                         switch(s)
2119                         {
2120                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
2121                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
2122                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2123                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2124                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2125                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
2126                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
2127                         }
2128                 if(c == "sex")
2129                         get_model_parameters_sex = s;
2130                 if(c == "weight")
2131                         get_model_parameters_weight = stof(s);
2132                 if(c == "age")
2133                         get_model_parameters_age = stof(s);
2134                 if(c == "description")
2135                         get_model_parameters_description = s;
2136                 if(c == "bone_upperbody")
2137                         get_model_parameters_bone_upperbody = s;
2138                 if(c == "bone_weapon")
2139                         get_model_parameters_bone_weapon = s;
2140                 for(i = 0; i < MAX_AIM_BONES; ++i)
2141                         if(c == strcat("bone_aim", ftos(i)))
2142                         {
2143                                 get_model_parameters_bone_aimweight[i] = stof(car(s));
2144                                 get_model_parameters_bone_aim[i] = cdr(s);
2145                         }
2146                 if(c == "fixbone")
2147                         get_model_parameters_fixbone = stof(s);
2148         }
2149
2150         while((s = fgets(fh)))
2151         {
2152                 if(get_model_parameters_desc)
2153                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2154                 if(s != "")
2155                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2156         }
2157
2158         fclose(fh);
2159
2160         return 1;
2161 }
2162
2163 vector vec2(vector v)
2164 {
2165         v_z = 0;
2166         return v;
2167 }
2168
2169 #ifndef MENUQC
2170 vector NearestPointOnBox(entity box, vector org)
2171 {
2172         vector m1, m2, nearest;
2173
2174         m1 = box.mins + box.origin;
2175         m2 = box.maxs + box.origin;
2176
2177         nearest_x = bound(m1_x, org.x, m2_x);
2178         nearest_y = bound(m1_y, org.y, m2_y);
2179         nearest_z = bound(m1_z, org.z, m2_z);
2180
2181         return nearest;
2182 }
2183 #endif
2184
2185 float vercmp_recursive(string v1, string v2)
2186 {
2187         float dot1, dot2;
2188         string s1, s2;
2189         float r;
2190
2191         dot1 = strstrofs(v1, ".", 0);
2192         dot2 = strstrofs(v2, ".", 0);
2193         if(dot1 == -1)
2194                 s1 = v1;
2195         else
2196                 s1 = substring(v1, 0, dot1);
2197         if(dot2 == -1)
2198                 s2 = v2;
2199         else
2200                 s2 = substring(v2, 0, dot2);
2201
2202         r = stof(s1) - stof(s2);
2203         if(r != 0)
2204                 return r;
2205
2206         r = strcasecmp(s1, s2);
2207         if(r != 0)
2208                 return r;
2209
2210         if(dot1 == -1)
2211                 if(dot2 == -1)
2212                         return 0;
2213                 else
2214                         return -1;
2215         else
2216                 if(dot2 == -1)
2217                         return 1;
2218                 else
2219                         return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2220 }
2221
2222 float vercmp(string v1, string v2)
2223 {
2224         if(strcasecmp(v1, v2) == 0) // early out check
2225                 return 0;
2226
2227         // "git" beats all
2228         if(v1 == "git")
2229                 return 1;
2230         if(v2 == "git")
2231                 return -1;
2232
2233         return vercmp_recursive(v1, v2);
2234 }
2235
2236 float u8_strsize(string s)
2237 {
2238         float l, i, c;
2239         l = 0;
2240         for(i = 0; ; ++i)
2241         {
2242                 c = str2chr(s, i);
2243                 if(c <= 0)
2244                         break;
2245                 ++l;
2246                 if(c >= 0x80)
2247                         ++l;
2248                 if(c >= 0x800)
2249                         ++l;
2250                 if(c >= 0x10000)
2251                         ++l;
2252         }
2253         return l;
2254 }
2255
2256 // translation helpers
2257 string language_filename(string s)
2258 {
2259         string fn;
2260         float fh;
2261         fn = prvm_language;
2262         if(fn == "" || fn == "dump")
2263                 return s;
2264         fn = strcat(s, ".", fn);
2265         if((fh = fopen(fn, FILE_READ)) >= 0)
2266         {
2267                 fclose(fh);
2268                 return fn;
2269         }
2270         return s;
2271 }
2272 string CTX(string s)
2273 {
2274         float p = strstrofs(s, "^", 0);
2275         if(p < 0)
2276                 return s;
2277         return substring(s, p+1, -1);
2278 }
2279
2280 // x-encoding (encoding as zero length invisible string)
2281 const string XENCODE_2  = "xX";
2282 const string XENCODE_22 = "0123456789abcdefABCDEF";
2283 string xencode(int f)
2284 {
2285         float a, b, c, d;
2286         d = f % 22; f = floor(f / 22);
2287         c = f % 22; f = floor(f / 22);
2288         b = f % 22; f = floor(f / 22);
2289         a = f %  2; // f = floor(f /  2);
2290         return strcat(
2291                 "^",
2292                 substring(XENCODE_2,  a, 1),
2293                 substring(XENCODE_22, b, 1),
2294                 substring(XENCODE_22, c, 1),
2295                 substring(XENCODE_22, d, 1)
2296         );
2297 }
2298 float xdecode(string s)
2299 {
2300         float a, b, c, d;
2301         if(substring(s, 0, 1) != "^")
2302                 return -1;
2303         if(strlen(s) < 5)
2304                 return -1;
2305         a = strstrofs(XENCODE_2,  substring(s, 1, 1), 0);
2306         b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2307         c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2308         d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2309         if(a < 0 || b < 0 || c < 0 || d < 0)
2310                 return -1;
2311         return ((a * 22 + b) * 22 + c) * 22 + d;
2312 }
2313
2314 float lowestbit(int f)
2315 {
2316         f &= ~(f * 2);
2317         f &= ~(f * 4);
2318         f &= ~(f * 16);
2319         f &= ~(f * 256);
2320         f &= ~(f * 65536);
2321         return f;
2322 }
2323
2324 /*
2325 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2326 {
2327         if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2328                 return input;
2329         else
2330                 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2331 }*/
2332
2333 // escape the string to make it safe for consoles
2334 string MakeConsoleSafe(string input)
2335 {
2336         input = strreplace("\n", "", input);
2337         input = strreplace("\\", "\\\\", input);
2338         input = strreplace("$", "$$", input);
2339         input = strreplace("\"", "\\\"", input);
2340         return input;
2341 }
2342
2343 #ifndef MENUQC
2344 // get true/false value of a string with multiple different inputs
2345 float InterpretBoolean(string input)
2346 {
2347         switch(strtolower(input))
2348         {
2349                 case "yes":
2350                 case "true":
2351                 case "on":
2352                         return true;
2353
2354                 case "no":
2355                 case "false":
2356                 case "off":
2357                         return false;
2358
2359                 default: return stof(input);
2360         }
2361 }
2362 #endif
2363
2364 #ifdef CSQC
2365 entity ReadCSQCEntity()
2366 {
2367         float f;
2368         f = ReadShort();
2369         if(f == 0)
2370                 return world;
2371         return findfloat(world, entnum, f);
2372 }
2373 #endif
2374
2375 float shutdown_running;
2376 #ifdef SVQC
2377 void SV_Shutdown()
2378 #endif
2379 #ifdef CSQC
2380 void CSQC_Shutdown()
2381 #endif
2382 #ifdef MENUQC
2383 void m_shutdown()
2384 #endif
2385 {
2386         if(shutdown_running)
2387         {
2388                 print("Recursive shutdown detected! Only restoring cvars...\n");
2389         }
2390         else
2391         {
2392                 shutdown_running = 1;
2393                 Shutdown();
2394         }
2395         cvar_settemp_restore(); // this must be done LAST, but in any case
2396 }
2397
2398 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2399 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2400 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2401 // this will use the value:
2402 //   128
2403 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2404 // accuracy at x is 1/derivative, i.e.
2405 //   APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2406 #ifdef SVQC
2407 void WriteApproxPastTime(float dst, float t)
2408 {
2409         float dt = time - t;
2410
2411         // warning: this is approximate; do not resend when you don't have to!
2412         // be careful with sendflags here!
2413         // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2414
2415         // map to range...
2416         dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2417
2418         // round...
2419         dt = rint(bound(0, dt, 255));
2420
2421         WriteByte(dst, dt);
2422 }
2423 #endif
2424 #ifdef CSQC
2425 float ReadApproxPastTime()
2426 {
2427         float dt = ReadByte();
2428
2429         // map from range...PPROXPASTTIME_MAX / 256
2430         dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2431
2432         return servertime - dt;
2433 }
2434 #endif
2435
2436 #ifndef MENUQC
2437 .float skeleton_bones_index;
2438 void Skeleton_SetBones(entity e)
2439 {
2440         // set skeleton_bones to the total number of bones on the model
2441         if(e.skeleton_bones_index == e.modelindex)
2442                 return; // same model, nothing to update
2443
2444         float skelindex;
2445         skelindex = skel_create(e.modelindex);
2446         e.skeleton_bones = skel_get_numbones(skelindex);
2447         skel_delete(skelindex);
2448         e.skeleton_bones_index = e.modelindex;
2449 }
2450 #endif
2451
2452 string to_execute_next_frame;
2453 void execute_next_frame()
2454 {
2455         if(to_execute_next_frame)
2456         {
2457                 localcmd("\n", to_execute_next_frame, "\n");
2458                 strunzone(to_execute_next_frame);
2459                 to_execute_next_frame = string_null;
2460         }
2461 }
2462 void queue_to_execute_next_frame(string s)
2463 {
2464         if(to_execute_next_frame)
2465         {
2466                 s = strcat(s, "\n", to_execute_next_frame);
2467                 strunzone(to_execute_next_frame);
2468         }
2469         to_execute_next_frame = strzone(s);
2470 }
2471
2472 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2473 {
2474         return
2475                 (((     startspeedfactor + endspeedfactor - 2
2476                 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2477                 ) * x + startspeedfactor
2478                 ) * x;
2479 }
2480
2481 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2482 {
2483         if(startspeedfactor < 0 || endspeedfactor < 0)
2484                 return false;
2485
2486         /*
2487         // if this is the case, the possible zeros of the first derivative are outside
2488         // 0..1
2489         We can calculate this condition as condition
2490         if(se <= 3)
2491                 return true;
2492         */
2493
2494         // better, see below:
2495         if(startspeedfactor <= 3 && endspeedfactor <= 3)
2496                 return true;
2497
2498         // if this is the case, the first derivative has no zeros at all
2499         float se = startspeedfactor + endspeedfactor;
2500         float s_e = startspeedfactor - endspeedfactor;
2501         if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2502                 return true;
2503
2504         // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2505         // we also get s_e <= 6 - se
2506         // 3 * (se - 4)^2 + (6 - se)^2
2507         // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2508         // Therefore, above "better" check works!
2509
2510         return false;
2511
2512         // known good cases:
2513         // (0, [0..3])
2514         // (0.5, [0..3.8])
2515         // (1, [0..4])
2516         // (1.5, [0..3.9])
2517         // (2, [0..3.7])
2518         // (2.5, [0..3.4])
2519         // (3, [0..3])
2520         // (3.5, [0.2..2.3])
2521         // (4, 1)
2522
2523         /*
2524            On another note:
2525            inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2526
2527            s + e - 2 == 0: no inflection
2528
2529            s + e > 2:
2530            0 < inflection < 1 if:
2531            0 < 2s + e - 3 < 3s + 3e - 6
2532            2s + e > 3 and 2e + s > 3
2533
2534            s + e < 2:
2535            0 < inflection < 1 if:
2536            0 > 2s + e - 3 > 3s + 3e - 6
2537            2s + e < 3 and 2e + s < 3
2538
2539            Therefore: there is an inflection point iff:
2540            e outside (3 - s)/2 .. 3 - s*2
2541
2542            in other words, if (s,e) in triangle (1,1)(0,3)(0,1.5) or in triangle (1,1)(3,0)(1.5,0)
2543         */
2544 }
2545
2546 .float FindConnectedComponent_processing;
2547 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2548 {
2549         entity queue_start, queue_end;
2550
2551         // we build a queue of to-be-processed entities.
2552         // queue_start is the next entity to be checked for neighbors
2553         // queue_end is the last entity added
2554
2555         if(e.FindConnectedComponent_processing)
2556                 error("recursion or broken cleanup");
2557
2558         // start with a 1-element queue
2559         queue_start = queue_end = e;
2560         queue_end.fld = world;
2561         queue_end.FindConnectedComponent_processing = 1;
2562
2563         // for each queued item:
2564         for(0; queue_start; queue_start = queue_start.fld)
2565         {
2566                 // find all neighbors of queue_start
2567                 entity t;
2568                 for(t = world; (t = nxt(t, queue_start, pass)); )
2569                 {
2570                         if(t.FindConnectedComponent_processing)
2571                                 continue;
2572                         if(iscon(t, queue_start, pass))
2573                         {
2574                                 // it is connected? ADD IT. It will look for neighbors soon too.
2575                                 queue_end.fld = t;
2576                                 queue_end = t;
2577                                 queue_end.fld = world;
2578                                 queue_end.FindConnectedComponent_processing = 1;
2579                         }
2580                 }
2581         }
2582
2583         // unmark
2584         for(queue_start = e; queue_start; queue_start = queue_start.fld)
2585                 queue_start.FindConnectedComponent_processing = 0;
2586 }
2587
2588 #ifdef SVQC
2589 vector combine_to_vector(float x, float y, float z)
2590 {
2591         vector result; result_x = x; result_y = y; result_z = z;
2592         return result;
2593 }
2594
2595 vector get_corner_position(entity box, float corner)
2596 {
2597         switch(corner)
2598         {
2599                 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2600                 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2601                 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2602                 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2603                 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2604                 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2605                 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2606                 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2607                 default: return '0 0 0';
2608         }
2609 }
2610 #endif
2611
2612 // todo: this sucks, lets find a better way to do backtraces?
2613 void backtrace(string msg)
2614 {
2615         float dev, war;
2616         #ifdef SVQC
2617         dev = autocvar_developer;
2618         war = autocvar_prvm_backtraceforwarnings;
2619         #else
2620         dev = cvar("developer");
2621         war = cvar("prvm_backtraceforwarnings");
2622         #endif
2623         cvar_set("developer", "1");
2624         cvar_set("prvm_backtraceforwarnings", "1");
2625         print("\n");
2626         print("--- CUT HERE ---\nWARNING: ");
2627         print(msg);
2628         print("\n");
2629         remove(world); // isn't there any better way to cause a backtrace?
2630         print("\n--- CUT UNTIL HERE ---\n");
2631         cvar_set("developer", ftos(dev));
2632         cvar_set("prvm_backtraceforwarnings", ftos(war));
2633 }
2634
2635 // color code replace, place inside of sprintf and parse the string
2636 string CCR(string input)
2637 {
2638         // See the autocvar declarations in util.qh for default values
2639
2640         // foreground/normal colors
2641         input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2642         input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2643         input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2644         input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2645
2646         // "kill" colors
2647         input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2648         input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2649         input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2650
2651         // background colors
2652         input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2653         input = strreplace("^N", "^7", input); // "none"-- reset to white...
2654         return input;
2655 }
2656
2657 vector vec3(float x, float y, float z)
2658 {
2659         vector v;
2660         v_x = x;
2661         v_y = y;
2662         v_z = z;
2663         return v;
2664 }
2665
2666 #ifndef MENUQC
2667 vector animfixfps(entity e, vector a, vector b)
2668 {
2669         // multi-frame anim: keep as-is
2670         if(a_y == 1)
2671         {
2672                 float dur;
2673                 dur = frameduration(e.modelindex, a.x);
2674                 if(dur <= 0 && b.y)
2675                 {
2676                         a = b;
2677                         dur = frameduration(e.modelindex, a.x);
2678                 }
2679                 if(dur > 0)
2680                         a_z = 1.0 / dur;
2681         }
2682         return a;
2683 }
2684 #endif
2685
2686 #ifdef SVQC
2687 void dedicated_print(string input) // print(), but only print if the server is not local
2688 {
2689         if(server_is_dedicated) { print(input); }
2690 }
2691 #endif
2692
2693 #ifndef MENUQC
2694 float Announcer_PickNumber(float type, float num)
2695 {
2696         switch(type)
2697         {
2698                 case CNT_GAMESTART:
2699                 {
2700                         switch(num)
2701                         {
2702                                 case 10: return ANNCE_NUM_GAMESTART_10;
2703                                 case 9:  return ANNCE_NUM_GAMESTART_9;
2704                                 case 8:  return ANNCE_NUM_GAMESTART_8;
2705                                 case 7:  return ANNCE_NUM_GAMESTART_7;
2706                                 case 6:  return ANNCE_NUM_GAMESTART_6;
2707                                 case 5:  return ANNCE_NUM_GAMESTART_5;
2708                                 case 4:  return ANNCE_NUM_GAMESTART_4;
2709                                 case 3:  return ANNCE_NUM_GAMESTART_3;
2710                                 case 2:  return ANNCE_NUM_GAMESTART_2;
2711                                 case 1:  return ANNCE_NUM_GAMESTART_1;
2712                         }
2713                         break;
2714                 }
2715                 case CNT_IDLE:
2716                 {
2717                         switch(num)
2718                         {
2719                                 case 10: return ANNCE_NUM_IDLE_10;
2720                                 case 9:  return ANNCE_NUM_IDLE_9;
2721                                 case 8:  return ANNCE_NUM_IDLE_8;
2722                                 case 7:  return ANNCE_NUM_IDLE_7;
2723                                 case 6:  return ANNCE_NUM_IDLE_6;
2724                                 case 5:  return ANNCE_NUM_IDLE_5;
2725                                 case 4:  return ANNCE_NUM_IDLE_4;
2726                                 case 3:  return ANNCE_NUM_IDLE_3;
2727                                 case 2:  return ANNCE_NUM_IDLE_2;
2728                                 case 1:  return ANNCE_NUM_IDLE_1;
2729                         }
2730                         break;
2731                 }
2732                 case CNT_KILL:
2733                 {
2734                         switch(num)
2735                         {
2736                                 case 10: return ANNCE_NUM_KILL_10;
2737                                 case 9:  return ANNCE_NUM_KILL_9;
2738                                 case 8:  return ANNCE_NUM_KILL_8;
2739                                 case 7:  return ANNCE_NUM_KILL_7;
2740                                 case 6:  return ANNCE_NUM_KILL_6;
2741                                 case 5:  return ANNCE_NUM_KILL_5;
2742                                 case 4:  return ANNCE_NUM_KILL_4;
2743                                 case 3:  return ANNCE_NUM_KILL_3;
2744                                 case 2:  return ANNCE_NUM_KILL_2;
2745                                 case 1:  return ANNCE_NUM_KILL_1;
2746                         }
2747                         break;
2748                 }
2749                 case CNT_RESPAWN:
2750                 {
2751                         switch(num)
2752                         {
2753                                 case 10: return ANNCE_NUM_RESPAWN_10;
2754                                 case 9:  return ANNCE_NUM_RESPAWN_9;
2755                                 case 8:  return ANNCE_NUM_RESPAWN_8;
2756                                 case 7:  return ANNCE_NUM_RESPAWN_7;
2757                                 case 6:  return ANNCE_NUM_RESPAWN_6;
2758                                 case 5:  return ANNCE_NUM_RESPAWN_5;
2759                                 case 4:  return ANNCE_NUM_RESPAWN_4;
2760                                 case 3:  return ANNCE_NUM_RESPAWN_3;
2761                                 case 2:  return ANNCE_NUM_RESPAWN_2;
2762                                 case 1:  return ANNCE_NUM_RESPAWN_1;
2763                         }
2764                         break;
2765                 }
2766                 case CNT_ROUNDSTART:
2767                 {
2768                         switch(num)
2769                         {
2770                                 case 10: return ANNCE_NUM_ROUNDSTART_10;
2771                                 case 9:  return ANNCE_NUM_ROUNDSTART_9;
2772                                 case 8:  return ANNCE_NUM_ROUNDSTART_8;
2773                                 case 7:  return ANNCE_NUM_ROUNDSTART_7;
2774                                 case 6:  return ANNCE_NUM_ROUNDSTART_6;
2775                                 case 5:  return ANNCE_NUM_ROUNDSTART_5;
2776                                 case 4:  return ANNCE_NUM_ROUNDSTART_4;
2777                                 case 3:  return ANNCE_NUM_ROUNDSTART_3;
2778                                 case 2:  return ANNCE_NUM_ROUNDSTART_2;
2779                                 case 1:  return ANNCE_NUM_ROUNDSTART_1;
2780                         }
2781                         break;
2782                 }
2783                 default:
2784                 {
2785                         switch(num)
2786                         {
2787                                 case 10: return ANNCE_NUM_10;
2788                                 case 9:  return ANNCE_NUM_9;
2789                                 case 8:  return ANNCE_NUM_8;
2790                                 case 7:  return ANNCE_NUM_7;
2791                                 case 6:  return ANNCE_NUM_6;
2792                                 case 5:  return ANNCE_NUM_5;
2793                                 case 4:  return ANNCE_NUM_4;
2794                                 case 3:  return ANNCE_NUM_3;
2795                                 case 2:  return ANNCE_NUM_2;
2796                                 case 1:  return ANNCE_NUM_1;
2797                         }
2798                         break;
2799                 }
2800         }
2801         return NOTIF_ABORT; // abort sending if none of these numbers were right
2802 }
2803 #endif
2804
2805 #ifndef MENUQC
2806 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2807 {
2808         switch(nativecontents)
2809         {
2810                 case CONTENT_EMPTY:
2811                         return 0;
2812                 case CONTENT_SOLID:
2813                         return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2814                 case CONTENT_WATER:
2815                         return DPCONTENTS_WATER;
2816                 case CONTENT_SLIME:
2817                         return DPCONTENTS_SLIME;
2818                 case CONTENT_LAVA:
2819                         return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2820                 case CONTENT_SKY:
2821                         return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2822         }
2823         return 0;
2824 }
2825
2826 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2827 {
2828         if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2829                 return CONTENT_SOLID;
2830         if(supercontents & DPCONTENTS_SKY)
2831                 return CONTENT_SKY;
2832         if(supercontents & DPCONTENTS_LAVA)
2833                 return CONTENT_LAVA;
2834         if(supercontents & DPCONTENTS_SLIME)
2835                 return CONTENT_SLIME;
2836         if(supercontents & DPCONTENTS_WATER)
2837                 return CONTENT_WATER;
2838         return CONTENT_EMPTY;
2839 }
2840 #endif
2841
2842 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2843 {
2844         return
2845                 (c - 2 * b + a) * (t * t) +
2846                 (b - a) * (2 * t) +
2847                 a;
2848 }
2849
2850 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2851 {
2852         return
2853                 (c - 2 * b + a) * (2 * t) +
2854                 (b - a) * 2;
2855 }