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