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