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