]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
Merge branch 'master' into terencehill/string_prefixes_cleanup
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
1 #include "util.qh"
2
3 #if defined(CSQC)
4         #include "../dpdefs/csprogsdefs.qh"
5     #include "../client/defs.qh"
6     #include "constants.qh"
7     #include "../warpzonelib/mathlib.qh"
8     #include "mapinfo.qh"
9     #include "notifications.qh"
10     #include "deathtypes.qh"
11 #elif defined(MENUQC)
12 #elif defined(SVQC)
13         #include "../dpdefs/progsdefs.qh"
14     #include "../dpdefs/dpextensions.qh"
15     #include "../warpzonelib/mathlib.qh"
16     #include "constants.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         get_model_parameters_modelname = string_null;
2059         get_model_parameters_modelskin = -1;
2060         get_model_parameters_name = string_null;
2061         get_model_parameters_species = -1;
2062         get_model_parameters_sex = string_null;
2063         get_model_parameters_weight = -1;
2064         get_model_parameters_age = -1;
2065         get_model_parameters_desc = string_null;
2066         get_model_parameters_bone_upperbody = string_null;
2067         get_model_parameters_bone_weapon = string_null;
2068         for(int i = 0; i < MAX_AIM_BONES; ++i)
2069         {
2070                 get_model_parameters_bone_aim[i] = string_null;
2071                 get_model_parameters_bone_aimweight[i] = 0;
2072         }
2073         get_model_parameters_fixbone = 0;
2074
2075         if (!m)
2076                 return 1;
2077
2078         if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2079                 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2080
2081         if(sk < 0)
2082         {
2083                 if(substring(m, -4, -1) != ".txt")
2084                         return 0;
2085                 if(substring(m, -6, 1) != "_")
2086                         return 0;
2087                 sk = stof(substring(m, -5, 1));
2088                 m = substring(m, 0, -7);
2089         }
2090
2091         string fn = get_model_datafilename(m, sk, "txt");
2092         int fh = fopen(fn, FILE_READ);
2093         if(fh < 0)
2094         {
2095                 sk = 0;
2096                 fn = get_model_datafilename(m, sk, "txt");
2097                 fh = fopen(fn, FILE_READ);
2098                 if(fh < 0)
2099                         return 0;
2100         }
2101
2102         get_model_parameters_modelname = m;
2103         get_model_parameters_modelskin = sk;
2104         string s, c;
2105         while((s = fgets(fh)))
2106         {
2107                 if(s == "")
2108                         break; // next lines will be description
2109                 c = car(s);
2110                 s = cdr(s);
2111                 if(c == "name")
2112                         get_model_parameters_name = s;
2113                 if(c == "species")
2114                         switch(s)
2115                         {
2116                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
2117                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
2118                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2119                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2120                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2121                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
2122                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
2123                         }
2124                 if(c == "sex")
2125                         get_model_parameters_sex = s;
2126                 if(c == "weight")
2127                         get_model_parameters_weight = stof(s);
2128                 if(c == "age")
2129                         get_model_parameters_age = stof(s);
2130                 if(c == "description")
2131                         get_model_parameters_description = s;
2132                 if(c == "bone_upperbody")
2133                         get_model_parameters_bone_upperbody = s;
2134                 if(c == "bone_weapon")
2135                         get_model_parameters_bone_weapon = s;
2136                 for(int i = 0; i < MAX_AIM_BONES; ++i)
2137                         if(c == strcat("bone_aim", ftos(i)))
2138                         {
2139                                 get_model_parameters_bone_aimweight[i] = stof(car(s));
2140                                 get_model_parameters_bone_aim[i] = cdr(s);
2141                         }
2142                 if(c == "fixbone")
2143                         get_model_parameters_fixbone = stof(s);
2144         }
2145
2146         while((s = fgets(fh)))
2147         {
2148                 if(get_model_parameters_desc)
2149                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2150                 if(s != "")
2151                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2152         }
2153
2154         fclose(fh);
2155
2156         return 1;
2157 }
2158
2159 vector vec2(vector v)
2160 {
2161         v.z = 0;
2162         return v;
2163 }
2164
2165 #ifndef MENUQC
2166 vector NearestPointOnBox(entity box, vector org)
2167 {
2168         vector m1, m2, nearest;
2169
2170         m1 = box.mins + box.origin;
2171         m2 = box.maxs + box.origin;
2172
2173         nearest.x = bound(m1_x, org.x, m2_x);
2174         nearest.y = bound(m1_y, org.y, m2_y);
2175         nearest.z = bound(m1_z, org.z, m2_z);
2176
2177         return nearest;
2178 }
2179 #endif
2180
2181 float vercmp_recursive(string v1, string v2)
2182 {
2183         float dot1, dot2;
2184         string s1, s2;
2185         float r;
2186
2187         dot1 = strstrofs(v1, ".", 0);
2188         dot2 = strstrofs(v2, ".", 0);
2189         if(dot1 == -1)
2190                 s1 = v1;
2191         else
2192                 s1 = substring(v1, 0, dot1);
2193         if(dot2 == -1)
2194                 s2 = v2;
2195         else
2196                 s2 = substring(v2, 0, dot2);
2197
2198         r = stof(s1) - stof(s2);
2199         if(r != 0)
2200                 return r;
2201
2202         r = strcasecmp(s1, s2);
2203         if(r != 0)
2204                 return r;
2205
2206         if(dot1 == -1)
2207                 if(dot2 == -1)
2208                         return 0;
2209                 else
2210                         return -1;
2211         else
2212                 if(dot2 == -1)
2213                         return 1;
2214                 else
2215                         return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2216 }
2217
2218 float vercmp(string v1, string v2)
2219 {
2220         if(strcasecmp(v1, v2) == 0) // early out check
2221                 return 0;
2222
2223         // "git" beats all
2224         if(v1 == "git")
2225                 return 1;
2226         if(v2 == "git")
2227                 return -1;
2228
2229         return vercmp_recursive(v1, v2);
2230 }
2231
2232 float u8_strsize(string s)
2233 {
2234         float l, i, c;
2235         l = 0;
2236         for(i = 0; ; ++i)
2237         {
2238                 c = str2chr(s, i);
2239                 if(c <= 0)
2240                         break;
2241                 ++l;
2242                 if(c >= 0x80)
2243                         ++l;
2244                 if(c >= 0x800)
2245                         ++l;
2246                 if(c >= 0x10000)
2247                         ++l;
2248         }
2249         return l;
2250 }
2251
2252 // translation helpers
2253 string language_filename(string s)
2254 {
2255         string fn;
2256         float fh;
2257         fn = prvm_language;
2258         if(fn == "" || fn == "dump")
2259                 return s;
2260         fn = strcat(s, ".", fn);
2261         if((fh = fopen(fn, FILE_READ)) >= 0)
2262         {
2263                 fclose(fh);
2264                 return fn;
2265         }
2266         return s;
2267 }
2268 string CTX(string s)
2269 {
2270         float p = strstrofs(s, "^", 0);
2271         if(p < 0)
2272                 return s;
2273         return substring(s, p+1, -1);
2274 }
2275
2276 // x-encoding (encoding as zero length invisible string)
2277 const string XENCODE_2  = "xX";
2278 const string XENCODE_22 = "0123456789abcdefABCDEF";
2279 string xencode(int f)
2280 {
2281         float a, b, c, d;
2282         d = f % 22; f = floor(f / 22);
2283         c = f % 22; f = floor(f / 22);
2284         b = f % 22; f = floor(f / 22);
2285         a = f %  2; // f = floor(f /  2);
2286         return strcat(
2287                 "^",
2288                 substring(XENCODE_2,  a, 1),
2289                 substring(XENCODE_22, b, 1),
2290                 substring(XENCODE_22, c, 1),
2291                 substring(XENCODE_22, d, 1)
2292         );
2293 }
2294 float xdecode(string s)
2295 {
2296         float a, b, c, d;
2297         if(substring(s, 0, 1) != "^")
2298                 return -1;
2299         if(strlen(s) < 5)
2300                 return -1;
2301         a = strstrofs(XENCODE_2,  substring(s, 1, 1), 0);
2302         b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2303         c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2304         d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2305         if(a < 0 || b < 0 || c < 0 || d < 0)
2306                 return -1;
2307         return ((a * 22 + b) * 22 + c) * 22 + d;
2308 }
2309
2310 float lowestbit(int f)
2311 {
2312         f &= ~(f * 2);
2313         f &= ~(f * 4);
2314         f &= ~(f * 16);
2315         f &= ~(f * 256);
2316         f &= ~(f * 65536);
2317         return f;
2318 }
2319
2320 /*
2321 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2322 {
2323         if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2324                 return input;
2325         else
2326                 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2327 }*/
2328
2329 // escape the string to make it safe for consoles
2330 string MakeConsoleSafe(string input)
2331 {
2332         input = strreplace("\n", "", input);
2333         input = strreplace("\\", "\\\\", input);
2334         input = strreplace("$", "$$", input);
2335         input = strreplace("\"", "\\\"", input);
2336         return input;
2337 }
2338
2339 #ifndef MENUQC
2340 // get true/false value of a string with multiple different inputs
2341 float InterpretBoolean(string input)
2342 {
2343         switch(strtolower(input))
2344         {
2345                 case "yes":
2346                 case "true":
2347                 case "on":
2348                         return true;
2349
2350                 case "no":
2351                 case "false":
2352                 case "off":
2353                         return false;
2354
2355                 default: return stof(input);
2356         }
2357 }
2358 #endif
2359
2360 #ifdef CSQC
2361 entity ReadCSQCEntity()
2362 {
2363         int f = ReadShort();
2364         if(f == 0)
2365                 return world;
2366         return findfloat(world, entnum, f);
2367 }
2368 #endif
2369
2370 float shutdown_running;
2371 #ifdef SVQC
2372 void SV_Shutdown()
2373 #endif
2374 #ifdef CSQC
2375 void CSQC_Shutdown()
2376 #endif
2377 #ifdef MENUQC
2378 void m_shutdown()
2379 #endif
2380 {
2381         if(shutdown_running)
2382         {
2383                 print("Recursive shutdown detected! Only restoring cvars...\n");
2384         }
2385         else
2386         {
2387                 shutdown_running = 1;
2388                 Shutdown();
2389         }
2390         cvar_settemp_restore(); // this must be done LAST, but in any case
2391 }
2392
2393 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2394 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2395 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2396 // this will use the value:
2397 //   128
2398 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2399 // accuracy at x is 1/derivative, i.e.
2400 //   APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2401 #ifdef SVQC
2402 void WriteApproxPastTime(float dst, float t)
2403 {
2404         float dt = time - t;
2405
2406         // warning: this is approximate; do not resend when you don't have to!
2407         // be careful with sendflags here!
2408         // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2409
2410         // map to range...
2411         dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2412
2413         // round...
2414         dt = rint(bound(0, dt, 255));
2415
2416         WriteByte(dst, dt);
2417 }
2418 #endif
2419 #ifdef CSQC
2420 float ReadApproxPastTime()
2421 {
2422         float dt = ReadByte();
2423
2424         // map from range...PPROXPASTTIME_MAX / 256
2425         dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2426
2427         return servertime - dt;
2428 }
2429 #endif
2430
2431 #ifndef MENUQC
2432 .float skeleton_bones_index;
2433 void Skeleton_SetBones(entity e)
2434 {
2435         // set skeleton_bones to the total number of bones on the model
2436         if(e.skeleton_bones_index == e.modelindex)
2437                 return; // same model, nothing to update
2438
2439         float skelindex;
2440         skelindex = skel_create(e.modelindex);
2441         e.skeleton_bones = skel_get_numbones(skelindex);
2442         skel_delete(skelindex);
2443         e.skeleton_bones_index = e.modelindex;
2444 }
2445 #endif
2446
2447 string to_execute_next_frame;
2448 void execute_next_frame()
2449 {
2450         if(to_execute_next_frame)
2451         {
2452                 localcmd("\n", to_execute_next_frame, "\n");
2453                 strunzone(to_execute_next_frame);
2454                 to_execute_next_frame = string_null;
2455         }
2456 }
2457 void queue_to_execute_next_frame(string s)
2458 {
2459         if(to_execute_next_frame)
2460         {
2461                 s = strcat(s, "\n", to_execute_next_frame);
2462                 strunzone(to_execute_next_frame);
2463         }
2464         to_execute_next_frame = strzone(s);
2465 }
2466
2467 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2468 {
2469         return
2470                 (((     startspeedfactor + endspeedfactor - 2
2471                 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2472                 ) * x + startspeedfactor
2473                 ) * x;
2474 }
2475
2476 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2477 {
2478         if(startspeedfactor < 0 || endspeedfactor < 0)
2479                 return false;
2480
2481         /*
2482         // if this is the case, the possible zeros of the first derivative are outside
2483         // 0..1
2484         We can calculate this condition as condition
2485         if(se <= 3)
2486                 return true;
2487         */
2488
2489         // better, see below:
2490         if(startspeedfactor <= 3 && endspeedfactor <= 3)
2491                 return true;
2492
2493         // if this is the case, the first derivative has no zeros at all
2494         float se = startspeedfactor + endspeedfactor;
2495         float s_e = startspeedfactor - endspeedfactor;
2496         if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2497                 return true;
2498
2499         // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2500         // we also get s_e <= 6 - se
2501         // 3 * (se - 4)^2 + (6 - se)^2
2502         // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2503         // Therefore, above "better" check works!
2504
2505         return false;
2506
2507         // known good cases:
2508         // (0, [0..3])
2509         // (0.5, [0..3.8])
2510         // (1, [0..4])
2511         // (1.5, [0..3.9])
2512         // (2, [0..3.7])
2513         // (2.5, [0..3.4])
2514         // (3, [0..3])
2515         // (3.5, [0.2..2.3])
2516         // (4, 1)
2517
2518         /*
2519            On another note:
2520            inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2521
2522            s + e - 2 == 0: no inflection
2523
2524            s + e > 2:
2525            0 < inflection < 1 if:
2526            0 < 2s + e - 3 < 3s + 3e - 6
2527            2s + e > 3 and 2e + s > 3
2528
2529            s + e < 2:
2530            0 < inflection < 1 if:
2531            0 > 2s + e - 3 > 3s + 3e - 6
2532            2s + e < 3 and 2e + s < 3
2533
2534            Therefore: there is an inflection point iff:
2535            e outside (3 - s)/2 .. 3 - s*2
2536
2537            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)
2538         */
2539 }
2540
2541 .float FindConnectedComponent_processing;
2542 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2543 {
2544         entity queue_start, queue_end;
2545
2546         // we build a queue of to-be-processed entities.
2547         // queue_start is the next entity to be checked for neighbors
2548         // queue_end is the last entity added
2549
2550         if(e.FindConnectedComponent_processing)
2551                 error("recursion or broken cleanup");
2552
2553         // start with a 1-element queue
2554         queue_start = queue_end = e;
2555         queue_end.fld = world;
2556         queue_end.FindConnectedComponent_processing = 1;
2557
2558         // for each queued item:
2559         for (; queue_start; queue_start = queue_start.fld)
2560         {
2561                 // find all neighbors of queue_start
2562                 entity t;
2563                 for(t = world; (t = nxt(t, queue_start, pass)); )
2564                 {
2565                         if(t.FindConnectedComponent_processing)
2566                                 continue;
2567                         if(iscon(t, queue_start, pass))
2568                         {
2569                                 // it is connected? ADD IT. It will look for neighbors soon too.
2570                                 queue_end.fld = t;
2571                                 queue_end = t;
2572                                 queue_end.fld = world;
2573                                 queue_end.FindConnectedComponent_processing = 1;
2574                         }
2575                 }
2576         }
2577
2578         // unmark
2579         for(queue_start = e; queue_start; queue_start = queue_start.fld)
2580                 queue_start.FindConnectedComponent_processing = 0;
2581 }
2582
2583 #ifdef SVQC
2584 vector combine_to_vector(float x, float y, float z)
2585 {
2586         vector result; result.x = x; result.y = y; result.z = z;
2587         return result;
2588 }
2589
2590 vector get_corner_position(entity box, float corner)
2591 {
2592         switch(corner)
2593         {
2594                 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2595                 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2596                 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2597                 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2598                 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2599                 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2600                 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2601                 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2602                 default: return '0 0 0';
2603         }
2604 }
2605 #endif
2606
2607 // todo: this sucks, lets find a better way to do backtraces?
2608 void backtrace(string msg)
2609 {
2610         float dev, war;
2611         #ifdef SVQC
2612         dev = autocvar_developer;
2613         war = autocvar_prvm_backtraceforwarnings;
2614         #else
2615         dev = cvar("developer");
2616         war = cvar("prvm_backtraceforwarnings");
2617         #endif
2618         cvar_set("developer", "1");
2619         cvar_set("prvm_backtraceforwarnings", "1");
2620         print("\n");
2621         print("--- CUT HERE ---\nWARNING: ");
2622         print(msg);
2623         print("\n");
2624         remove(world); // isn't there any better way to cause a backtrace?
2625         print("\n--- CUT UNTIL HERE ---\n");
2626         cvar_set("developer", ftos(dev));
2627         cvar_set("prvm_backtraceforwarnings", ftos(war));
2628 }
2629
2630 // color code replace, place inside of sprintf and parse the string
2631 string CCR(string input)
2632 {
2633         // See the autocvar declarations in util.qh for default values
2634
2635         // foreground/normal colors
2636         input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2637         input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2638         input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2639         input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2640
2641         // "kill" colors
2642         input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2643         input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2644         input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2645
2646         // background colors
2647         input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2648         input = strreplace("^N", "^7", input); // "none"-- reset to white...
2649         return input;
2650 }
2651
2652 vector vec3(float x, float y, float z)
2653 {
2654         vector v;
2655         v.x = x;
2656         v.y = y;
2657         v.z = z;
2658         return v;
2659 }
2660
2661 #ifndef MENUQC
2662 vector animfixfps(entity e, vector a, vector b)
2663 {
2664         // multi-frame anim: keep as-is
2665         if(a.y == 1)
2666         {
2667                 float dur;
2668                 dur = frameduration(e.modelindex, a.x);
2669                 if(dur <= 0 && b.y)
2670                 {
2671                         a = b;
2672                         dur = frameduration(e.modelindex, a.x);
2673                 }
2674                 if(dur > 0)
2675                         a.z = 1.0 / dur;
2676         }
2677         return a;
2678 }
2679 #endif
2680
2681 #ifdef SVQC
2682 void dedicated_print(string input) // print(), but only print if the server is not local
2683 {
2684         if(server_is_dedicated) { print(input); }
2685 }
2686 #endif
2687
2688 #ifndef MENUQC
2689 float Announcer_PickNumber(float type, float num)
2690 {
2691         switch(type)
2692         {
2693                 case CNT_GAMESTART:
2694                 {
2695                         switch(num)
2696                         {
2697                                 case 10: return ANNCE_NUM_GAMESTART_10;
2698                                 case 9:  return ANNCE_NUM_GAMESTART_9;
2699                                 case 8:  return ANNCE_NUM_GAMESTART_8;
2700                                 case 7:  return ANNCE_NUM_GAMESTART_7;
2701                                 case 6:  return ANNCE_NUM_GAMESTART_6;
2702                                 case 5:  return ANNCE_NUM_GAMESTART_5;
2703                                 case 4:  return ANNCE_NUM_GAMESTART_4;
2704                                 case 3:  return ANNCE_NUM_GAMESTART_3;
2705                                 case 2:  return ANNCE_NUM_GAMESTART_2;
2706                                 case 1:  return ANNCE_NUM_GAMESTART_1;
2707                         }
2708                         break;
2709                 }
2710                 case CNT_IDLE:
2711                 {
2712                         switch(num)
2713                         {
2714                                 case 10: return ANNCE_NUM_IDLE_10;
2715                                 case 9:  return ANNCE_NUM_IDLE_9;
2716                                 case 8:  return ANNCE_NUM_IDLE_8;
2717                                 case 7:  return ANNCE_NUM_IDLE_7;
2718                                 case 6:  return ANNCE_NUM_IDLE_6;
2719                                 case 5:  return ANNCE_NUM_IDLE_5;
2720                                 case 4:  return ANNCE_NUM_IDLE_4;
2721                                 case 3:  return ANNCE_NUM_IDLE_3;
2722                                 case 2:  return ANNCE_NUM_IDLE_2;
2723                                 case 1:  return ANNCE_NUM_IDLE_1;
2724                         }
2725                         break;
2726                 }
2727                 case CNT_KILL:
2728                 {
2729                         switch(num)
2730                         {
2731                                 case 10: return ANNCE_NUM_KILL_10;
2732                                 case 9:  return ANNCE_NUM_KILL_9;
2733                                 case 8:  return ANNCE_NUM_KILL_8;
2734                                 case 7:  return ANNCE_NUM_KILL_7;
2735                                 case 6:  return ANNCE_NUM_KILL_6;
2736                                 case 5:  return ANNCE_NUM_KILL_5;
2737                                 case 4:  return ANNCE_NUM_KILL_4;
2738                                 case 3:  return ANNCE_NUM_KILL_3;
2739                                 case 2:  return ANNCE_NUM_KILL_2;
2740                                 case 1:  return ANNCE_NUM_KILL_1;
2741                         }
2742                         break;
2743                 }
2744                 case CNT_RESPAWN:
2745                 {
2746                         switch(num)
2747                         {
2748                                 case 10: return ANNCE_NUM_RESPAWN_10;
2749                                 case 9:  return ANNCE_NUM_RESPAWN_9;
2750                                 case 8:  return ANNCE_NUM_RESPAWN_8;
2751                                 case 7:  return ANNCE_NUM_RESPAWN_7;
2752                                 case 6:  return ANNCE_NUM_RESPAWN_6;
2753                                 case 5:  return ANNCE_NUM_RESPAWN_5;
2754                                 case 4:  return ANNCE_NUM_RESPAWN_4;
2755                                 case 3:  return ANNCE_NUM_RESPAWN_3;
2756                                 case 2:  return ANNCE_NUM_RESPAWN_2;
2757                                 case 1:  return ANNCE_NUM_RESPAWN_1;
2758                         }
2759                         break;
2760                 }
2761                 case CNT_ROUNDSTART:
2762                 {
2763                         switch(num)
2764                         {
2765                                 case 10: return ANNCE_NUM_ROUNDSTART_10;
2766                                 case 9:  return ANNCE_NUM_ROUNDSTART_9;
2767                                 case 8:  return ANNCE_NUM_ROUNDSTART_8;
2768                                 case 7:  return ANNCE_NUM_ROUNDSTART_7;
2769                                 case 6:  return ANNCE_NUM_ROUNDSTART_6;
2770                                 case 5:  return ANNCE_NUM_ROUNDSTART_5;
2771                                 case 4:  return ANNCE_NUM_ROUNDSTART_4;
2772                                 case 3:  return ANNCE_NUM_ROUNDSTART_3;
2773                                 case 2:  return ANNCE_NUM_ROUNDSTART_2;
2774                                 case 1:  return ANNCE_NUM_ROUNDSTART_1;
2775                         }
2776                         break;
2777                 }
2778                 default:
2779                 {
2780                         switch(num)
2781                         {
2782                                 case 10: return ANNCE_NUM_10;
2783                                 case 9:  return ANNCE_NUM_9;
2784                                 case 8:  return ANNCE_NUM_8;
2785                                 case 7:  return ANNCE_NUM_7;
2786                                 case 6:  return ANNCE_NUM_6;
2787                                 case 5:  return ANNCE_NUM_5;
2788                                 case 4:  return ANNCE_NUM_4;
2789                                 case 3:  return ANNCE_NUM_3;
2790                                 case 2:  return ANNCE_NUM_2;
2791                                 case 1:  return ANNCE_NUM_1;
2792                         }
2793                         break;
2794                 }
2795         }
2796         return NOTIF_ABORT; // abort sending if none of these numbers were right
2797 }
2798 #endif
2799
2800 #ifndef MENUQC
2801 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2802 {
2803         switch(nativecontents)
2804         {
2805                 case CONTENT_EMPTY:
2806                         return 0;
2807                 case CONTENT_SOLID:
2808                         return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2809                 case CONTENT_WATER:
2810                         return DPCONTENTS_WATER;
2811                 case CONTENT_SLIME:
2812                         return DPCONTENTS_SLIME;
2813                 case CONTENT_LAVA:
2814                         return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2815                 case CONTENT_SKY:
2816                         return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2817         }
2818         return 0;
2819 }
2820
2821 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2822 {
2823         if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2824                 return CONTENT_SOLID;
2825         if(supercontents & DPCONTENTS_SKY)
2826                 return CONTENT_SKY;
2827         if(supercontents & DPCONTENTS_LAVA)
2828                 return CONTENT_LAVA;
2829         if(supercontents & DPCONTENTS_SLIME)
2830                 return CONTENT_SLIME;
2831         if(supercontents & DPCONTENTS_WATER)
2832                 return CONTENT_WATER;
2833         return CONTENT_EMPTY;
2834 }
2835 #endif
2836
2837 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2838 {
2839         return
2840                 (c - 2 * b + a) * (t * t) +
2841                 (b - a) * (2 * t) +
2842                 a;
2843 }
2844
2845 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2846 {
2847         return
2848                 (c - 2 * b + a) * (2 * t) +
2849                 (b - a) * 2;
2850 }