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