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