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