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