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