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