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