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