]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
8c87b7fdad4f0e81a26e66633d9bb9032c7020a8
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
1 // checkextension wrapper for log
2 float sqrt(float f); // declared later
3 float exp(float f); // declared later
4 float pow(float f, float e); // declared later
5 float checkextension(string s); // declared later
6 float log_synth(float f)
7 {
8         float p, l;
9         if(f < 0)
10                 return sqrt(-1); // nan? -inf?
11         if(f == 0)
12                 return sqrt(-1); // ACTUALLY this should rather be -inf, but we cannot create a +inf in QC
13         if(f + f == f)
14                 return l; // +inf
15         if(f < 1)
16         {
17                 f = 1 / f;
18                 p = -1;
19         }
20         else
21                 p = 1;
22         while(f > 2)
23         {
24                 f = sqrt(f);
25                 p *= 2;
26         }
27         // two steps are good enough
28         l = ((6-f) * f - 5) / 4.32808512266689022212;
29         l += exp(-l) * f - 1;
30         l += exp(-l) * f - 1;
31         return l * p;
32 }
33 float log(float f)
34 {
35         if(checkextension("DP_QC_LOG"))
36                 return log_builtin(f);
37         else
38                 return log_synth(f);
39 }
40
41 string wordwrap_buffer;
42
43 void wordwrap_buffer_put(string s)
44 {
45         wordwrap_buffer = strcat(wordwrap_buffer, s);
46 }
47
48 string wordwrap(string s, float l)
49 {
50         string r;
51         wordwrap_buffer = "";
52         wordwrap_cb(s, l, wordwrap_buffer_put);
53         r = wordwrap_buffer;
54         wordwrap_buffer = "";
55         return r;
56 }
57
58 #ifndef MENUQC
59 #ifndef CSQC
60 void wordwrap_buffer_sprint(string s)
61 {
62         wordwrap_buffer = strcat(wordwrap_buffer, s);
63         if(s == "\n")
64         {
65                 sprint(self, wordwrap_buffer);
66                 wordwrap_buffer = "";
67         }
68 }
69
70 void wordwrap_sprint(string s, float l)
71 {
72         wordwrap_buffer = "";
73         wordwrap_cb(s, l, wordwrap_buffer_sprint);
74         if(wordwrap_buffer != "")
75                 sprint(self, strcat(wordwrap_buffer, "\n"));
76         wordwrap_buffer = "";
77         return;
78 }
79 #endif
80 #endif
81
82 string unescape(string in)
83 {
84         local float i, len;
85         local string str, s;
86
87         // but it doesn't seem to be necessary in my tests at least
88         in = strzone(in);
89
90         len = strlen(in);
91         str = "";
92         for(i = 0; i < len; ++i)
93         {
94                 s = substring(in, i, 1);
95                 if(s == "\\")
96                 {
97                         s = substring(in, i+1, 1);
98                         if(s == "n")
99                                 str = strcat(str, "\n");
100                         else if(s == "\\")
101                                 str = strcat(str, "\\");
102                         else
103                                 str = strcat(str, substring(in, i, 2));
104                         ++i;
105                 } else
106                         str = strcat(str, s);
107         }
108
109         strunzone(in);
110         return str;
111 }
112
113 void wordwrap_cb(string s, float l, void(string) callback)
114 {
115         local string c;
116         local float lleft, i, j, wlen;
117
118         s = strzone(s);
119         lleft = l;
120         for (i = 0;i < strlen(s);++i)
121         {
122                 if (substring(s, i, 2) == "\\n")
123                 {
124                         callback("\n");
125                         lleft = l;
126                         ++i;
127                 }
128                 else if (substring(s, i, 1) == "\n")
129                 {
130                         callback("\n");
131                         lleft = l;
132                 }
133                 else if (substring(s, i, 1) == " ")
134                 {
135                         if (lleft > 0)
136                         {
137                                 callback(" ");
138                                 lleft = lleft - 1;
139                         }
140                 }
141                 else
142                 {
143                         for (j = i+1;j < strlen(s);++j)
144                                 //    ^^ this skips over the first character of a word, which
145                                 //       is ALWAYS part of the word
146                                 //       this is safe since if i+1 == strlen(s), i will become
147                                 //       strlen(s)-1 at the end of this block and the function
148                                 //       will terminate. A space can't be the first character we
149                                 //       read here, and neither can a \n be the start, since these
150                                 //       two cases have been handled above.
151                         {
152                                 c = substring(s, j, 1);
153                                 if (c == " ")
154                                         break;
155                                 if (c == "\\")
156                                         break;
157                                 if (c == "\n")
158                                         break;
159                                 // we need to keep this tempstring alive even if substring is
160                                 // called repeatedly, so call strcat even though we're not
161                                 // doing anything
162                                 callback("");
163                         }
164                         wlen = j - i;
165                         if (lleft < wlen)
166                         {
167                                 callback("\n");
168                                 lleft = l;
169                         }
170                         callback(substring(s, i, wlen));
171                         lleft = lleft - wlen;
172                         i = j - 1;
173                 }
174         }
175         strunzone(s);
176 }
177
178 float dist_point_line(vector p, vector l0, vector ldir)
179 {
180         ldir = normalize(ldir);
181         
182         // remove the component in line direction
183         p = p - (p * ldir) * ldir;
184
185         // vlen of the remaining vector
186         return vlen(p);
187 }
188
189 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
190 {
191         entity e;
192         e = start;
193         funcPre(pass, e);
194         while(e.downleft)
195         {
196                 e = e.downleft;
197                 funcPre(pass, e);
198         }
199         funcPost(pass, e);
200         while(e != start)
201         {
202                 if(e.right)
203                 {
204                         e = e.right;
205                         funcPre(pass, e);
206                         while(e.downleft)
207                         {
208                                 e = e.downleft;
209                                 funcPre(pass, e);
210                         }
211                 }
212                 else
213                         e = e.up;
214                 funcPost(pass, e);
215         }
216 }
217
218 float median(float a, float b, float c)
219 {
220         if(a < c)
221                 return bound(a, b, c);
222         return bound(c, b, a);
223 }
224
225 // converts a number to a string with the indicated number of decimals
226 // works for up to 10 decimals!
227 string ftos_decimals(float number, float decimals)
228 {
229         // we have sprintf...
230         return sprintf("%.*f", decimals, number);
231 }
232
233 float time;
234 vector colormapPaletteColor(float c, float isPants)
235 {
236         switch(c)
237         {
238                 case  0: return '0.800000 0.800000 0.800000';
239                 case  1: return '0.600000 0.400000 0.000000';
240                 case  2: return '0.000000 1.000000 0.501961';
241                 case  3: return '0.000000 1.000000 0.000000';
242                 case  4: return '1.000000 0.000000 0.000000';
243                 case  5: return '0.000000 0.658824 1.000000';
244                 case  6: return '0.000000 1.000000 1.000000';
245                 case  7: return '0.501961 1.000000 0.000000';
246                 case  8: return '0.501961 0.000000 1.000000';
247                 case  9: return '1.000000 0.000000 1.000000';
248                 case 10: return '1.000000 0.000000 0.501961';
249                 case 11: return '0.600000 0.600000 0.600000';
250                 case 12: return '1.000000 1.000000 0.000000';
251                 case 13: return '0.000000 0.313725 1.000000';
252                 case 14: return '1.000000 0.501961 0.000000';
253                 case 15:
254                         if(isPants)
255                                 return
256                                           '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
257                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
258                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
259                         else
260                                 return
261                                           '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
262                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
263                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
264                 default: return '0.000 0.000 0.000';
265         }
266 }
267
268 // unzone the string, and return it as tempstring. Safe to be called on string_null
269 string fstrunzone(string s)
270 {
271         string sc;
272         if not(s)
273                 return s;
274         sc = strcat(s, "");
275         strunzone(s);
276         return sc;
277 }
278
279 // Databases (hash tables)
280 #define DB_BUCKETS 8192
281 void db_save(float db, string pFilename)
282 {
283         float fh, i, n;
284         fh = fopen(pFilename, FILE_WRITE);
285         if(fh < 0) 
286         {
287                 print(strcat("^1Can't write DB to ", pFilename));
288                 return;
289         }
290         n = buf_getsize(db);
291         fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
292         for(i = 0; i < n; ++i)
293                 fputs(fh, strcat(bufstr_get(db, i), "\n"));
294         fclose(fh);
295 }
296
297 float db_create()
298 {
299         return buf_create();
300 }
301
302 float db_load(string pFilename)
303 {
304         float db, fh, i, j, n;
305         string l;
306         db = buf_create();
307         if(db < 0)
308                 return -1;
309         fh = fopen(pFilename, FILE_READ);
310         if(fh < 0)
311                 return db;
312         l = fgets(fh);
313         if(stof(l) == DB_BUCKETS)
314         {
315                 i = 0;
316                 while((l = fgets(fh)))
317                 {
318                         if(l != "")
319                                 bufstr_set(db, i, l);
320                         ++i;
321                 }
322         }
323         else
324         {
325                 // different count of buckets, or a dump?
326                 // need to reorganize the database then (SLOW)
327                 //
328                 // note: we also parse the first line (l) in case the DB file is
329                 // missing the bucket count
330                 do
331                 {
332                         n = tokenizebyseparator(l, "\\");
333                         for(j = 2; j < n; j += 2)
334                                 db_put(db, argv(j-1), uri_unescape(argv(j)));
335                 }
336                 while((l = fgets(fh)));
337         }
338         fclose(fh);
339         return db;
340 }
341
342 void db_dump(float db, string pFilename)
343 {
344         float fh, i, j, n, m;
345         fh = fopen(pFilename, FILE_WRITE);
346         if(fh < 0)
347                 error(strcat("Can't dump DB to ", pFilename));
348         n = buf_getsize(db);
349         fputs(fh, "0\n");
350         for(i = 0; i < n; ++i)
351         {
352                 m = tokenizebyseparator(bufstr_get(db, i), "\\");
353                 for(j = 2; j < m; j += 2)
354                         fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
355         }
356         fclose(fh);
357 }
358
359 void db_close(float db)
360 {
361         buf_del(db);
362 }
363
364 string db_get(float db, string pKey)
365 {
366         float h;
367         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
368         return uri_unescape(infoget(bufstr_get(db, h), pKey));
369 }
370
371 void db_put(float db, string pKey, string pValue)
372 {
373         float h;
374         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
375         bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
376 }
377
378 void db_test()
379 {
380         float db, i;
381         print("LOAD...\n");
382         db = db_load("foo.db");
383         print("LOADED. FILL...\n");
384         for(i = 0; i < DB_BUCKETS; ++i)
385                 db_put(db, ftos(random()), "X");
386         print("FILLED. SAVE...\n");
387         db_save(db, "foo.db");
388         print("SAVED. CLOSE...\n");
389         db_close(db);
390         print("CLOSED.\n");
391 }
392
393 // Multiline text file buffers
394 float buf_load(string pFilename)
395 {
396         float buf, fh, i;
397         string l;
398         buf = buf_create();
399         if(buf < 0)
400                 return -1;
401         fh = fopen(pFilename, FILE_READ);
402         if(fh < 0)
403                 return buf;
404         i = 0;
405         while((l = fgets(fh)))
406         {
407                 bufstr_set(buf, i, l);
408                 ++i;
409         }
410         fclose(fh);
411         return buf;
412 }
413
414 void buf_save(float buf, string pFilename)
415 {
416         float fh, i, n;
417         fh = fopen(pFilename, FILE_WRITE);
418         if(fh < 0)
419                 error(strcat("Can't write buf to ", pFilename));
420         n = buf_getsize(buf);
421         for(i = 0; i < n; ++i)
422                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
423         fclose(fh);
424 }
425
426 string GametypeNameFromType(float g)
427 {
428         if      (g == GAME_DEATHMATCH) return "dm";
429         else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
430         else if (g == GAME_DOMINATION) return "dom";
431         else if (g == GAME_CTF) return "ctf";
432         else if (g == GAME_RUNEMATCH) return "rune";
433         else if (g == GAME_LMS) return "lms";
434         else if (g == GAME_ARENA) return "arena";
435         else if (g == GAME_CA) return "ca";
436         else if (g == GAME_KEYHUNT) return "kh";
437         else if (g == GAME_ONSLAUGHT) return "ons";
438         else if (g == GAME_ASSAULT) return "as";
439         else if (g == GAME_RACE) return "rc";
440         else if (g == GAME_NEXBALL) return "nexball";
441         else if (g == GAME_CTS) return "cts";
442         return "dm";
443 }
444
445 string mmsss(float tenths)
446 {
447         float minutes;
448         string s;
449         tenths = floor(tenths + 0.5);
450         minutes = floor(tenths / 600);
451         tenths -= minutes * 600;
452         s = ftos(1000 + tenths);
453         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
454 }
455
456 string mmssss(float hundredths)
457 {
458         float minutes;
459         string s;
460         hundredths = floor(hundredths + 0.5);
461         minutes = floor(hundredths / 6000);
462         hundredths -= minutes * 6000;
463         s = ftos(10000 + hundredths);
464         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
465 }
466
467 string ScoreString(float pFlags, float pValue)
468 {
469         string valstr;
470         float l;
471
472         pValue = floor(pValue + 0.5); // round
473
474         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
475                 valstr = "";
476         else if(pFlags & SFL_RANK)
477         {
478                 valstr = ftos(pValue);
479                 l = strlen(valstr);
480                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
481                         valstr = strcat(valstr, "th");
482                 else if(substring(valstr, l - 1, 1) == "1")
483                         valstr = strcat(valstr, "st");
484                 else if(substring(valstr, l - 1, 1) == "2")
485                         valstr = strcat(valstr, "nd");
486                 else if(substring(valstr, l - 1, 1) == "3")
487                         valstr = strcat(valstr, "rd");
488                 else
489                         valstr = strcat(valstr, "th");
490         }
491         else if(pFlags & SFL_TIME)
492                 valstr = TIME_ENCODED_TOSTRING(pValue);
493         else
494                 valstr = ftos(pValue);
495         
496         return valstr;
497 }
498
499 vector cross(vector a, vector b)
500 {
501         return
502                 '1 0 0' * (a_y * b_z - a_z * b_y)
503         +       '0 1 0' * (a_z * b_x - a_x * b_z)
504         +       '0 0 1' * (a_x * b_y - a_y * b_x);
505 }
506
507 // compressed vector format:
508 // like MD3, just even shorter
509 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
510 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
511 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
512 //     length = 2^(length_encoded/8) / 8
513 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
514 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
515 // the special value 0 indicates the zero vector
516
517 float lengthLogTable[128];
518
519 float invertLengthLog(float x)
520 {
521         float l, r, m, lerr, rerr;
522
523         if(x >= lengthLogTable[127])
524                 return 127;
525         if(x <= lengthLogTable[0])
526                 return 0;
527
528         l = 0;
529         r = 127;
530
531         while(r - l > 1)
532         {
533                 m = floor((l + r) / 2);
534                 if(lengthLogTable[m] < x)
535                         l = m;
536                 else
537                         r = m;
538         }
539
540         // now: r is >=, l is <
541         lerr = (x - lengthLogTable[l]);
542         rerr = (lengthLogTable[r] - x);
543         if(lerr < rerr)
544                 return l;
545         return r;
546 }
547
548 vector decompressShortVector(float data)
549 {
550         vector out;
551         float pitch, yaw, len;
552         if(data == 0)
553                 return '0 0 0';
554         pitch = (data & 0xF000) / 0x1000;
555         yaw =   (data & 0x0F80) / 0x80;
556         len =   (data & 0x007F);
557
558         //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
559
560         if(pitch == 0)
561         {
562                 out_x = 0;
563                 out_y = 0;
564                 if(yaw == 31)
565                         out_z = -1;
566                 else
567                         out_z = +1;
568         }
569         else
570         {
571                 yaw   = .19634954084936207740 * yaw;
572                 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
573                 out_x = cos(yaw) *  cos(pitch);
574                 out_y = sin(yaw) *  cos(pitch);
575                 out_z =            -sin(pitch);
576         }
577
578         //print("decompressed: ", vtos(out), "\n");
579
580         return out * lengthLogTable[len];
581 }
582
583 float compressShortVector(vector vec)
584 {
585         vector ang;
586         float pitch, yaw, len;
587         if(vlen(vec) == 0)
588                 return 0;
589         //print("compress: ", vtos(vec), "\n");
590         ang = vectoangles(vec);
591         ang_x = -ang_x;
592         if(ang_x < -90)
593                 ang_x += 360;
594         if(ang_x < -90 && ang_x > +90)
595                 error("BOGUS vectoangles");
596         //print("angles: ", vtos(ang), "\n");
597
598         pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
599         if(pitch == 0)
600         {
601                 if(vec_z < 0)
602                         yaw = 31;
603                 else
604                         yaw = 30;
605         }
606         else
607                 yaw = floor(0.5 + ang_y * 32 / 360)          & 31; // 0..360 to 0..32
608         len = invertLengthLog(vlen(vec));
609
610         //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
611
612         return (pitch * 0x1000) + (yaw * 0x80) + len;
613 }
614
615 void compressShortVector_init()
616 {
617         float l, f, i;
618         l = 1;
619         f = pow(2, 1/8);
620         for(i = 0; i < 128; ++i)
621         {
622                 lengthLogTable[i] = l;
623                 l *= f;
624         }
625
626         if(cvar("developer"))
627         {
628                 print("Verifying vector compression table...\n");
629                 for(i = 0x0F00; i < 0xFFFF; ++i)
630                         if(i != compressShortVector(decompressShortVector(i)))
631                         {
632                                 print("BROKEN vector compression: ", ftos(i));
633                                 print(" -> ", vtos(decompressShortVector(i)));
634                                 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
635                                 print("\n");
636                                 error("b0rk");
637                         }
638                 print("Done.\n");
639         }
640 }
641
642 #ifndef MENUQC
643 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
644 {
645         traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
646         traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
647         traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
648         traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
649         traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
650         traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
651         traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
652         traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
653         traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
654         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
655         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
656         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
657         return 1;
658 }
659 #endif
660
661 string fixPriorityList(string order, float from, float to, float subtract, float complete)
662 {
663         string neworder;
664         float i, n, w;
665
666         n = tokenize_console(order);
667         neworder = "";
668         for(i = 0; i < n; ++i)
669         {
670                 w = stof(argv(i));
671                 if(w == floor(w))
672                 {
673                         if(w >= from && w <= to)
674                                 neworder = strcat(neworder, ftos(w), " ");
675                         else
676                         {
677                                 w -= subtract;
678                                 if(w >= from && w <= to)
679                                         neworder = strcat(neworder, ftos(w), " ");
680                         }
681                 }
682         }
683
684         if(complete)
685         {
686                 n = tokenize_console(neworder);
687                 for(w = to; w >= from; --w)
688                 {
689                         for(i = 0; i < n; ++i)
690                                 if(stof(argv(i)) == w)
691                                         break;
692                         if(i == n) // not found
693                                 neworder = strcat(neworder, ftos(w), " ");
694                 }
695         }
696         
697         return substring(neworder, 0, strlen(neworder) - 1);
698 }
699
700 string mapPriorityList(string order, string(string) mapfunc)
701 {
702         string neworder;
703         float i, n;
704
705         n = tokenize_console(order);
706         neworder = "";
707         for(i = 0; i < n; ++i)
708                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
709         
710         return substring(neworder, 0, strlen(neworder) - 1);
711 }
712
713 string swapInPriorityList(string order, float i, float j)
714 {
715         string s;
716         float w, n;
717
718         n = tokenize_console(order);
719
720         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
721         {
722                 s = "";
723                 for(w = 0; w < n; ++w)
724                 {
725                         if(w == i)
726                                 s = strcat(s, argv(j), " ");
727                         else if(w == j)
728                                 s = strcat(s, argv(i), " ");
729                         else
730                                 s = strcat(s, argv(w), " ");
731                 }
732                 return substring(s, 0, strlen(s) - 1);
733         }
734         
735         return order;
736 }
737
738 float cvar_value_issafe(string s)
739 {
740         if(strstrofs(s, "\"", 0) >= 0)
741                 return 0;
742         if(strstrofs(s, "\\", 0) >= 0)
743                 return 0;
744         if(strstrofs(s, ";", 0) >= 0)
745                 return 0;
746         if(strstrofs(s, "$", 0) >= 0)
747                 return 0;
748         if(strstrofs(s, "\r", 0) >= 0)
749                 return 0;
750         if(strstrofs(s, "\n", 0) >= 0)
751                 return 0;
752         return 1;
753 }
754
755 #ifndef MENUQC
756 void get_mi_min_max(float mode)
757 {
758         vector mi, ma;
759
760         if(mi_shortname)
761                 strunzone(mi_shortname);
762         mi_shortname = mapname;
763         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
764                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
765         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
766                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
767         mi_shortname = strzone(mi_shortname);
768
769 #ifdef CSQC
770         mi = world.mins;
771         ma = world.maxs;
772 #else
773         mi = world.absmin;
774         ma = world.absmax;
775 #endif
776
777         mi_min = mi;
778         mi_max = ma;
779         MapInfo_Get_ByName(mi_shortname, 0, 0);
780         if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
781         {
782                 mi_min = MapInfo_Map_mins;
783                 mi_max = MapInfo_Map_maxs;
784         }
785         else
786         {
787                 // not specified
788                 if(mode)
789                 {
790                         // be clever
791                         tracebox('1 0 0' * mi_x,
792                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
793                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
794                                          '1 0 0' * ma_x,
795                                          MOVE_WORLDONLY,
796                                          world);
797                         if(!trace_startsolid)
798                                 mi_min_x = trace_endpos_x;
799
800                         tracebox('0 1 0' * mi_y,
801                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
802                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
803                                          '0 1 0' * ma_y,
804                                          MOVE_WORLDONLY,
805                                          world);
806                         if(!trace_startsolid)
807                                 mi_min_y = trace_endpos_y;
808
809                         tracebox('0 0 1' * mi_z,
810                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
811                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
812                                          '0 0 1' * ma_z,
813                                          MOVE_WORLDONLY,
814                                          world);
815                         if(!trace_startsolid)
816                                 mi_min_z = trace_endpos_z;
817
818                         tracebox('1 0 0' * ma_x,
819                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
820                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
821                                          '1 0 0' * mi_x,
822                                          MOVE_WORLDONLY,
823                                          world);
824                         if(!trace_startsolid)
825                                 mi_max_x = trace_endpos_x;
826
827                         tracebox('0 1 0' * ma_y,
828                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
829                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
830                                          '0 1 0' * mi_y,
831                                          MOVE_WORLDONLY,
832                                          world);
833                         if(!trace_startsolid)
834                                 mi_max_y = trace_endpos_y;
835
836                         tracebox('0 0 1' * ma_z,
837                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
838                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
839                                          '0 0 1' * mi_z,
840                                          MOVE_WORLDONLY,
841                                          world);
842                         if(!trace_startsolid)
843                                 mi_max_z = trace_endpos_z;
844                 }
845         }
846 }
847
848 void get_mi_min_max_texcoords(float mode)
849 {
850         vector extend;
851
852         get_mi_min_max(mode);
853
854         mi_picmin = mi_min;
855         mi_picmax = mi_max;
856
857         // extend mi_picmax to get a square aspect ratio
858         // center the map in that area
859         extend = mi_picmax - mi_picmin;
860         if(extend_y > extend_x)
861         {
862                 mi_picmin_x -= (extend_y - extend_x) * 0.5;
863                 mi_picmax_x += (extend_y - extend_x) * 0.5;
864         }
865         else
866         {
867                 mi_picmin_y -= (extend_x - extend_y) * 0.5;
868                 mi_picmax_y += (extend_x - extend_y) * 0.5;
869         }
870
871         // add another some percent
872         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
873         mi_picmin -= extend;
874         mi_picmax += extend;
875
876         // calculate the texcoords
877         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
878         // first the two corners of the origin
879         mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
880         mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
881         mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
882         mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
883         // then the other corners
884         mi_pictexcoord1_x = mi_pictexcoord0_x;
885         mi_pictexcoord1_y = mi_pictexcoord2_y;
886         mi_pictexcoord3_x = mi_pictexcoord2_x;
887         mi_pictexcoord3_y = mi_pictexcoord0_y;
888 }
889 #endif
890
891 #ifdef CSQC
892 void cvar_settemp(string pKey, string pValue)
893 {
894         error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
895 }
896 void cvar_settemp_restore()
897 {
898         error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
899 }
900 #else
901 void cvar_settemp(string pKey, string pValue)
902 {
903         float i;
904         string settemp_var;
905         if(cvar_string(pKey) == pValue)
906                 return;
907         i = cvar("settemp_idx");
908         cvar_set("settemp_idx", ftos(i+1));
909         settemp_var = strcat("_settemp_x", ftos(i));
910 #ifdef MENUQC
911         registercvar(settemp_var, "", 0);
912 #else
913         registercvar(settemp_var, "");
914 #endif
915         cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
916         cvar_set(settemp_var, cvar_string(pKey));
917         cvar_set(pKey, pValue);
918 }
919
920 void cvar_settemp_restore()
921 {
922         // undo what cvar_settemp did
923         float n, i;
924         n = tokenize_console(cvar_string("settemp_list"));
925         for(i = 0; i < n - 3; i += 3)
926                 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
927         cvar_set("settemp_list", "0");
928 }
929 #endif
930
931 float almost_equals(float a, float b)
932 {
933         float eps;
934         eps = (max(a, -a) + max(b, -b)) * 0.001;
935         if(a - b < eps && b - a < eps)
936                 return TRUE;
937         return FALSE;
938 }
939
940 float almost_in_bounds(float a, float b, float c)
941 {
942         float eps;
943         eps = (max(a, -a) + max(c, -c)) * 0.001;
944         return b == median(a - eps, b, c + eps);
945 }
946
947 float power2of(float e)
948 {
949         return pow(2, e);
950 }
951 float log2of(float x)
952 {
953         // NOTE: generated code
954         if(x > 2048)
955                 if(x > 131072)
956                         if(x > 1048576)
957                                 if(x > 4194304)
958                                         return 23;
959                                 else
960                                         if(x > 2097152)
961                                                 return 22;
962                                         else
963                                                 return 21;
964                         else
965                                 if(x > 524288)
966                                         return 20;
967                                 else
968                                         if(x > 262144)
969                                                 return 19;
970                                         else
971                                                 return 18;
972                 else
973                         if(x > 16384)
974                                 if(x > 65536)
975                                         return 17;
976                                 else
977                                         if(x > 32768)
978                                                 return 16;
979                                         else
980                                                 return 15;
981                         else
982                                 if(x > 8192)
983                                         return 14;
984                                 else
985                                         if(x > 4096)
986                                                 return 13;
987                                         else
988                                                 return 12;
989         else
990                 if(x > 32)
991                         if(x > 256)
992                                 if(x > 1024)
993                                         return 11;
994                                 else
995                                         if(x > 512)
996                                                 return 10;
997                                         else
998                                                 return 9;
999                         else
1000                                 if(x > 128)
1001                                         return 8;
1002                                 else
1003                                         if(x > 64)
1004                                                 return 7;
1005                                         else
1006                                                 return 6;
1007                 else
1008                         if(x > 4)
1009                                 if(x > 16)
1010                                         return 5;
1011                                 else
1012                                         if(x > 8)
1013                                                 return 4;
1014                                         else
1015                                                 return 3;
1016                         else
1017                                 if(x > 2)
1018                                         return 2;
1019                                 else
1020                                         if(x > 1)
1021                                                 return 1;
1022                                         else
1023                                                 return 0;
1024 }
1025
1026 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1027 {
1028         if(mi == ma)
1029                 return 0;
1030         else if(ma == rgb_x)
1031         {
1032                 if(rgb_y >= rgb_z)
1033                         return (rgb_y - rgb_z) / (ma - mi);
1034                 else
1035                         return (rgb_y - rgb_z) / (ma - mi) + 6;
1036         }
1037         else if(ma == rgb_y)
1038                 return (rgb_z - rgb_x) / (ma - mi) + 2;
1039         else // if(ma == rgb_z)
1040                 return (rgb_x - rgb_y) / (ma - mi) + 4;
1041 }
1042
1043 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1044 {
1045         vector rgb;
1046
1047         hue -= 6 * floor(hue / 6);
1048
1049         //else if(ma == rgb_x)
1050         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1051         if(hue <= 1)
1052         {
1053                 rgb_x = ma;
1054                 rgb_y = hue * (ma - mi) + mi;
1055                 rgb_z = mi;
1056         }
1057         //else if(ma == rgb_y)
1058         //      hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1059         else if(hue <= 2)
1060         {
1061                 rgb_x = (2 - hue) * (ma - mi) + mi;
1062                 rgb_y = ma;
1063                 rgb_z = mi;
1064         }
1065         else if(hue <= 3)
1066         {
1067                 rgb_x = mi;
1068                 rgb_y = ma;
1069                 rgb_z = (hue - 2) * (ma - mi) + mi;
1070         }
1071         //else // if(ma == rgb_z)
1072         //      hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1073         else if(hue <= 4)
1074         {
1075                 rgb_x = mi;
1076                 rgb_y = (4 - hue) * (ma - mi) + mi;
1077                 rgb_z = ma;
1078         }
1079         else if(hue <= 5)
1080         {
1081                 rgb_x = (hue - 4) * (ma - mi) + mi;
1082                 rgb_y = mi;
1083                 rgb_z = ma;
1084         }
1085         //else if(ma == rgb_x)
1086         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1087         else // if(hue <= 6)
1088         {
1089                 rgb_x = ma;
1090                 rgb_y = mi;
1091                 rgb_z = (6 - hue) * (ma - mi) + mi;
1092         }
1093
1094         return rgb;
1095 }
1096
1097 vector rgb_to_hsv(vector rgb)
1098 {
1099         float mi, ma;
1100         vector hsv;
1101
1102         mi = min3(rgb_x, rgb_y, rgb_z);
1103         ma = max3(rgb_x, rgb_y, rgb_z);
1104
1105         hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1106         hsv_z = ma;
1107
1108         if(ma == 0)
1109                 hsv_y = 0;
1110         else
1111                 hsv_y = 1 - mi/ma;
1112         
1113         return hsv;
1114 }
1115
1116 vector hsv_to_rgb(vector hsv)
1117 {
1118         return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1119 }
1120
1121 vector rgb_to_hsl(vector rgb)
1122 {
1123         float mi, ma;
1124         vector hsl;
1125
1126         mi = min3(rgb_x, rgb_y, rgb_z);
1127         ma = max3(rgb_x, rgb_y, rgb_z);
1128
1129         hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1130         
1131         hsl_z = 0.5 * (mi + ma);
1132         if(mi == ma)
1133                 hsl_y = 0;
1134         else if(hsl_z <= 0.5)
1135                 hsl_y = (ma - mi) / (2*hsl_z);
1136         else // if(hsl_z > 0.5)
1137                 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1138         
1139         return hsl;
1140 }
1141
1142 vector hsl_to_rgb(vector hsl)
1143 {
1144         float mi, ma, maminusmi;
1145
1146         if(hsl_z <= 0.5)
1147                 maminusmi = hsl_y * 2 * hsl_z;
1148         else
1149                 maminusmi = hsl_y * (2 - 2 * hsl_z);
1150         
1151         // hsl_z     = 0.5 * mi + 0.5 * ma
1152         // maminusmi =     - mi +       ma
1153         mi = hsl_z - 0.5 * maminusmi;
1154         ma = hsl_z + 0.5 * maminusmi;
1155
1156         return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1157 }
1158
1159 string rgb_to_hexcolor(vector rgb)
1160 {
1161         return
1162                 strcat(
1163                         "^x",
1164                         DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1165                         DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1166                         DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1167                 );
1168 }
1169
1170 // requires that m2>m1 in all coordinates, and that m4>m3
1171 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;};
1172
1173 // requires the same, but is a stronger condition
1174 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;};
1175
1176 #ifndef MENUQC
1177 #endif
1178
1179 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1180 {
1181         float ICanHasKallerz;
1182
1183         // detect color codes support in the width function
1184         ICanHasKallerz = (w("^7", theSize) == 0);
1185
1186         // STOP.
1187         // The following function is SLOW.
1188         // For your safety and for the protection of those around you...
1189         // DO NOT CALL THIS AT HOME.
1190         // No really, don't.
1191         if(w(theText, theSize) <= maxWidth)
1192                 return strlen(theText); // yeah!
1193
1194         // binary search for right place to cut string
1195         float ch;
1196         float left, right, middle; // this always works
1197         left = 0;
1198         right = strlen(theText); // this always fails
1199         do
1200         {
1201                 middle = floor((left + right) / 2);
1202                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1203                         left = middle;
1204                 else
1205                         right = middle;
1206         }
1207         while(left < right - 1);
1208
1209         if(ICanHasKallerz)
1210         {
1211                 // NOTE: when color codes are involved, this binary search is,
1212                 // mathematically, BROKEN. However, it is obviously guaranteed to
1213                 // terminate, as the range still halves each time - but nevertheless, it is
1214                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1215                 // range, and "right" is outside).
1216                 
1217                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1218                 // and decrease left on the basis of the chars detected of the truncated tag
1219                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1220                 // (sometimes too much but with a correct result)
1221                 // it fixes also ^[0-9]
1222                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1223                         left-=1;
1224
1225                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1226                         left-=2;
1227                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1228                         {
1229                                 ch = str2chr(theText, left-1);
1230                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1231                                         left-=3;
1232                         }
1233                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1234                         {
1235                                 ch = str2chr(theText, left-2);
1236                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1237                                 {
1238                                         ch = str2chr(theText, left-1);
1239                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1240                                                 left-=4;
1241                                 }
1242                         }
1243         }
1244         
1245         return left;
1246 }
1247
1248 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1249 {
1250         float ICanHasKallerz;
1251
1252         // detect color codes support in the width function
1253         ICanHasKallerz = (w("^7") == 0);
1254
1255         // STOP.
1256         // The following function is SLOW.
1257         // For your safety and for the protection of those around you...
1258         // DO NOT CALL THIS AT HOME.
1259         // No really, don't.
1260         if(w(theText) <= maxWidth)
1261                 return strlen(theText); // yeah!
1262
1263         // binary search for right place to cut string
1264         float ch;
1265         float left, right, middle; // this always works
1266         left = 0;
1267         right = strlen(theText); // this always fails
1268         do
1269         {
1270                 middle = floor((left + right) / 2);
1271                 if(w(substring(theText, 0, middle)) <= maxWidth)
1272                         left = middle;
1273                 else
1274                         right = middle;
1275         }
1276         while(left < right - 1);
1277
1278         if(ICanHasKallerz)
1279         {
1280                 // NOTE: when color codes are involved, this binary search is,
1281                 // mathematically, BROKEN. However, it is obviously guaranteed to
1282                 // terminate, as the range still halves each time - but nevertheless, it is
1283                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1284                 // range, and "right" is outside).
1285                 
1286                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1287                 // and decrease left on the basis of the chars detected of the truncated tag
1288                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1289                 // (sometimes too much but with a correct result)
1290                 // it fixes also ^[0-9]
1291                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1292                         left-=1;
1293
1294                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1295                         left-=2;
1296                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1297                         {
1298                                 ch = str2chr(theText, left-1);
1299                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1300                                         left-=3;
1301                         }
1302                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1303                         {
1304                                 ch = str2chr(theText, left-2);
1305                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1306                                 {
1307                                         ch = str2chr(theText, left-1);
1308                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1309                                                 left-=4;
1310                                 }
1311                         }
1312         }
1313         
1314         return left;
1315 }
1316
1317 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1318 {
1319         float cantake;
1320         float take;
1321         string s;
1322
1323         s = getWrappedLine_remaining;
1324
1325         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1326         if(cantake > 0 && cantake < strlen(s))
1327         {
1328                 take = cantake - 1;
1329                 while(take > 0 && substring(s, take, 1) != " ")
1330                         --take;
1331                 if(take == 0)
1332                 {
1333                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1334                         if(getWrappedLine_remaining == "")
1335                                 getWrappedLine_remaining = string_null;
1336                         return substring(s, 0, cantake);
1337                 }
1338                 else
1339                 {
1340                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1341                         if(getWrappedLine_remaining == "")
1342                                 getWrappedLine_remaining = string_null;
1343                         return substring(s, 0, take);
1344                 }
1345         }
1346         else
1347         {
1348                 getWrappedLine_remaining = string_null;
1349                 return s;
1350         }
1351 }
1352
1353 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1354 {
1355         float cantake;
1356         float take;
1357         string s;
1358
1359         s = getWrappedLine_remaining;
1360
1361         cantake = textLengthUpToLength(s, w, tw);
1362         if(cantake > 0 && cantake < strlen(s))
1363         {
1364                 take = cantake - 1;
1365                 while(take > 0 && substring(s, take, 1) != " ")
1366                         --take;
1367                 if(take == 0)
1368                 {
1369                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1370                         if(getWrappedLine_remaining == "")
1371                                 getWrappedLine_remaining = string_null;
1372                         return substring(s, 0, cantake);
1373                 }
1374                 else
1375                 {
1376                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1377                         if(getWrappedLine_remaining == "")
1378                                 getWrappedLine_remaining = string_null;
1379                         return substring(s, 0, take);
1380                 }
1381         }
1382         else
1383         {
1384                 getWrappedLine_remaining = string_null;
1385                 return s;
1386         }
1387 }
1388
1389 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1390 {
1391         if(tw(theText, theFontSize) <= maxWidth)
1392                 return theText;
1393         else
1394                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1395 }
1396
1397 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1398 {
1399         if(tw(theText) <= maxWidth)
1400                 return theText;
1401         else
1402                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1403 }
1404
1405 float isGametypeInFilter(float gt, float tp, string pattern)
1406 {
1407         string subpattern, subpattern2, subpattern3;
1408         subpattern = strcat(",", GametypeNameFromType(gt), ",");
1409         if(tp)
1410                 subpattern2 = ",teams,";
1411         else
1412                 subpattern2 = ",noteams,";
1413         if(gt == GAME_RACE || gt == GAME_CTS)
1414                 subpattern3 = ",race,";
1415         else
1416                 subpattern3 = string_null;
1417
1418         if(substring(pattern, 0, 1) == "-")
1419         {
1420                 pattern = substring(pattern, 1, strlen(pattern) - 1);
1421                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1422                         return 0;
1423                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1424                         return 0;
1425                 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1426                         return 0;
1427         }
1428         else
1429         {
1430                 if(substring(pattern, 0, 1) == "+")
1431                         pattern = substring(pattern, 1, strlen(pattern) - 1);
1432                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1433                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1434                 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1435                         return 0;
1436         }
1437         return 1;
1438 }
1439
1440 void shuffle(float n, swapfunc_t swap, entity pass)
1441 {
1442         float i, j;
1443         for(i = 1; i < n; ++i)
1444         {
1445                 // swap i-th item at a random position from 0 to i
1446                 // proof for even distribution:
1447                 //   n = 1: obvious
1448                 //   n -> n+1:
1449                 //     item n+1 gets at any position with chance 1/(n+1)
1450                 //     all others will get their 1/n chance reduced by factor n/(n+1)
1451                 //     to be on place n+1, their chance will be 1/(n+1)
1452                 //     1/n * n/(n+1) = 1/(n+1)
1453                 //     q.e.d.
1454                 j = floor(random() * (i + 1));
1455                 if(j != i)
1456                         swap(j, i, pass);
1457         }
1458 }
1459
1460 string substring_range(string s, float b, float e)
1461 {
1462         return substring(s, b, e - b);
1463 }
1464
1465 string swapwords(string str, float i, float j)
1466 {
1467         float n;
1468         string s1, s2, s3, s4, s5;
1469         float si, ei, sj, ej, s0, en;
1470         n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1471         si = argv_start_index(i);
1472         sj = argv_start_index(j);
1473         ei = argv_end_index(i);
1474         ej = argv_end_index(j);
1475         s0 = argv_start_index(0);
1476         en = argv_end_index(n-1);
1477         s1 = substring_range(str, s0, si);
1478         s2 = substring_range(str, si, ei);
1479         s3 = substring_range(str, ei, sj);
1480         s4 = substring_range(str, sj, ej);
1481         s5 = substring_range(str, ej, en);
1482         return strcat(s1, s4, s3, s2, s5);
1483 }
1484
1485 string _shufflewords_str;
1486 void _shufflewords_swapfunc(float i, float j, entity pass)
1487 {
1488         _shufflewords_str = swapwords(_shufflewords_str, i, j);
1489 }
1490 string shufflewords(string str)
1491 {
1492         float n;
1493         _shufflewords_str = str;
1494         n = tokenizebyseparator(str, " ");
1495         shuffle(n, _shufflewords_swapfunc, world);
1496         str = _shufflewords_str;
1497         _shufflewords_str = string_null;
1498         return str;
1499 }
1500
1501 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1502 {
1503         vector v;
1504         float D;
1505         v = '0 0 0';
1506         if(a == 0)
1507         {
1508                 if(b != 0)
1509                 {
1510                         v_x = v_y = -c / b;
1511                         v_z = 1;
1512                 }
1513                 else
1514                 {
1515                         if(c == 0)
1516                         {
1517                                 // actually, every number solves the equation!
1518                                 v_z = 1;
1519                         }
1520                 }
1521         }
1522         else
1523         {
1524                 D = b*b - 4*a*c;
1525                 if(D >= 0)
1526                 {
1527                         D = sqrt(D);
1528                         if(a > 0) // put the smaller solution first
1529                         {
1530                                 v_x = ((-b)-D) / (2*a);
1531                                 v_y = ((-b)+D) / (2*a);
1532                         }
1533                         else
1534                         {
1535                                 v_x = (-b+D) / (2*a);
1536                                 v_y = (-b-D) / (2*a);
1537                         }
1538                         v_z = 1;
1539                 }
1540                 else
1541                 {
1542                         // complex solutions!
1543                         D = sqrt(-D);
1544                         v_x = -b / (2*a);
1545                         if(a > 0)
1546                                 v_y =  D / (2*a);
1547                         else
1548                                 v_y = -D / (2*a);
1549                         v_z = 0;
1550                 }
1551         }
1552         return v;
1553 }
1554
1555
1556 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1557 float _unacceptable_compiler_bug_1_b() { return 1; }
1558 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1559 float _unacceptable_compiler_bug_1_d() { return 1; }
1560
1561 void check_unacceptable_compiler_bugs()
1562 {
1563         if(cvar("_allow_unacceptable_compiler_bugs"))
1564                 return;
1565         tokenize_console("foo bar");
1566         if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1567                 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.");
1568 }
1569
1570 float compressShotOrigin(vector v)
1571 {
1572         float x, y, z;
1573         x = rint(v_x * 2);
1574         y = rint(v_y * 4) + 128;
1575         z = rint(v_z * 4) + 128;
1576         if(x > 255 || x < 0)
1577         {
1578                 print("shot origin ", vtos(v), " x out of bounds\n");
1579                 x = bound(0, x, 255);
1580         }
1581         if(y > 255 || y < 0)
1582         {
1583                 print("shot origin ", vtos(v), " y out of bounds\n");
1584                 y = bound(0, y, 255);
1585         }
1586         if(z > 255 || z < 0)
1587         {
1588                 print("shot origin ", vtos(v), " z out of bounds\n");
1589                 z = bound(0, z, 255);
1590         }
1591         return x * 0x10000 + y * 0x100 + z;
1592 }
1593 vector decompressShotOrigin(float f)
1594 {
1595         vector v;
1596         v_x = ((f & 0xFF0000) / 0x10000) / 2;
1597         v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1598         v_z = ((f & 0xFF) - 128) / 4;
1599         return v;
1600 }
1601
1602 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1603 {
1604         float start, end, root, child;
1605
1606         // heapify
1607         start = floor((n - 2) / 2);
1608         while(start >= 0)
1609         {
1610                 // siftdown(start, count-1);
1611                 root = start;
1612                 while(root * 2 + 1 <= n-1)
1613                 {
1614                         child = root * 2 + 1;
1615                         if(child < n-1)
1616                                 if(cmp(child, child+1, pass) < 0)
1617                                         ++child;
1618                         if(cmp(root, child, pass) < 0)
1619                         {
1620                                 swap(root, child, pass);
1621                                 root = child;
1622                         }
1623                         else
1624                                 break;
1625                 }
1626                 // end of siftdown
1627                 --start;
1628         }
1629
1630         // extract
1631         end = n - 1;
1632         while(end > 0)
1633         {
1634                 swap(0, end, pass);
1635                 --end;
1636                 // siftdown(0, end);
1637                 root = 0;
1638                 while(root * 2 + 1 <= end)
1639                 {
1640                         child = root * 2 + 1;
1641                         if(child < end && cmp(child, child+1, pass) < 0)
1642                                 ++child;
1643                         if(cmp(root, child, pass) < 0)
1644                         {
1645                                 swap(root, child, pass);
1646                                 root = child;
1647                         }
1648                         else
1649                                 break;
1650                 }
1651                 // end of siftdown
1652         }
1653 }
1654
1655 void RandomSelection_Init()
1656 {
1657         RandomSelection_totalweight = 0;
1658         RandomSelection_chosen_ent = world;
1659         RandomSelection_chosen_float = 0;
1660         RandomSelection_chosen_string = string_null;
1661         RandomSelection_best_priority = -1;
1662 }
1663 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1664 {
1665         if(priority > RandomSelection_best_priority)
1666         {
1667                 RandomSelection_best_priority = priority;
1668                 RandomSelection_chosen_ent = e;
1669                 RandomSelection_chosen_float = f;
1670                 RandomSelection_chosen_string = s;
1671                 RandomSelection_totalweight = weight;
1672         }
1673         else if(priority == RandomSelection_best_priority)
1674         {
1675                 RandomSelection_totalweight += weight;
1676                 if(random() * RandomSelection_totalweight <= weight)
1677                 {
1678                         RandomSelection_chosen_ent = e;
1679                         RandomSelection_chosen_float = f;
1680                         RandomSelection_chosen_string = s;
1681                 }
1682         }
1683 }
1684
1685 vector healtharmor_maxdamage(float h, float a, float armorblock)
1686 {
1687         // NOTE: we'll always choose the SMALLER value...
1688         float healthdamage, armordamage, armorideal;
1689         vector v;
1690         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1691         armordamage = a + (h - 1); // damage we can take if we could use more armor
1692         armorideal = healthdamage * armorblock;
1693         v_y = armorideal;
1694         if(armordamage < healthdamage)
1695         {
1696                 v_x = armordamage;
1697                 v_z = 1;
1698         }
1699         else
1700         {
1701                 v_x = healthdamage;
1702                 v_z = 0;
1703         }
1704         return v;
1705 }
1706
1707 vector healtharmor_applydamage(float a, float armorblock, float damage)
1708 {
1709         vector v;
1710         v_y = bound(0, damage * armorblock, a); // save
1711         v_x = bound(0, damage - v_y, damage); // take
1712         v_z = 0;
1713         return v;
1714 }
1715
1716 string getcurrentmod()
1717 {
1718         float n;
1719         string m;
1720         m = cvar_string("fs_gamedir");
1721         n = tokenize_console(m);
1722         if(n == 0)
1723                 return "data";
1724         else
1725                 return argv(n - 1);
1726 }
1727
1728 #ifndef MENUQC
1729 #ifdef CSQC
1730 float ReadInt24_t()
1731 {
1732         float v;
1733         v = ReadShort() * 256; // note: this is signed
1734         v += ReadByte(); // note: this is unsigned
1735         return v;
1736 }
1737 #else
1738 void WriteInt24_t(float dest, float val)
1739 {
1740         float v;
1741         WriteShort(dest, (v = floor(val / 256)));
1742         WriteByte(dest, val - v * 256); // 0..255
1743 }
1744 #endif
1745 #endif
1746
1747 float float2range11(float f)
1748 {
1749         // continuous function mapping all reals into -1..1
1750         return f / (fabs(f) + 1);
1751 }
1752
1753 float float2range01(float f)
1754 {
1755         // continuous function mapping all reals into 0..1
1756         return 0.5 + 0.5 * float2range11(f);
1757 }
1758
1759 // from the GNU Scientific Library
1760 float gsl_ran_gaussian_lastvalue;
1761 float gsl_ran_gaussian_lastvalue_set;
1762 float gsl_ran_gaussian(float sigma)
1763 {
1764         float a, b;
1765         if(gsl_ran_gaussian_lastvalue_set)
1766         {
1767                 gsl_ran_gaussian_lastvalue_set = 0;
1768                 return sigma * gsl_ran_gaussian_lastvalue;
1769         }
1770         else
1771         {
1772                 a = random() * 2 * M_PI;
1773                 b = sqrt(-2 * log(random()));
1774                 gsl_ran_gaussian_lastvalue = cos(a) * b;
1775                 gsl_ran_gaussian_lastvalue_set = 1;
1776                 return sigma * sin(a) * b;
1777         }
1778 }
1779
1780 string car(string s)
1781 {
1782         float o;
1783         o = strstrofs(s, " ", 0);
1784         if(o < 0)
1785                 return s;
1786         return substring(s, 0, o);
1787 }
1788 string cdr(string s)
1789 {
1790         float o;
1791         o = strstrofs(s, " ", 0);
1792         if(o < 0)
1793                 return string_null;
1794         return substring(s, o + 1, strlen(s) - (o + 1));
1795 }
1796 float matchacl(string acl, string str)
1797 {
1798         string t, s;
1799         float r, d;
1800         r = 0;
1801         while(acl)
1802         {
1803                 t = car(acl); acl = cdr(acl);
1804                 d = 1;
1805                 if(substring(t, 0, 1) == "-")
1806                 {
1807                         d = -1;
1808                         t = substring(t, 1, strlen(t) - 1);
1809                 }
1810                 else if(substring(t, 0, 1) == "+")
1811                         t = substring(t, 1, strlen(t) - 1);
1812                 if(substring(t, -1, 1) == "*")
1813                 {
1814                         t = substring(t, 0, strlen(t) - 1);
1815                         s = substring(s, 0, strlen(t));
1816                 }
1817                 else
1818                         s = str;
1819
1820                 if(s == t)
1821                 {
1822                         r = d;
1823                 }
1824         }
1825         return r;
1826 }
1827 float startsWith(string haystack, string needle)
1828 {
1829         return substring(haystack, 0, strlen(needle)) == needle;
1830 }
1831 float startsWithNocase(string haystack, string needle)
1832 {
1833         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1834 }
1835
1836 string get_model_datafilename(string m, float sk, string fil)
1837 {
1838         if(m)
1839                 m = strcat(m, "_");
1840         else
1841                 m = "models/player/*_";
1842         if(sk >= 0)
1843                 m = strcat(m, ftos(sk));
1844         else
1845                 m = strcat(m, "*");
1846         return strcat(m, ".", fil);
1847 }
1848
1849 float get_model_parameters(string m, float sk)
1850 {
1851         string fn, s, c;
1852         float fh;
1853
1854         get_model_parameters_modelname = string_null;
1855         get_model_parameters_modelskin = -1;
1856         get_model_parameters_name = string_null;
1857         get_model_parameters_species = -1;
1858         get_model_parameters_sex = string_null;
1859         get_model_parameters_weight = -1;
1860         get_model_parameters_age = -1;
1861         get_model_parameters_desc = string_null;
1862
1863         if not(m)
1864                 return 1;
1865         if(sk < 0)
1866         {
1867                 if(substring(m, -4, -1) != ".txt")
1868                         return 0;
1869                 if(substring(m, -6, 1) != "_")
1870                         return 0;
1871                 sk = stof(substring(m, -5, 1));
1872                 m = substring(m, 0, -7);
1873         }
1874
1875         fn = get_model_datafilename(m, sk, "txt");
1876         fh = fopen(fn, FILE_READ);
1877         if(fh < 0)
1878                 return 0;
1879
1880         get_model_parameters_modelname = m;
1881         get_model_parameters_modelskin = sk;
1882         while((s = fgets(fh)))
1883         {
1884                 if(s == "")
1885                         break; // next lines will be description
1886                 c = car(s);
1887                 s = cdr(s);
1888                 if(c == "name")
1889                         get_model_parameters_name = s;
1890                 if(c == "species")
1891                         switch(s)
1892                         {
1893                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
1894                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
1895                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1896                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1897                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1898                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
1899                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
1900                         }
1901                 if(c == "sex")
1902                         get_model_parameters_sex = s;
1903                 if(c == "weight")
1904                         get_model_parameters_weight = stof(s);
1905                 if(c == "age")
1906                         get_model_parameters_age = stof(s);
1907         }
1908
1909         while((s = fgets(fh)))
1910         {
1911                 if(get_model_parameters_desc)
1912                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1913                 if(s != "")
1914                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1915         }
1916
1917         fclose(fh);
1918
1919         return 1;
1920 }
1921
1922 vector vec2(vector v)
1923 {
1924         v_z = 0;
1925         return v;
1926 }
1927
1928 #ifndef MENUQC
1929 vector NearestPointOnBox(entity box, vector org)
1930 {
1931         vector m1, m2, nearest;
1932
1933         m1 = box.mins + box.origin;
1934         m2 = box.maxs + box.origin;
1935
1936         nearest_x = bound(m1_x, org_x, m2_x);
1937         nearest_y = bound(m1_y, org_y, m2_y);
1938         nearest_z = bound(m1_z, org_z, m2_z);
1939
1940         return nearest;
1941 }
1942 #endif
1943
1944 float vercmp_recursive(string v1, string v2)
1945 {
1946         float dot1, dot2;
1947         string s1, s2;
1948         float r;
1949
1950         dot1 = strstrofs(v1, ".", 0);
1951         dot2 = strstrofs(v2, ".", 0);
1952         if(dot1 == -1)
1953                 s1 = v1;
1954         else
1955                 s1 = substring(v1, 0, dot1);
1956         if(dot2 == -1)
1957                 s2 = v2;
1958         else
1959                 s2 = substring(v2, 0, dot2);
1960
1961         r = stof(s1) - stof(s2);
1962         if(r != 0)
1963                 return r;
1964
1965         r = strcasecmp(s1, s2);
1966         if(r != 0)
1967                 return r;
1968
1969         if(dot1 == -1)
1970                 if(dot2 == -1)
1971                         return 0;
1972                 else
1973                         return -1;
1974         else
1975                 if(dot2 == -1)
1976                         return 1;
1977                 else
1978                         return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1979 }
1980
1981 float vercmp(string v1, string v2)
1982 {
1983         if(strcasecmp(v1, v2) == 0) // early out check
1984                 return 0;
1985
1986         // "git" beats all
1987         if(v1 == "git")
1988                 return 1;
1989         if(v2 == "git")
1990                 return -1;
1991
1992         return vercmp_recursive(v1, v2);
1993 }