]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
fix starting the gametype
[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         else if (g == GAME_FREEZETAG) return "freezetag";
443         return "dm";
444 }
445
446 string mmsss(float tenths)
447 {
448         float minutes;
449         string s;
450         tenths = floor(tenths + 0.5);
451         minutes = floor(tenths / 600);
452         tenths -= minutes * 600;
453         s = ftos(1000 + tenths);
454         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
455 }
456
457 string mmssss(float hundredths)
458 {
459         float minutes;
460         string s;
461         hundredths = floor(hundredths + 0.5);
462         minutes = floor(hundredths / 6000);
463         hundredths -= minutes * 6000;
464         s = ftos(10000 + hundredths);
465         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
466 }
467
468 string ScoreString(float pFlags, float pValue)
469 {
470         string valstr;
471         float l;
472
473         pValue = floor(pValue + 0.5); // round
474
475         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
476                 valstr = "";
477         else if(pFlags & SFL_RANK)
478         {
479                 valstr = ftos(pValue);
480                 l = strlen(valstr);
481                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
482                         valstr = strcat(valstr, "th");
483                 else if(substring(valstr, l - 1, 1) == "1")
484                         valstr = strcat(valstr, "st");
485                 else if(substring(valstr, l - 1, 1) == "2")
486                         valstr = strcat(valstr, "nd");
487                 else if(substring(valstr, l - 1, 1) == "3")
488                         valstr = strcat(valstr, "rd");
489                 else
490                         valstr = strcat(valstr, "th");
491         }
492         else if(pFlags & SFL_TIME)
493                 valstr = TIME_ENCODED_TOSTRING(pValue);
494         else
495                 valstr = ftos(pValue);
496         
497         return valstr;
498 }
499
500 vector cross(vector a, vector b)
501 {
502         return
503                 '1 0 0' * (a_y * b_z - a_z * b_y)
504         +       '0 1 0' * (a_z * b_x - a_x * b_z)
505         +       '0 0 1' * (a_x * b_y - a_y * b_x);
506 }
507
508 // compressed vector format:
509 // like MD3, just even shorter
510 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
511 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
512 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
513 //     length = 2^(length_encoded/8) / 8
514 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
515 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
516 // the special value 0 indicates the zero vector
517
518 float lengthLogTable[128];
519
520 float invertLengthLog(float x)
521 {
522         float l, r, m, lerr, rerr;
523
524         if(x >= lengthLogTable[127])
525                 return 127;
526         if(x <= lengthLogTable[0])
527                 return 0;
528
529         l = 0;
530         r = 127;
531
532         while(r - l > 1)
533         {
534                 m = floor((l + r) / 2);
535                 if(lengthLogTable[m] < x)
536                         l = m;
537                 else
538                         r = m;
539         }
540
541         // now: r is >=, l is <
542         lerr = (x - lengthLogTable[l]);
543         rerr = (lengthLogTable[r] - x);
544         if(lerr < rerr)
545                 return l;
546         return r;
547 }
548
549 vector decompressShortVector(float data)
550 {
551         vector out;
552         float pitch, yaw, len;
553         if(data == 0)
554                 return '0 0 0';
555         pitch = (data & 0xF000) / 0x1000;
556         yaw =   (data & 0x0F80) / 0x80;
557         len =   (data & 0x007F);
558
559         //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
560
561         if(pitch == 0)
562         {
563                 out_x = 0;
564                 out_y = 0;
565                 if(yaw == 31)
566                         out_z = -1;
567                 else
568                         out_z = +1;
569         }
570         else
571         {
572                 yaw   = .19634954084936207740 * yaw;
573                 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
574                 out_x = cos(yaw) *  cos(pitch);
575                 out_y = sin(yaw) *  cos(pitch);
576                 out_z =            -sin(pitch);
577         }
578
579         //print("decompressed: ", vtos(out), "\n");
580
581         return out * lengthLogTable[len];
582 }
583
584 float compressShortVector(vector vec)
585 {
586         vector ang;
587         float pitch, yaw, len;
588         if(vlen(vec) == 0)
589                 return 0;
590         //print("compress: ", vtos(vec), "\n");
591         ang = vectoangles(vec);
592         ang_x = -ang_x;
593         if(ang_x < -90)
594                 ang_x += 360;
595         if(ang_x < -90 && ang_x > +90)
596                 error("BOGUS vectoangles");
597         //print("angles: ", vtos(ang), "\n");
598
599         pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
600         if(pitch == 0)
601         {
602                 if(vec_z < 0)
603                         yaw = 31;
604                 else
605                         yaw = 30;
606         }
607         else
608                 yaw = floor(0.5 + ang_y * 32 / 360)          & 31; // 0..360 to 0..32
609         len = invertLengthLog(vlen(vec));
610
611         //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
612
613         return (pitch * 0x1000) + (yaw * 0x80) + len;
614 }
615
616 void compressShortVector_init()
617 {
618         float l, f, i;
619         l = 1;
620         f = pow(2, 1/8);
621         for(i = 0; i < 128; ++i)
622         {
623                 lengthLogTable[i] = l;
624                 l *= f;
625         }
626
627         if(cvar("developer"))
628         {
629                 print("Verifying vector compression table...\n");
630                 for(i = 0x0F00; i < 0xFFFF; ++i)
631                         if(i != compressShortVector(decompressShortVector(i)))
632                         {
633                                 print("BROKEN vector compression: ", ftos(i));
634                                 print(" -> ", vtos(decompressShortVector(i)));
635                                 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
636                                 print("\n");
637                                 error("b0rk");
638                         }
639                 print("Done.\n");
640         }
641 }
642
643 #ifndef MENUQC
644 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
645 {
646         traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
647         traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
648         traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
649         traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
650         traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
651         traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
652         traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
653         traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
654         traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
655         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
656         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
657         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
658         return 1;
659 }
660 #endif
661
662 string fixPriorityList(string order, float from, float to, float subtract, float complete)
663 {
664         string neworder;
665         float i, n, w;
666
667         n = tokenize_console(order);
668         neworder = "";
669         for(i = 0; i < n; ++i)
670         {
671                 w = stof(argv(i));
672                 if(w == floor(w))
673                 {
674                         if(w >= from && w <= to)
675                                 neworder = strcat(neworder, ftos(w), " ");
676                         else
677                         {
678                                 w -= subtract;
679                                 if(w >= from && w <= to)
680                                         neworder = strcat(neworder, ftos(w), " ");
681                         }
682                 }
683         }
684
685         if(complete)
686         {
687                 n = tokenize_console(neworder);
688                 for(w = to; w >= from; --w)
689                 {
690                         for(i = 0; i < n; ++i)
691                                 if(stof(argv(i)) == w)
692                                         break;
693                         if(i == n) // not found
694                                 neworder = strcat(neworder, ftos(w), " ");
695                 }
696         }
697         
698         return substring(neworder, 0, strlen(neworder) - 1);
699 }
700
701 string mapPriorityList(string order, string(string) mapfunc)
702 {
703         string neworder;
704         float i, n;
705
706         n = tokenize_console(order);
707         neworder = "";
708         for(i = 0; i < n; ++i)
709                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
710         
711         return substring(neworder, 0, strlen(neworder) - 1);
712 }
713
714 string swapInPriorityList(string order, float i, float j)
715 {
716         string s;
717         float w, n;
718
719         n = tokenize_console(order);
720
721         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
722         {
723                 s = "";
724                 for(w = 0; w < n; ++w)
725                 {
726                         if(w == i)
727                                 s = strcat(s, argv(j), " ");
728                         else if(w == j)
729                                 s = strcat(s, argv(i), " ");
730                         else
731                                 s = strcat(s, argv(w), " ");
732                 }
733                 return substring(s, 0, strlen(s) - 1);
734         }
735         
736         return order;
737 }
738
739 float cvar_value_issafe(string s)
740 {
741         if(strstrofs(s, "\"", 0) >= 0)
742                 return 0;
743         if(strstrofs(s, "\\", 0) >= 0)
744                 return 0;
745         if(strstrofs(s, ";", 0) >= 0)
746                 return 0;
747         if(strstrofs(s, "$", 0) >= 0)
748                 return 0;
749         if(strstrofs(s, "\r", 0) >= 0)
750                 return 0;
751         if(strstrofs(s, "\n", 0) >= 0)
752                 return 0;
753         return 1;
754 }
755
756 #ifndef MENUQC
757 void get_mi_min_max(float mode)
758 {
759         vector mi, ma;
760
761         if(mi_shortname)
762                 strunzone(mi_shortname);
763         mi_shortname = mapname;
764         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
765                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
766         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
767                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
768         mi_shortname = strzone(mi_shortname);
769
770 #ifdef CSQC
771         mi = world.mins;
772         ma = world.maxs;
773 #else
774         mi = world.absmin;
775         ma = world.absmax;
776 #endif
777
778         mi_min = mi;
779         mi_max = ma;
780         MapInfo_Get_ByName(mi_shortname, 0, 0);
781         if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
782         {
783                 mi_min = MapInfo_Map_mins;
784                 mi_max = MapInfo_Map_maxs;
785         }
786         else
787         {
788                 // not specified
789                 if(mode)
790                 {
791                         // be clever
792                         tracebox('1 0 0' * mi_x,
793                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
794                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
795                                          '1 0 0' * ma_x,
796                                          MOVE_WORLDONLY,
797                                          world);
798                         if(!trace_startsolid)
799                                 mi_min_x = trace_endpos_x;
800
801                         tracebox('0 1 0' * mi_y,
802                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
803                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
804                                          '0 1 0' * ma_y,
805                                          MOVE_WORLDONLY,
806                                          world);
807                         if(!trace_startsolid)
808                                 mi_min_y = trace_endpos_y;
809
810                         tracebox('0 0 1' * mi_z,
811                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
812                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
813                                          '0 0 1' * ma_z,
814                                          MOVE_WORLDONLY,
815                                          world);
816                         if(!trace_startsolid)
817                                 mi_min_z = trace_endpos_z;
818
819                         tracebox('1 0 0' * ma_x,
820                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
821                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
822                                          '1 0 0' * mi_x,
823                                          MOVE_WORLDONLY,
824                                          world);
825                         if(!trace_startsolid)
826                                 mi_max_x = trace_endpos_x;
827
828                         tracebox('0 1 0' * ma_y,
829                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
830                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
831                                          '0 1 0' * mi_y,
832                                          MOVE_WORLDONLY,
833                                          world);
834                         if(!trace_startsolid)
835                                 mi_max_y = trace_endpos_y;
836
837                         tracebox('0 0 1' * ma_z,
838                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
839                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
840                                          '0 0 1' * mi_z,
841                                          MOVE_WORLDONLY,
842                                          world);
843                         if(!trace_startsolid)
844                                 mi_max_z = trace_endpos_z;
845                 }
846         }
847 }
848
849 void get_mi_min_max_texcoords(float mode)
850 {
851         vector extend;
852
853         get_mi_min_max(mode);
854
855         mi_picmin = mi_min;
856         mi_picmax = mi_max;
857
858         // extend mi_picmax to get a square aspect ratio
859         // center the map in that area
860         extend = mi_picmax - mi_picmin;
861         if(extend_y > extend_x)
862         {
863                 mi_picmin_x -= (extend_y - extend_x) * 0.5;
864                 mi_picmax_x += (extend_y - extend_x) * 0.5;
865         }
866         else
867         {
868                 mi_picmin_y -= (extend_x - extend_y) * 0.5;
869                 mi_picmax_y += (extend_x - extend_y) * 0.5;
870         }
871
872         // add another some percent
873         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
874         mi_picmin -= extend;
875         mi_picmax += extend;
876
877         // calculate the texcoords
878         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
879         // first the two corners of the origin
880         mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
881         mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
882         mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
883         mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
884         // then the other corners
885         mi_pictexcoord1_x = mi_pictexcoord0_x;
886         mi_pictexcoord1_y = mi_pictexcoord2_y;
887         mi_pictexcoord3_x = mi_pictexcoord2_x;
888         mi_pictexcoord3_y = mi_pictexcoord0_y;
889 }
890 #endif
891
892 #ifdef CSQC
893 void cvar_settemp(string pKey, string pValue)
894 {
895         error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
896 }
897 void cvar_settemp_restore()
898 {
899         error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
900 }
901 #else
902 void cvar_settemp(string pKey, string pValue)
903 {
904         float i;
905         string settemp_var;
906         if(cvar_string(pKey) == pValue)
907                 return;
908         i = cvar("settemp_idx");
909         cvar_set("settemp_idx", ftos(i+1));
910         settemp_var = strcat("_settemp_x", ftos(i));
911 #ifdef MENUQC
912         registercvar(settemp_var, "", 0);
913 #else
914         registercvar(settemp_var, "");
915 #endif
916         cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
917         cvar_set(settemp_var, cvar_string(pKey));
918         cvar_set(pKey, pValue);
919 }
920
921 void cvar_settemp_restore()
922 {
923         // undo what cvar_settemp did
924         float n, i;
925         n = tokenize_console(cvar_string("settemp_list"));
926         for(i = 0; i < n - 3; i += 3)
927                 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
928         cvar_set("settemp_list", "0");
929 }
930 #endif
931
932 float almost_equals(float a, float b)
933 {
934         float eps;
935         eps = (max(a, -a) + max(b, -b)) * 0.001;
936         if(a - b < eps && b - a < eps)
937                 return TRUE;
938         return FALSE;
939 }
940
941 float almost_in_bounds(float a, float b, float c)
942 {
943         float eps;
944         eps = (max(a, -a) + max(c, -c)) * 0.001;
945         return b == median(a - eps, b, c + eps);
946 }
947
948 float power2of(float e)
949 {
950         return pow(2, e);
951 }
952 float log2of(float x)
953 {
954         // NOTE: generated code
955         if(x > 2048)
956                 if(x > 131072)
957                         if(x > 1048576)
958                                 if(x > 4194304)
959                                         return 23;
960                                 else
961                                         if(x > 2097152)
962                                                 return 22;
963                                         else
964                                                 return 21;
965                         else
966                                 if(x > 524288)
967                                         return 20;
968                                 else
969                                         if(x > 262144)
970                                                 return 19;
971                                         else
972                                                 return 18;
973                 else
974                         if(x > 16384)
975                                 if(x > 65536)
976                                         return 17;
977                                 else
978                                         if(x > 32768)
979                                                 return 16;
980                                         else
981                                                 return 15;
982                         else
983                                 if(x > 8192)
984                                         return 14;
985                                 else
986                                         if(x > 4096)
987                                                 return 13;
988                                         else
989                                                 return 12;
990         else
991                 if(x > 32)
992                         if(x > 256)
993                                 if(x > 1024)
994                                         return 11;
995                                 else
996                                         if(x > 512)
997                                                 return 10;
998                                         else
999                                                 return 9;
1000                         else
1001                                 if(x > 128)
1002                                         return 8;
1003                                 else
1004                                         if(x > 64)
1005                                                 return 7;
1006                                         else
1007                                                 return 6;
1008                 else
1009                         if(x > 4)
1010                                 if(x > 16)
1011                                         return 5;
1012                                 else
1013                                         if(x > 8)
1014                                                 return 4;
1015                                         else
1016                                                 return 3;
1017                         else
1018                                 if(x > 2)
1019                                         return 2;
1020                                 else
1021                                         if(x > 1)
1022                                                 return 1;
1023                                         else
1024                                                 return 0;
1025 }
1026
1027 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1028 {
1029         if(mi == ma)
1030                 return 0;
1031         else if(ma == rgb_x)
1032         {
1033                 if(rgb_y >= rgb_z)
1034                         return (rgb_y - rgb_z) / (ma - mi);
1035                 else
1036                         return (rgb_y - rgb_z) / (ma - mi) + 6;
1037         }
1038         else if(ma == rgb_y)
1039                 return (rgb_z - rgb_x) / (ma - mi) + 2;
1040         else // if(ma == rgb_z)
1041                 return (rgb_x - rgb_y) / (ma - mi) + 4;
1042 }
1043
1044 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1045 {
1046         vector rgb;
1047
1048         hue -= 6 * floor(hue / 6);
1049
1050         //else if(ma == rgb_x)
1051         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1052         if(hue <= 1)
1053         {
1054                 rgb_x = ma;
1055                 rgb_y = hue * (ma - mi) + mi;
1056                 rgb_z = mi;
1057         }
1058         //else if(ma == rgb_y)
1059         //      hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1060         else if(hue <= 2)
1061         {
1062                 rgb_x = (2 - hue) * (ma - mi) + mi;
1063                 rgb_y = ma;
1064                 rgb_z = mi;
1065         }
1066         else if(hue <= 3)
1067         {
1068                 rgb_x = mi;
1069                 rgb_y = ma;
1070                 rgb_z = (hue - 2) * (ma - mi) + mi;
1071         }
1072         //else // if(ma == rgb_z)
1073         //      hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1074         else if(hue <= 4)
1075         {
1076                 rgb_x = mi;
1077                 rgb_y = (4 - hue) * (ma - mi) + mi;
1078                 rgb_z = ma;
1079         }
1080         else if(hue <= 5)
1081         {
1082                 rgb_x = (hue - 4) * (ma - mi) + mi;
1083                 rgb_y = mi;
1084                 rgb_z = ma;
1085         }
1086         //else if(ma == rgb_x)
1087         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1088         else // if(hue <= 6)
1089         {
1090                 rgb_x = ma;
1091                 rgb_y = mi;
1092                 rgb_z = (6 - hue) * (ma - mi) + mi;
1093         }
1094
1095         return rgb;
1096 }
1097
1098 vector rgb_to_hsv(vector rgb)
1099 {
1100         float mi, ma;
1101         vector hsv;
1102
1103         mi = min3(rgb_x, rgb_y, rgb_z);
1104         ma = max3(rgb_x, rgb_y, rgb_z);
1105
1106         hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1107         hsv_z = ma;
1108
1109         if(ma == 0)
1110                 hsv_y = 0;
1111         else
1112                 hsv_y = 1 - mi/ma;
1113         
1114         return hsv;
1115 }
1116
1117 vector hsv_to_rgb(vector hsv)
1118 {
1119         return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1120 }
1121
1122 vector rgb_to_hsl(vector rgb)
1123 {
1124         float mi, ma;
1125         vector hsl;
1126
1127         mi = min3(rgb_x, rgb_y, rgb_z);
1128         ma = max3(rgb_x, rgb_y, rgb_z);
1129
1130         hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1131         
1132         hsl_z = 0.5 * (mi + ma);
1133         if(mi == ma)
1134                 hsl_y = 0;
1135         else if(hsl_z <= 0.5)
1136                 hsl_y = (ma - mi) / (2*hsl_z);
1137         else // if(hsl_z > 0.5)
1138                 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1139         
1140         return hsl;
1141 }
1142
1143 vector hsl_to_rgb(vector hsl)
1144 {
1145         float mi, ma, maminusmi;
1146
1147         if(hsl_z <= 0.5)
1148                 maminusmi = hsl_y * 2 * hsl_z;
1149         else
1150                 maminusmi = hsl_y * (2 - 2 * hsl_z);
1151         
1152         // hsl_z     = 0.5 * mi + 0.5 * ma
1153         // maminusmi =     - mi +       ma
1154         mi = hsl_z - 0.5 * maminusmi;
1155         ma = hsl_z + 0.5 * maminusmi;
1156
1157         return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1158 }
1159
1160 string rgb_to_hexcolor(vector rgb)
1161 {
1162         return
1163                 strcat(
1164                         "^x",
1165                         DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1166                         DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1167                         DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1168                 );
1169 }
1170
1171 // requires that m2>m1 in all coordinates, and that m4>m3
1172 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;};
1173
1174 // requires the same, but is a stronger condition
1175 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;};
1176
1177 #ifndef MENUQC
1178 #endif
1179
1180 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1181 {
1182         float ICanHasKallerz;
1183
1184         // detect color codes support in the width function
1185         ICanHasKallerz = (w("^7", theSize) == 0);
1186
1187         // STOP.
1188         // The following function is SLOW.
1189         // For your safety and for the protection of those around you...
1190         // DO NOT CALL THIS AT HOME.
1191         // No really, don't.
1192         if(w(theText, theSize) <= maxWidth)
1193                 return strlen(theText); // yeah!
1194
1195         // binary search for right place to cut string
1196         float ch;
1197         float left, right, middle; // this always works
1198         left = 0;
1199         right = strlen(theText); // this always fails
1200         do
1201         {
1202                 middle = floor((left + right) / 2);
1203                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1204                         left = middle;
1205                 else
1206                         right = middle;
1207         }
1208         while(left < right - 1);
1209
1210         if(ICanHasKallerz)
1211         {
1212                 // NOTE: when color codes are involved, this binary search is,
1213                 // mathematically, BROKEN. However, it is obviously guaranteed to
1214                 // terminate, as the range still halves each time - but nevertheless, it is
1215                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1216                 // range, and "right" is outside).
1217                 
1218                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1219                 // and decrease left on the basis of the chars detected of the truncated tag
1220                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1221                 // (sometimes too much but with a correct result)
1222                 // it fixes also ^[0-9]
1223                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1224                         left-=1;
1225
1226                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1227                         left-=2;
1228                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1229                         {
1230                                 ch = str2chr(theText, left-1);
1231                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1232                                         left-=3;
1233                         }
1234                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1235                         {
1236                                 ch = str2chr(theText, left-2);
1237                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1238                                 {
1239                                         ch = str2chr(theText, left-1);
1240                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1241                                                 left-=4;
1242                                 }
1243                         }
1244         }
1245         
1246         return left;
1247 }
1248
1249 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1250 {
1251         float ICanHasKallerz;
1252
1253         // detect color codes support in the width function
1254         ICanHasKallerz = (w("^7") == 0);
1255
1256         // STOP.
1257         // The following function is SLOW.
1258         // For your safety and for the protection of those around you...
1259         // DO NOT CALL THIS AT HOME.
1260         // No really, don't.
1261         if(w(theText) <= maxWidth)
1262                 return strlen(theText); // yeah!
1263
1264         // binary search for right place to cut string
1265         float ch;
1266         float left, right, middle; // this always works
1267         left = 0;
1268         right = strlen(theText); // this always fails
1269         do
1270         {
1271                 middle = floor((left + right) / 2);
1272                 if(w(substring(theText, 0, middle)) <= maxWidth)
1273                         left = middle;
1274                 else
1275                         right = middle;
1276         }
1277         while(left < right - 1);
1278
1279         if(ICanHasKallerz)
1280         {
1281                 // NOTE: when color codes are involved, this binary search is,
1282                 // mathematically, BROKEN. However, it is obviously guaranteed to
1283                 // terminate, as the range still halves each time - but nevertheless, it is
1284                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1285                 // range, and "right" is outside).
1286                 
1287                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1288                 // and decrease left on the basis of the chars detected of the truncated tag
1289                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1290                 // (sometimes too much but with a correct result)
1291                 // it fixes also ^[0-9]
1292                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1293                         left-=1;
1294
1295                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1296                         left-=2;
1297                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1298                         {
1299                                 ch = str2chr(theText, left-1);
1300                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1301                                         left-=3;
1302                         }
1303                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1304                         {
1305                                 ch = str2chr(theText, left-2);
1306                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1307                                 {
1308                                         ch = str2chr(theText, left-1);
1309                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1310                                                 left-=4;
1311                                 }
1312                         }
1313         }
1314         
1315         return left;
1316 }
1317
1318 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1319 {
1320         float cantake;
1321         float take;
1322         string s;
1323
1324         s = getWrappedLine_remaining;
1325
1326         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1327         if(cantake > 0 && cantake < strlen(s))
1328         {
1329                 take = cantake - 1;
1330                 while(take > 0 && substring(s, take, 1) != " ")
1331                         --take;
1332                 if(take == 0)
1333                 {
1334                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1335                         if(getWrappedLine_remaining == "")
1336                                 getWrappedLine_remaining = string_null;
1337                         return substring(s, 0, cantake);
1338                 }
1339                 else
1340                 {
1341                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1342                         if(getWrappedLine_remaining == "")
1343                                 getWrappedLine_remaining = string_null;
1344                         return substring(s, 0, take);
1345                 }
1346         }
1347         else
1348         {
1349                 getWrappedLine_remaining = string_null;
1350                 return s;
1351         }
1352 }
1353
1354 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1355 {
1356         float cantake;
1357         float take;
1358         string s;
1359
1360         s = getWrappedLine_remaining;
1361
1362         cantake = textLengthUpToLength(s, w, tw);
1363         if(cantake > 0 && cantake < strlen(s))
1364         {
1365                 take = cantake - 1;
1366                 while(take > 0 && substring(s, take, 1) != " ")
1367                         --take;
1368                 if(take == 0)
1369                 {
1370                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1371                         if(getWrappedLine_remaining == "")
1372                                 getWrappedLine_remaining = string_null;
1373                         return substring(s, 0, cantake);
1374                 }
1375                 else
1376                 {
1377                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1378                         if(getWrappedLine_remaining == "")
1379                                 getWrappedLine_remaining = string_null;
1380                         return substring(s, 0, take);
1381                 }
1382         }
1383         else
1384         {
1385                 getWrappedLine_remaining = string_null;
1386                 return s;
1387         }
1388 }
1389
1390 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1391 {
1392         if(tw(theText, theFontSize) <= maxWidth)
1393                 return theText;
1394         else
1395                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1396 }
1397
1398 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1399 {
1400         if(tw(theText) <= maxWidth)
1401                 return theText;
1402         else
1403                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1404 }
1405
1406 float isGametypeInFilter(float gt, float tp, string pattern)
1407 {
1408         string subpattern, subpattern2, subpattern3;
1409         subpattern = strcat(",", GametypeNameFromType(gt), ",");
1410         if(tp)
1411                 subpattern2 = ",teams,";
1412         else
1413                 subpattern2 = ",noteams,";
1414         if(gt == GAME_RACE || gt == GAME_CTS)
1415                 subpattern3 = ",race,";
1416         else
1417                 subpattern3 = string_null;
1418
1419         if(substring(pattern, 0, 1) == "-")
1420         {
1421                 pattern = substring(pattern, 1, strlen(pattern) - 1);
1422                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1423                         return 0;
1424                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1425                         return 0;
1426                 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1427                         return 0;
1428         }
1429         else
1430         {
1431                 if(substring(pattern, 0, 1) == "+")
1432                         pattern = substring(pattern, 1, strlen(pattern) - 1);
1433                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1434                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1435                 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1436                         return 0;
1437         }
1438         return 1;
1439 }
1440
1441 void shuffle(float n, swapfunc_t swap, entity pass)
1442 {
1443         float i, j;
1444         for(i = 1; i < n; ++i)
1445         {
1446                 // swap i-th item at a random position from 0 to i
1447                 // proof for even distribution:
1448                 //   n = 1: obvious
1449                 //   n -> n+1:
1450                 //     item n+1 gets at any position with chance 1/(n+1)
1451                 //     all others will get their 1/n chance reduced by factor n/(n+1)
1452                 //     to be on place n+1, their chance will be 1/(n+1)
1453                 //     1/n * n/(n+1) = 1/(n+1)
1454                 //     q.e.d.
1455                 j = floor(random() * (i + 1));
1456                 if(j != i)
1457                         swap(j, i, pass);
1458         }
1459 }
1460
1461 string substring_range(string s, float b, float e)
1462 {
1463         return substring(s, b, e - b);
1464 }
1465
1466 string swapwords(string str, float i, float j)
1467 {
1468         float n;
1469         string s1, s2, s3, s4, s5;
1470         float si, ei, sj, ej, s0, en;
1471         n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1472         si = argv_start_index(i);
1473         sj = argv_start_index(j);
1474         ei = argv_end_index(i);
1475         ej = argv_end_index(j);
1476         s0 = argv_start_index(0);
1477         en = argv_end_index(n-1);
1478         s1 = substring_range(str, s0, si);
1479         s2 = substring_range(str, si, ei);
1480         s3 = substring_range(str, ei, sj);
1481         s4 = substring_range(str, sj, ej);
1482         s5 = substring_range(str, ej, en);
1483         return strcat(s1, s4, s3, s2, s5);
1484 }
1485
1486 string _shufflewords_str;
1487 void _shufflewords_swapfunc(float i, float j, entity pass)
1488 {
1489         _shufflewords_str = swapwords(_shufflewords_str, i, j);
1490 }
1491 string shufflewords(string str)
1492 {
1493         float n;
1494         _shufflewords_str = str;
1495         n = tokenizebyseparator(str, " ");
1496         shuffle(n, _shufflewords_swapfunc, world);
1497         str = _shufflewords_str;
1498         _shufflewords_str = string_null;
1499         return str;
1500 }
1501
1502 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1503 {
1504         vector v;
1505         float D;
1506         v = '0 0 0';
1507         if(a == 0)
1508         {
1509                 if(b != 0)
1510                 {
1511                         v_x = v_y = -c / b;
1512                         v_z = 1;
1513                 }
1514                 else
1515                 {
1516                         if(c == 0)
1517                         {
1518                                 // actually, every number solves the equation!
1519                                 v_z = 1;
1520                         }
1521                 }
1522         }
1523         else
1524         {
1525                 D = b*b - 4*a*c;
1526                 if(D >= 0)
1527                 {
1528                         D = sqrt(D);
1529                         if(a > 0) // put the smaller solution first
1530                         {
1531                                 v_x = ((-b)-D) / (2*a);
1532                                 v_y = ((-b)+D) / (2*a);
1533                         }
1534                         else
1535                         {
1536                                 v_x = (-b+D) / (2*a);
1537                                 v_y = (-b-D) / (2*a);
1538                         }
1539                         v_z = 1;
1540                 }
1541                 else
1542                 {
1543                         // complex solutions!
1544                         D = sqrt(-D);
1545                         v_x = -b / (2*a);
1546                         if(a > 0)
1547                                 v_y =  D / (2*a);
1548                         else
1549                                 v_y = -D / (2*a);
1550                         v_z = 0;
1551                 }
1552         }
1553         return v;
1554 }
1555
1556
1557 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1558 float _unacceptable_compiler_bug_1_b() { return 1; }
1559 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1560 float _unacceptable_compiler_bug_1_d() { return 1; }
1561
1562 void check_unacceptable_compiler_bugs()
1563 {
1564         if(cvar("_allow_unacceptable_compiler_bugs"))
1565                 return;
1566         tokenize_console("foo bar");
1567         if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1568                 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.");
1569 }
1570
1571 float compressShotOrigin(vector v)
1572 {
1573         float x, y, z;
1574         x = rint(v_x * 2);
1575         y = rint(v_y * 4) + 128;
1576         z = rint(v_z * 4) + 128;
1577         if(x > 255 || x < 0)
1578         {
1579                 print("shot origin ", vtos(v), " x out of bounds\n");
1580                 x = bound(0, x, 255);
1581         }
1582         if(y > 255 || y < 0)
1583         {
1584                 print("shot origin ", vtos(v), " y out of bounds\n");
1585                 y = bound(0, y, 255);
1586         }
1587         if(z > 255 || z < 0)
1588         {
1589                 print("shot origin ", vtos(v), " z out of bounds\n");
1590                 z = bound(0, z, 255);
1591         }
1592         return x * 0x10000 + y * 0x100 + z;
1593 }
1594 vector decompressShotOrigin(float f)
1595 {
1596         vector v;
1597         v_x = ((f & 0xFF0000) / 0x10000) / 2;
1598         v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1599         v_z = ((f & 0xFF) - 128) / 4;
1600         return v;
1601 }
1602
1603 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1604 {
1605         float start, end, root, child;
1606
1607         // heapify
1608         start = floor((n - 2) / 2);
1609         while(start >= 0)
1610         {
1611                 // siftdown(start, count-1);
1612                 root = start;
1613                 while(root * 2 + 1 <= n-1)
1614                 {
1615                         child = root * 2 + 1;
1616                         if(child < n-1)
1617                                 if(cmp(child, child+1, pass) < 0)
1618                                         ++child;
1619                         if(cmp(root, child, pass) < 0)
1620                         {
1621                                 swap(root, child, pass);
1622                                 root = child;
1623                         }
1624                         else
1625                                 break;
1626                 }
1627                 // end of siftdown
1628                 --start;
1629         }
1630
1631         // extract
1632         end = n - 1;
1633         while(end > 0)
1634         {
1635                 swap(0, end, pass);
1636                 --end;
1637                 // siftdown(0, end);
1638                 root = 0;
1639                 while(root * 2 + 1 <= end)
1640                 {
1641                         child = root * 2 + 1;
1642                         if(child < end && cmp(child, child+1, pass) < 0)
1643                                 ++child;
1644                         if(cmp(root, child, pass) < 0)
1645                         {
1646                                 swap(root, child, pass);
1647                                 root = child;
1648                         }
1649                         else
1650                                 break;
1651                 }
1652                 // end of siftdown
1653         }
1654 }
1655
1656 void RandomSelection_Init()
1657 {
1658         RandomSelection_totalweight = 0;
1659         RandomSelection_chosen_ent = world;
1660         RandomSelection_chosen_float = 0;
1661         RandomSelection_chosen_string = string_null;
1662         RandomSelection_best_priority = -1;
1663 }
1664 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1665 {
1666         if(priority > RandomSelection_best_priority)
1667         {
1668                 RandomSelection_best_priority = priority;
1669                 RandomSelection_chosen_ent = e;
1670                 RandomSelection_chosen_float = f;
1671                 RandomSelection_chosen_string = s;
1672                 RandomSelection_totalweight = weight;
1673         }
1674         else if(priority == RandomSelection_best_priority)
1675         {
1676                 RandomSelection_totalweight += weight;
1677                 if(random() * RandomSelection_totalweight <= weight)
1678                 {
1679                         RandomSelection_chosen_ent = e;
1680                         RandomSelection_chosen_float = f;
1681                         RandomSelection_chosen_string = s;
1682                 }
1683         }
1684 }
1685
1686 vector healtharmor_maxdamage(float h, float a, float armorblock)
1687 {
1688         // NOTE: we'll always choose the SMALLER value...
1689         float healthdamage, armordamage, armorideal;
1690         vector v;
1691         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1692         armordamage = a + (h - 1); // damage we can take if we could use more armor
1693         armorideal = healthdamage * armorblock;
1694         v_y = armorideal;
1695         if(armordamage < healthdamage)
1696         {
1697                 v_x = armordamage;
1698                 v_z = 1;
1699         }
1700         else
1701         {
1702                 v_x = healthdamage;
1703                 v_z = 0;
1704         }
1705         return v;
1706 }
1707
1708 vector healtharmor_applydamage(float a, float armorblock, float damage)
1709 {
1710         vector v;
1711         v_y = bound(0, damage * armorblock, a); // save
1712         v_x = bound(0, damage - v_y, damage); // take
1713         v_z = 0;
1714         return v;
1715 }
1716
1717 string getcurrentmod()
1718 {
1719         float n;
1720         string m;
1721         m = cvar_string("fs_gamedir");
1722         n = tokenize_console(m);
1723         if(n == 0)
1724                 return "data";
1725         else
1726                 return argv(n - 1);
1727 }
1728
1729 #ifndef MENUQC
1730 #ifdef CSQC
1731 float ReadInt24_t()
1732 {
1733         float v;
1734         v = ReadShort() * 256; // note: this is signed
1735         v += ReadByte(); // note: this is unsigned
1736         return v;
1737 }
1738 #else
1739 void WriteInt24_t(float dest, float val)
1740 {
1741         float v;
1742         WriteShort(dest, (v = floor(val / 256)));
1743         WriteByte(dest, val - v * 256); // 0..255
1744 }
1745 #endif
1746 #endif
1747
1748 float float2range11(float f)
1749 {
1750         // continuous function mapping all reals into -1..1
1751         return f / (fabs(f) + 1);
1752 }
1753
1754 float float2range01(float f)
1755 {
1756         // continuous function mapping all reals into 0..1
1757         return 0.5 + 0.5 * float2range11(f);
1758 }
1759
1760 // from the GNU Scientific Library
1761 float gsl_ran_gaussian_lastvalue;
1762 float gsl_ran_gaussian_lastvalue_set;
1763 float gsl_ran_gaussian(float sigma)
1764 {
1765         float a, b;
1766         if(gsl_ran_gaussian_lastvalue_set)
1767         {
1768                 gsl_ran_gaussian_lastvalue_set = 0;
1769                 return sigma * gsl_ran_gaussian_lastvalue;
1770         }
1771         else
1772         {
1773                 a = random() * 2 * M_PI;
1774                 b = sqrt(-2 * log(random()));
1775                 gsl_ran_gaussian_lastvalue = cos(a) * b;
1776                 gsl_ran_gaussian_lastvalue_set = 1;
1777                 return sigma * sin(a) * b;
1778         }
1779 }
1780
1781 string car(string s)
1782 {
1783         float o;
1784         o = strstrofs(s, " ", 0);
1785         if(o < 0)
1786                 return s;
1787         return substring(s, 0, o);
1788 }
1789 string cdr(string s)
1790 {
1791         float o;
1792         o = strstrofs(s, " ", 0);
1793         if(o < 0)
1794                 return string_null;
1795         return substring(s, o + 1, strlen(s) - (o + 1));
1796 }
1797 float matchacl(string acl, string str)
1798 {
1799         string t, s;
1800         float r, d;
1801         r = 0;
1802         while(acl)
1803         {
1804                 t = car(acl); acl = cdr(acl);
1805                 d = 1;
1806                 if(substring(t, 0, 1) == "-")
1807                 {
1808                         d = -1;
1809                         t = substring(t, 1, strlen(t) - 1);
1810                 }
1811                 else if(substring(t, 0, 1) == "+")
1812                         t = substring(t, 1, strlen(t) - 1);
1813                 if(substring(t, -1, 1) == "*")
1814                 {
1815                         t = substring(t, 0, strlen(t) - 1);
1816                         s = substring(s, 0, strlen(t));
1817                 }
1818                 else
1819                         s = str;
1820
1821                 if(s == t)
1822                 {
1823                         r = d;
1824                 }
1825         }
1826         return r;
1827 }
1828 float startsWith(string haystack, string needle)
1829 {
1830         return substring(haystack, 0, strlen(needle)) == needle;
1831 }
1832 float startsWithNocase(string haystack, string needle)
1833 {
1834         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1835 }
1836
1837 string get_model_datafilename(string m, float sk, string fil)
1838 {
1839         if(m)
1840                 m = strcat(m, "_");
1841         else
1842                 m = "models/player/*_";
1843         if(sk >= 0)
1844                 m = strcat(m, ftos(sk));
1845         else
1846                 m = strcat(m, "*");
1847         return strcat(m, ".", fil);
1848 }
1849
1850 float get_model_parameters(string m, float sk)
1851 {
1852         string fn, s, c;
1853         float fh;
1854
1855         get_model_parameters_modelname = string_null;
1856         get_model_parameters_modelskin = -1;
1857         get_model_parameters_name = string_null;
1858         get_model_parameters_species = -1;
1859         get_model_parameters_sex = string_null;
1860         get_model_parameters_weight = -1;
1861         get_model_parameters_age = -1;
1862         get_model_parameters_desc = string_null;
1863
1864         if not(m)
1865                 return 1;
1866         if(sk < 0)
1867         {
1868                 if(substring(m, -4, -1) != ".txt")
1869                         return 0;
1870                 if(substring(m, -6, 1) != "_")
1871                         return 0;
1872                 sk = stof(substring(m, -5, 1));
1873                 m = substring(m, 0, -7);
1874         }
1875
1876         fn = get_model_datafilename(m, sk, "txt");
1877         fh = fopen(fn, FILE_READ);
1878         if(fh < 0)
1879                 return 0;
1880
1881         get_model_parameters_modelname = m;
1882         get_model_parameters_modelskin = sk;
1883         while((s = fgets(fh)))
1884         {
1885                 if(s == "")
1886                         break; // next lines will be description
1887                 c = car(s);
1888                 s = cdr(s);
1889                 if(c == "name")
1890                         get_model_parameters_name = s;
1891                 if(c == "species")
1892                         switch(s)
1893                         {
1894                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
1895                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
1896                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1897                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1898                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1899                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
1900                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
1901                         }
1902                 if(c == "sex")
1903                         get_model_parameters_sex = s;
1904                 if(c == "weight")
1905                         get_model_parameters_weight = stof(s);
1906                 if(c == "age")
1907                         get_model_parameters_age = stof(s);
1908         }
1909
1910         while((s = fgets(fh)))
1911         {
1912                 if(get_model_parameters_desc)
1913                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1914                 if(s != "")
1915                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1916         }
1917
1918         fclose(fh);
1919
1920         return 1;
1921 }
1922
1923 vector vec2(vector v)
1924 {
1925         v_z = 0;
1926         return v;
1927 }
1928
1929 #ifndef MENUQC
1930 vector NearestPointOnBox(entity box, vector org)
1931 {
1932         vector m1, m2, nearest;
1933
1934         m1 = box.mins + box.origin;
1935         m2 = box.maxs + box.origin;
1936
1937         nearest_x = bound(m1_x, org_x, m2_x);
1938         nearest_y = bound(m1_y, org_y, m2_y);
1939         nearest_z = bound(m1_z, org_z, m2_z);
1940
1941         return nearest;
1942 }
1943 #endif