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