]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
Merge branch 'fruitiex/newpanelhud_stable'
[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;
232
233         // if negative, cut off the sign first
234         if(number < 0)
235                 return strcat("-", ftos_decimals(-number, decimals));
236         // it now is always positive!
237
238         // 3.516 -> 352
239         number = floor(number * pow(10, decimals) + 0.5);
240
241         // 352 -> "352"
242         result = ftos(number);
243         len = strlen(result);
244         // does it have a decimal point (should not happen)? If there is one, it is always at len-7)
245                 // if ftos had messed it up, which should never happen: "34278.000000"
246         if(len >= 7)
247                 if(substring(result, len - 7, 1) == ".")
248                 {
249                         dprint("ftos(integer) has comma? Can't be. Affected result: ", result, "\n");
250                         result = substring(result, 0, len - 7);
251                         len -= 7;
252                 }
253                 // "34278"
254         if(decimals == 0)
255                 return result; // don't insert a point for zero decimals
256         // is it too short? If yes, insert leading zeroes
257         if(len <= decimals)
258         {
259                 result = strcat(substring("0000000000", 0, decimals - len + 1), result);
260                 len = decimals + 1;
261         }
262         // and now... INSERT THE POINT!
263         tmp = substring(result, len - decimals, decimals);
264         result = strcat(substring(result, 0, len - decimals), ".", tmp);
265         return result;
266 }
267
268 float time;
269 vector colormapPaletteColor(float c, float isPants)
270 {
271         switch(c)
272         {
273                 case  0: return '0.800000 0.800000 0.800000';
274                 case  1: return '0.600000 0.400000 0.000000';
275                 case  2: return '0.000000 1.000000 0.501961';
276                 case  3: return '0.000000 1.000000 0.000000';
277                 case  4: return '1.000000 0.000000 0.000000';
278                 case  5: return '0.000000 0.658824 1.000000';
279                 case  6: return '0.000000 1.000000 1.000000';
280                 case  7: return '0.501961 1.000000 0.000000';
281                 case  8: return '0.501961 0.000000 1.000000';
282                 case  9: return '1.000000 0.000000 1.000000';
283                 case 10: return '1.000000 0.000000 0.501961';
284                 case 11: return '0.600000 0.600000 0.600000';
285                 case 12: return '1.000000 1.000000 0.000000';
286                 case 13: return '0.000000 0.313725 1.000000';
287                 case 14: return '1.000000 0.501961 0.000000';
288                 case 15:
289                         if(isPants)
290                                 return
291                                           '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
292                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
293                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
294                         else
295                                 return
296                                           '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
297                                         + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
298                                         + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
299                 default: return '0.000 0.000 0.000';
300         }
301 }
302
303 // unzone the string, and return it as tempstring. Safe to be called on string_null
304 string fstrunzone(string s)
305 {
306         string sc;
307         if not(s)
308                 return s;
309         sc = strcat(s, "");
310         strunzone(s);
311         return sc;
312 }
313
314 // Databases (hash tables)
315 #define DB_BUCKETS 8192
316 void db_save(float db, string pFilename)
317 {
318         float fh, i, n;
319         fh = fopen(pFilename, FILE_WRITE);
320         if(fh < 0) 
321         {
322                 print(strcat("^1Can't write DB to ", pFilename));
323                 return;
324         }
325         n = buf_getsize(db);
326         fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
327         for(i = 0; i < n; ++i)
328                 fputs(fh, strcat(bufstr_get(db, i), "\n"));
329         fclose(fh);
330 }
331
332 float db_create()
333 {
334         return buf_create();
335 }
336
337 float db_load(string pFilename)
338 {
339         float db, fh, i, j, n;
340         string l;
341         db = buf_create();
342         if(db < 0)
343                 return -1;
344         fh = fopen(pFilename, FILE_READ);
345         if(fh < 0)
346                 return db;
347         if(stof(fgets(fh)) == DB_BUCKETS)
348         {
349                 i = 0;
350                 while((l = fgets(fh)))
351                 {
352                         if(l != "")
353                                 bufstr_set(db, i, l);
354                         ++i;
355                 }
356         }
357         else
358         {
359                 // different count of buckets?
360                 // need to reorganize the database then (SLOW)
361                 while((l = fgets(fh)))
362                 {
363                         n = tokenizebyseparator(l, "\\");
364                         for(j = 2; j < n; j += 2)
365                                 db_put(db, argv(j-1), uri_unescape(argv(j)));
366                 }
367         }
368         fclose(fh);
369         return db;
370 }
371
372 void db_dump(float db, string pFilename)
373 {
374         float fh, i, j, n, m;
375         fh = fopen(pFilename, FILE_WRITE);
376         if(fh < 0)
377                 error(strcat("Can't dump DB to ", pFilename));
378         n = buf_getsize(db);
379         fputs(fh, "0\n");
380         for(i = 0; i < n; ++i)
381         {
382                 m = tokenizebyseparator(bufstr_get(db, i), "\\");
383                 for(j = 2; j < m; j += 2)
384                         fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
385         }
386         fclose(fh);
387 }
388
389 void db_close(float db)
390 {
391         buf_del(db);
392 }
393
394 string db_get(float db, string pKey)
395 {
396         float h;
397         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
398         return uri_unescape(infoget(bufstr_get(db, h), pKey));
399 }
400
401 void db_put(float db, string pKey, string pValue)
402 {
403         float h;
404         h = mod(crc16(FALSE, pKey), DB_BUCKETS);
405         bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
406 }
407
408 void db_test()
409 {
410         float db, i;
411         print("LOAD...\n");
412         db = db_load("foo.db");
413         print("LOADED. FILL...\n");
414         for(i = 0; i < DB_BUCKETS; ++i)
415                 db_put(db, ftos(random()), "X");
416         print("FILLED. SAVE...\n");
417         db_save(db, "foo.db");
418         print("SAVED. CLOSE...\n");
419         db_close(db);
420         print("CLOSED.\n");
421 }
422
423 // Multiline text file buffers
424 float buf_load(string pFilename)
425 {
426         float buf, fh, i;
427         string l;
428         buf = buf_create();
429         if(buf < 0)
430                 return -1;
431         fh = fopen(pFilename, FILE_READ);
432         if(fh < 0)
433                 return buf;
434         i = 0;
435         while((l = fgets(fh)))
436         {
437                 bufstr_set(buf, i, l);
438                 ++i;
439         }
440         fclose(fh);
441         return buf;
442 }
443
444 void buf_save(float buf, string pFilename)
445 {
446         float fh, i, n;
447         fh = fopen(pFilename, FILE_WRITE);
448         if(fh < 0)
449                 error(strcat("Can't write buf to ", pFilename));
450         n = buf_getsize(buf);
451         for(i = 0; i < n; ++i)
452                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
453         fclose(fh);
454 }
455
456 string GametypeNameFromType(float g)
457 {
458         if      (g == GAME_DEATHMATCH) return "dm";
459         else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
460         else if (g == GAME_DOMINATION) return "dom";
461         else if (g == GAME_CTF) return "ctf";
462         else if (g == GAME_RUNEMATCH) return "rune";
463         else if (g == GAME_LMS) return "lms";
464         else if (g == GAME_ARENA) return "arena";
465         else if (g == GAME_CA) return "ca";
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 #endif
690
691 string fixPriorityList(string order, float from, float to, float subtract, float complete)
692 {
693         string neworder;
694         float i, n, w;
695
696         n = tokenize_console(order);
697         neworder = "";
698         for(i = 0; i < n; ++i)
699         {
700                 w = stof(argv(i));
701                 if(w == floor(w))
702                 {
703                         if(w >= from && w <= to)
704                                 neworder = strcat(neworder, ftos(w), " ");
705                         else
706                         {
707                                 w -= subtract;
708                                 if(w >= from && w <= to)
709                                         neworder = strcat(neworder, ftos(w), " ");
710                         }
711                 }
712         }
713
714         if(complete)
715         {
716                 n = tokenize_console(neworder);
717                 for(w = to; w >= from; --w)
718                 {
719                         for(i = 0; i < n; ++i)
720                                 if(stof(argv(i)) == w)
721                                         break;
722                         if(i == n) // not found
723                                 neworder = strcat(neworder, ftos(w), " ");
724                 }
725         }
726         
727         return substring(neworder, 0, strlen(neworder) - 1);
728 }
729
730 string mapPriorityList(string order, string(string) mapfunc)
731 {
732         string neworder;
733         float i, n;
734
735         n = tokenize_console(order);
736         neworder = "";
737         for(i = 0; i < n; ++i)
738                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
739         
740         return substring(neworder, 0, strlen(neworder) - 1);
741 }
742
743 string swapInPriorityList(string order, float i, float j)
744 {
745         string s;
746         float w, n;
747
748         n = tokenize_console(order);
749
750         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
751         {
752                 s = "";
753                 for(w = 0; w < n; ++w)
754                 {
755                         if(w == i)
756                                 s = strcat(s, argv(j), " ");
757                         else if(w == j)
758                                 s = strcat(s, argv(i), " ");
759                         else
760                                 s = strcat(s, argv(w), " ");
761                 }
762                 return substring(s, 0, strlen(s) - 1);
763         }
764         
765         return order;
766 }
767
768 float cvar_value_issafe(string s)
769 {
770         if(strstrofs(s, "\"", 0) >= 0)
771                 return 0;
772         if(strstrofs(s, "\\", 0) >= 0)
773                 return 0;
774         if(strstrofs(s, ";", 0) >= 0)
775                 return 0;
776         if(strstrofs(s, "$", 0) >= 0)
777                 return 0;
778         if(strstrofs(s, "\r", 0) >= 0)
779                 return 0;
780         if(strstrofs(s, "\n", 0) >= 0)
781                 return 0;
782         return 1;
783 }
784
785 #ifndef MENUQC
786 void get_mi_min_max(float mode)
787 {
788         vector mi, ma;
789
790         if(mi_shortname)
791                 strunzone(mi_shortname);
792         mi_shortname = mapname;
793         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
794                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
795         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
796                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
797         mi_shortname = strzone(mi_shortname);
798
799 #ifdef CSQC
800         mi = world.mins;
801         ma = world.maxs;
802 #else
803         mi = world.absmin;
804         ma = world.absmax;
805 #endif
806
807         mi_min = mi;
808         mi_max = ma;
809         MapInfo_Get_ByName(mi_shortname, 0, 0);
810         if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
811         {
812                 mi_min = MapInfo_Map_mins;
813                 mi_max = MapInfo_Map_maxs;
814         }
815         else
816         {
817                 // not specified
818                 if(mode)
819                 {
820                         // be clever
821                         tracebox('1 0 0' * mi_x,
822                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
823                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
824                                          '1 0 0' * ma_x,
825                                          MOVE_WORLDONLY,
826                                          world);
827                         if(!trace_startsolid)
828                                 mi_min_x = trace_endpos_x;
829
830                         tracebox('0 1 0' * mi_y,
831                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
832                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
833                                          '0 1 0' * ma_y,
834                                          MOVE_WORLDONLY,
835                                          world);
836                         if(!trace_startsolid)
837                                 mi_min_y = trace_endpos_y;
838
839                         tracebox('0 0 1' * mi_z,
840                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
841                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
842                                          '0 0 1' * ma_z,
843                                          MOVE_WORLDONLY,
844                                          world);
845                         if(!trace_startsolid)
846                                 mi_min_z = trace_endpos_z;
847
848                         tracebox('1 0 0' * ma_x,
849                                          '0 1 0' * mi_y + '0 0 1' * mi_z,
850                                          '0 1 0' * ma_y + '0 0 1' * ma_z,
851                                          '1 0 0' * mi_x,
852                                          MOVE_WORLDONLY,
853                                          world);
854                         if(!trace_startsolid)
855                                 mi_max_x = trace_endpos_x;
856
857                         tracebox('0 1 0' * ma_y,
858                                          '1 0 0' * mi_x + '0 0 1' * mi_z,
859                                          '1 0 0' * ma_x + '0 0 1' * ma_z,
860                                          '0 1 0' * mi_y,
861                                          MOVE_WORLDONLY,
862                                          world);
863                         if(!trace_startsolid)
864                                 mi_max_y = trace_endpos_y;
865
866                         tracebox('0 0 1' * ma_z,
867                                          '1 0 0' * mi_x + '0 1 0' * mi_y,
868                                          '1 0 0' * ma_x + '0 1 0' * ma_y,
869                                          '0 0 1' * mi_z,
870                                          MOVE_WORLDONLY,
871                                          world);
872                         if(!trace_startsolid)
873                                 mi_max_z = trace_endpos_z;
874                 }
875         }
876 }
877
878 void get_mi_min_max_texcoords(float mode)
879 {
880         vector extend;
881
882         get_mi_min_max(mode);
883
884         mi_picmin = mi_min;
885         mi_picmax = mi_max;
886
887         // extend mi_picmax to get a square aspect ratio
888         // center the map in that area
889         extend = mi_picmax - mi_picmin;
890         if(extend_y > extend_x)
891         {
892                 mi_picmin_x -= (extend_y - extend_x) * 0.5;
893                 mi_picmax_x += (extend_y - extend_x) * 0.5;
894         }
895         else
896         {
897                 mi_picmin_y -= (extend_x - extend_y) * 0.5;
898                 mi_picmax_y += (extend_x - extend_y) * 0.5;
899         }
900
901         // add another some percent
902         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
903         mi_picmin -= extend;
904         mi_picmax += extend;
905
906         // calculate the texcoords
907         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
908         // first the two corners of the origin
909         mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
910         mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
911         mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
912         mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
913         // then the other corners
914         mi_pictexcoord1_x = mi_pictexcoord0_x;
915         mi_pictexcoord1_y = mi_pictexcoord2_y;
916         mi_pictexcoord3_x = mi_pictexcoord2_x;
917         mi_pictexcoord3_y = mi_pictexcoord0_y;
918 }
919 #endif
920
921 #ifdef CSQC
922 void cvar_settemp(string pKey, string pValue)
923 {
924         error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
925 }
926 void cvar_settemp_restore()
927 {
928         error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
929 }
930 #else
931 void cvar_settemp(string pKey, string pValue)
932 {
933         float i;
934         string settemp_var;
935         if(cvar_string(pKey) == pValue)
936                 return;
937         i = cvar("settemp_idx");
938         cvar_set("settemp_idx", ftos(i+1));
939         settemp_var = strcat("_settemp_x", ftos(i));
940 #ifdef MENUQC
941         registercvar(settemp_var, "", 0);
942 #else
943         registercvar(settemp_var, "");
944 #endif
945         cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
946         cvar_set(settemp_var, cvar_string(pKey));
947         cvar_set(pKey, pValue);
948 }
949
950 void cvar_settemp_restore()
951 {
952         // undo what cvar_settemp did
953         float n, i;
954         n = tokenize_console(cvar_string("settemp_list"));
955         for(i = 0; i < n - 3; i += 3)
956                 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
957         cvar_set("settemp_list", "0");
958 }
959 #endif
960
961 float almost_equals(float a, float b)
962 {
963         float eps;
964         eps = (max(a, -a) + max(b, -b)) * 0.001;
965         if(a - b < eps && b - a < eps)
966                 return TRUE;
967         return FALSE;
968 }
969
970 float almost_in_bounds(float a, float b, float c)
971 {
972         float eps;
973         eps = (max(a, -a) + max(c, -c)) * 0.001;
974         return b == median(a - eps, b, c + eps);
975 }
976
977 float power2of(float e)
978 {
979         return pow(2, e);
980 }
981 float log2of(float x)
982 {
983         // NOTE: generated code
984         if(x > 2048)
985                 if(x > 131072)
986                         if(x > 1048576)
987                                 if(x > 4194304)
988                                         return 23;
989                                 else
990                                         if(x > 2097152)
991                                                 return 22;
992                                         else
993                                                 return 21;
994                         else
995                                 if(x > 524288)
996                                         return 20;
997                                 else
998                                         if(x > 262144)
999                                                 return 19;
1000                                         else
1001                                                 return 18;
1002                 else
1003                         if(x > 16384)
1004                                 if(x > 65536)
1005                                         return 17;
1006                                 else
1007                                         if(x > 32768)
1008                                                 return 16;
1009                                         else
1010                                                 return 15;
1011                         else
1012                                 if(x > 8192)
1013                                         return 14;
1014                                 else
1015                                         if(x > 4096)
1016                                                 return 13;
1017                                         else
1018                                                 return 12;
1019         else
1020                 if(x > 32)
1021                         if(x > 256)
1022                                 if(x > 1024)
1023                                         return 11;
1024                                 else
1025                                         if(x > 512)
1026                                                 return 10;
1027                                         else
1028                                                 return 9;
1029                         else
1030                                 if(x > 128)
1031                                         return 8;
1032                                 else
1033                                         if(x > 64)
1034                                                 return 7;
1035                                         else
1036                                                 return 6;
1037                 else
1038                         if(x > 4)
1039                                 if(x > 16)
1040                                         return 5;
1041                                 else
1042                                         if(x > 8)
1043                                                 return 4;
1044                                         else
1045                                                 return 3;
1046                         else
1047                                 if(x > 2)
1048                                         return 2;
1049                                 else
1050                                         if(x > 1)
1051                                                 return 1;
1052                                         else
1053                                                 return 0;
1054 }
1055
1056 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1057 {
1058         if(mi == ma)
1059                 return 0;
1060         else if(ma == rgb_x)
1061         {
1062                 if(rgb_y >= rgb_z)
1063                         return (rgb_y - rgb_z) / (ma - mi);
1064                 else
1065                         return (rgb_y - rgb_z) / (ma - mi) + 6;
1066         }
1067         else if(ma == rgb_y)
1068                 return (rgb_z - rgb_x) / (ma - mi) + 2;
1069         else // if(ma == rgb_z)
1070                 return (rgb_x - rgb_y) / (ma - mi) + 4;
1071 }
1072
1073 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1074 {
1075         vector rgb;
1076
1077         hue -= 6 * floor(hue / 6);
1078
1079         //else if(ma == rgb_x)
1080         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1081         if(hue <= 1)
1082         {
1083                 rgb_x = ma;
1084                 rgb_y = hue * (ma - mi) + mi;
1085                 rgb_z = mi;
1086         }
1087         //else if(ma == rgb_y)
1088         //      hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1089         else if(hue <= 2)
1090         {
1091                 rgb_x = (2 - hue) * (ma - mi) + mi;
1092                 rgb_y = ma;
1093                 rgb_z = mi;
1094         }
1095         else if(hue <= 3)
1096         {
1097                 rgb_x = mi;
1098                 rgb_y = ma;
1099                 rgb_z = (hue - 2) * (ma - mi) + mi;
1100         }
1101         //else // if(ma == rgb_z)
1102         //      hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1103         else if(hue <= 4)
1104         {
1105                 rgb_x = mi;
1106                 rgb_y = (4 - hue) * (ma - mi) + mi;
1107                 rgb_z = ma;
1108         }
1109         else if(hue <= 5)
1110         {
1111                 rgb_x = (hue - 4) * (ma - mi) + mi;
1112                 rgb_y = mi;
1113                 rgb_z = ma;
1114         }
1115         //else if(ma == rgb_x)
1116         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1117         else // if(hue <= 6)
1118         {
1119                 rgb_x = ma;
1120                 rgb_y = mi;
1121                 rgb_z = (6 - hue) * (ma - mi) + mi;
1122         }
1123
1124         return rgb;
1125 }
1126
1127 vector rgb_to_hsv(vector rgb)
1128 {
1129         float mi, ma;
1130         vector hsv;
1131
1132         mi = min3(rgb_x, rgb_y, rgb_z);
1133         ma = max3(rgb_x, rgb_y, rgb_z);
1134
1135         hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1136         hsv_z = ma;
1137
1138         if(ma == 0)
1139                 hsv_y = 0;
1140         else
1141                 hsv_y = 1 - mi/ma;
1142         
1143         return hsv;
1144 }
1145
1146 vector hsv_to_rgb(vector hsv)
1147 {
1148         return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1149 }
1150
1151 vector rgb_to_hsl(vector rgb)
1152 {
1153         float mi, ma;
1154         vector hsl;
1155
1156         mi = min3(rgb_x, rgb_y, rgb_z);
1157         ma = max3(rgb_x, rgb_y, rgb_z);
1158
1159         hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1160         
1161         hsl_z = 0.5 * (mi + ma);
1162         if(mi == ma)
1163                 hsl_y = 0;
1164         else if(hsl_z <= 0.5)
1165                 hsl_y = (ma - mi) / (2*hsl_z);
1166         else // if(hsl_z > 0.5)
1167                 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1168         
1169         return hsl;
1170 }
1171
1172 vector hsl_to_rgb(vector hsl)
1173 {
1174         float mi, ma, maminusmi;
1175
1176         if(hsl_z <= 0.5)
1177                 maminusmi = hsl_y * 2 * hsl_z;
1178         else
1179                 maminusmi = hsl_y * (2 - 2 * hsl_z);
1180         
1181         // hsl_z     = 0.5 * mi + 0.5 * ma
1182         // maminusmi =     - mi +       ma
1183         mi = hsl_z - 0.5 * maminusmi;
1184         ma = hsl_z + 0.5 * maminusmi;
1185
1186         return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1187 }
1188
1189 string rgb_to_hexcolor(vector rgb)
1190 {
1191         return
1192                 strcat(
1193                         "^x",
1194                         DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1195                         DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1196                         DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1197                 );
1198 }
1199
1200 // requires that m2>m1 in all coordinates, and that m4>m3
1201 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;};
1202
1203 // requires the same, but is a stronger condition
1204 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;};
1205
1206 #ifndef MENUQC
1207 #endif
1208
1209 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1210 {
1211         float ICanHasKallerz;
1212
1213         // detect color codes support in the width function
1214         ICanHasKallerz = (w("^7", theSize) == 0);
1215
1216         // STOP.
1217         // The following function is SLOW.
1218         // For your safety and for the protection of those around you...
1219         // DO NOT CALL THIS AT HOME.
1220         // No really, don't.
1221         if(w(theText, theSize) <= maxWidth)
1222                 return strlen(theText); // yeah!
1223
1224         // binary search for right place to cut string
1225         float ch;
1226         float left, right, middle; // this always works
1227         left = 0;
1228         right = strlen(theText); // this always fails
1229         do
1230         {
1231                 middle = floor((left + right) / 2);
1232                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1233                         left = middle;
1234                 else
1235                         right = middle;
1236         }
1237         while(left < right - 1);
1238
1239         if(ICanHasKallerz)
1240         {
1241                 // NOTE: when color codes are involved, this binary search is,
1242                 // mathematically, BROKEN. However, it is obviously guaranteed to
1243                 // terminate, as the range still halves each time - but nevertheless, it is
1244                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1245                 // range, and "right" is outside).
1246                 
1247                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1248                 // and decrease left on the basis of the chars detected of the truncated tag
1249                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1250                 // (sometimes too much but with a correct result)
1251                 // it fixes also ^[0-9]
1252                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1253                         left-=1;
1254
1255                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1256                         left-=2;
1257                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1258                         {
1259                                 ch = str2chr(theText, left-1);
1260                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1261                                         left-=3;
1262                         }
1263                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1264                         {
1265                                 ch = str2chr(theText, left-2);
1266                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1267                                 {
1268                                         ch = str2chr(theText, left-1);
1269                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1270                                                 left-=4;
1271                                 }
1272                         }
1273         }
1274         
1275         return left;
1276 }
1277
1278 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1279 {
1280         float ICanHasKallerz;
1281
1282         // detect color codes support in the width function
1283         ICanHasKallerz = (w("^7") == 0);
1284
1285         // STOP.
1286         // The following function is SLOW.
1287         // For your safety and for the protection of those around you...
1288         // DO NOT CALL THIS AT HOME.
1289         // No really, don't.
1290         if(w(theText) <= maxWidth)
1291                 return strlen(theText); // yeah!
1292
1293         // binary search for right place to cut string
1294         float ch;
1295         float left, right, middle; // this always works
1296         left = 0;
1297         right = strlen(theText); // this always fails
1298         do
1299         {
1300                 middle = floor((left + right) / 2);
1301                 if(w(substring(theText, 0, middle)) <= maxWidth)
1302                         left = middle;
1303                 else
1304                         right = middle;
1305         }
1306         while(left < right - 1);
1307
1308         if(ICanHasKallerz)
1309         {
1310                 // NOTE: when color codes are involved, this binary search is,
1311                 // mathematically, BROKEN. However, it is obviously guaranteed to
1312                 // terminate, as the range still halves each time - but nevertheless, it is
1313                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1314                 // range, and "right" is outside).
1315                 
1316                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1317                 // and decrease left on the basis of the chars detected of the truncated tag
1318                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1319                 // (sometimes too much but with a correct result)
1320                 // it fixes also ^[0-9]
1321                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1322                         left-=1;
1323
1324                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1325                         left-=2;
1326                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1327                         {
1328                                 ch = str2chr(theText, left-1);
1329                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1330                                         left-=3;
1331                         }
1332                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1333                         {
1334                                 ch = str2chr(theText, left-2);
1335                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1336                                 {
1337                                         ch = str2chr(theText, left-1);
1338                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1339                                                 left-=4;
1340                                 }
1341                         }
1342         }
1343         
1344         return left;
1345 }
1346
1347 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1348 {
1349         float cantake;
1350         float take;
1351         string s;
1352
1353         s = getWrappedLine_remaining;
1354
1355         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1356         if(cantake > 0 && cantake < strlen(s))
1357         {
1358                 take = cantake - 1;
1359                 while(take > 0 && substring(s, take, 1) != " ")
1360                         --take;
1361                 if(take == 0)
1362                 {
1363                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1364                         if(getWrappedLine_remaining == "")
1365                                 getWrappedLine_remaining = string_null;
1366                         return substring(s, 0, cantake);
1367                 }
1368                 else
1369                 {
1370                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1371                         if(getWrappedLine_remaining == "")
1372                                 getWrappedLine_remaining = string_null;
1373                         return substring(s, 0, take);
1374                 }
1375         }
1376         else
1377         {
1378                 getWrappedLine_remaining = string_null;
1379                 return s;
1380         }
1381 }
1382
1383 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1384 {
1385         float cantake;
1386         float take;
1387         string s;
1388
1389         s = getWrappedLine_remaining;
1390
1391         cantake = textLengthUpToLength(s, w, tw);
1392         if(cantake > 0 && cantake < strlen(s))
1393         {
1394                 take = cantake - 1;
1395                 while(take > 0 && substring(s, take, 1) != " ")
1396                         --take;
1397                 if(take == 0)
1398                 {
1399                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1400                         if(getWrappedLine_remaining == "")
1401                                 getWrappedLine_remaining = string_null;
1402                         return substring(s, 0, cantake);
1403                 }
1404                 else
1405                 {
1406                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1407                         if(getWrappedLine_remaining == "")
1408                                 getWrappedLine_remaining = string_null;
1409                         return substring(s, 0, take);
1410                 }
1411         }
1412         else
1413         {
1414                 getWrappedLine_remaining = string_null;
1415                 return s;
1416         }
1417 }
1418
1419 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1420 {
1421         if(tw(theText, theFontSize) <= maxWidth)
1422                 return theText;
1423         else
1424                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1425 }
1426
1427 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1428 {
1429         if(tw(theText) <= maxWidth)
1430                 return theText;
1431         else
1432                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1433 }
1434
1435 float isGametypeInFilter(float gt, float tp, string pattern)
1436 {
1437         string subpattern, subpattern2, subpattern3;
1438         subpattern = strcat(",", GametypeNameFromType(gt), ",");
1439         if(tp)
1440                 subpattern2 = ",teams,";
1441         else
1442                 subpattern2 = ",noteams,";
1443         if(gt == GAME_RACE || gt == GAME_CTS)
1444                 subpattern3 = ",race,";
1445         else
1446                 subpattern3 = string_null;
1447
1448         if(substring(pattern, 0, 1) == "-")
1449         {
1450                 pattern = substring(pattern, 1, strlen(pattern) - 1);
1451                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1452                         return 0;
1453                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1454                         return 0;
1455                 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1456                         return 0;
1457         }
1458         else
1459         {
1460                 if(substring(pattern, 0, 1) == "+")
1461                         pattern = substring(pattern, 1, strlen(pattern) - 1);
1462                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1463                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1464                 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1465                         return 0;
1466         }
1467         return 1;
1468 }
1469
1470 void shuffle(float n, swapfunc_t swap, entity pass)
1471 {
1472         float i, j;
1473         for(i = 1; i < n; ++i)
1474         {
1475                 // swap i-th item at a random position from 0 to i
1476                 // proof for even distribution:
1477                 //   n = 1: obvious
1478                 //   n -> n+1:
1479                 //     item n+1 gets at any position with chance 1/(n+1)
1480                 //     all others will get their 1/n chance reduced by factor n/(n+1)
1481                 //     to be on place n+1, their chance will be 1/(n+1)
1482                 //     1/n * n/(n+1) = 1/(n+1)
1483                 //     q.e.d.
1484                 j = floor(random() * (i + 1));
1485                 if(j != i)
1486                         swap(j, i, pass);
1487         }
1488 }
1489
1490 string substring_range(string s, float b, float e)
1491 {
1492         return substring(s, b, e - b);
1493 }
1494
1495 string swapwords(string str, float i, float j)
1496 {
1497         float n;
1498         string s1, s2, s3, s4, s5;
1499         float si, ei, sj, ej, s0, en;
1500         n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1501         si = argv_start_index(i);
1502         sj = argv_start_index(j);
1503         ei = argv_end_index(i);
1504         ej = argv_end_index(j);
1505         s0 = argv_start_index(0);
1506         en = argv_end_index(n-1);
1507         s1 = substring_range(str, s0, si);
1508         s2 = substring_range(str, si, ei);
1509         s3 = substring_range(str, ei, sj);
1510         s4 = substring_range(str, sj, ej);
1511         s5 = substring_range(str, ej, en);
1512         return strcat(s1, s4, s3, s2, s5);
1513 }
1514
1515 string _shufflewords_str;
1516 void _shufflewords_swapfunc(float i, float j, entity pass)
1517 {
1518         _shufflewords_str = swapwords(_shufflewords_str, i, j);
1519 }
1520 string shufflewords(string str)
1521 {
1522         float n;
1523         _shufflewords_str = str;
1524         n = tokenizebyseparator(str, " ");
1525         shuffle(n, _shufflewords_swapfunc, world);
1526         str = _shufflewords_str;
1527         _shufflewords_str = string_null;
1528         return str;
1529 }
1530
1531 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1532 {
1533         vector v;
1534         float D;
1535         v = '0 0 0';
1536         if(a == 0)
1537         {
1538                 if(b != 0)
1539                 {
1540                         v_x = v_y = -c / b;
1541                         v_z = 1;
1542                 }
1543                 else
1544                 {
1545                         if(c == 0)
1546                         {
1547                                 // actually, every number solves the equation!
1548                                 v_z = 1;
1549                         }
1550                 }
1551         }
1552         else
1553         {
1554                 D = b*b - 4*a*c;
1555                 if(D >= 0)
1556                 {
1557                         D = sqrt(D);
1558                         if(a > 0) // put the smaller solution first
1559                         {
1560                                 v_x = ((-b)-D) / (2*a);
1561                                 v_y = ((-b)+D) / (2*a);
1562                         }
1563                         else
1564                         {
1565                                 v_x = (-b+D) / (2*a);
1566                                 v_y = (-b-D) / (2*a);
1567                         }
1568                         v_z = 1;
1569                 }
1570                 else
1571                 {
1572                         // complex solutions!
1573                         D = sqrt(-D);
1574                         v_x = -b / (2*a);
1575                         if(a > 0)
1576                                 v_y =  D / (2*a);
1577                         else
1578                                 v_y = -D / (2*a);
1579                         v_z = 0;
1580                 }
1581         }
1582         return v;
1583 }
1584
1585
1586 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1587 float _unacceptable_compiler_bug_1_b() { return 1; }
1588 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1589 float _unacceptable_compiler_bug_1_d() { return 1; }
1590
1591 void check_unacceptable_compiler_bugs()
1592 {
1593         if(cvar("_allow_unacceptable_compiler_bugs"))
1594                 return;
1595         tokenize_console("foo bar");
1596         if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1597                 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.");
1598 }
1599
1600 float compressShotOrigin(vector v)
1601 {
1602         float x, y, z;
1603         x = rint(v_x * 2);
1604         y = rint(v_y * 4) + 128;
1605         z = rint(v_z * 4) + 128;
1606         if(x > 255 || x < 0)
1607         {
1608                 print("shot origin ", vtos(v), " x out of bounds\n");
1609                 x = bound(0, x, 255);
1610         }
1611         if(y > 255 || y < 0)
1612         {
1613                 print("shot origin ", vtos(v), " y out of bounds\n");
1614                 y = bound(0, y, 255);
1615         }
1616         if(z > 255 || z < 0)
1617         {
1618                 print("shot origin ", vtos(v), " z out of bounds\n");
1619                 z = bound(0, z, 255);
1620         }
1621         return x * 0x10000 + y * 0x100 + z;
1622 }
1623 vector decompressShotOrigin(float f)
1624 {
1625         vector v;
1626         v_x = ((f & 0xFF0000) / 0x10000) / 2;
1627         v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1628         v_z = ((f & 0xFF) - 128) / 4;
1629         return v;
1630 }
1631
1632 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1633 {
1634         float start, end, root, child;
1635
1636         // heapify
1637         start = floor((n - 2) / 2);
1638         while(start >= 0)
1639         {
1640                 // siftdown(start, count-1);
1641                 root = start;
1642                 while(root * 2 + 1 <= n-1)
1643                 {
1644                         child = root * 2 + 1;
1645                         if(child < n-1)
1646                                 if(cmp(child, child+1, pass) < 0)
1647                                         ++child;
1648                         if(cmp(root, child, pass) < 0)
1649                         {
1650                                 swap(root, child, pass);
1651                                 root = child;
1652                         }
1653                         else
1654                                 break;
1655                 }
1656                 // end of siftdown
1657                 --start;
1658         }
1659
1660         // extract
1661         end = n - 1;
1662         while(end > 0)
1663         {
1664                 swap(0, end, pass);
1665                 --end;
1666                 // siftdown(0, end);
1667                 root = 0;
1668                 while(root * 2 + 1 <= end)
1669                 {
1670                         child = root * 2 + 1;
1671                         if(child < end && cmp(child, child+1, pass) < 0)
1672                                 ++child;
1673                         if(cmp(root, child, pass) < 0)
1674                         {
1675                                 swap(root, child, pass);
1676                                 root = child;
1677                         }
1678                         else
1679                                 break;
1680                 }
1681                 // end of siftdown
1682         }
1683 }
1684
1685 void RandomSelection_Init()
1686 {
1687         RandomSelection_totalweight = 0;
1688         RandomSelection_chosen_ent = world;
1689         RandomSelection_chosen_float = 0;
1690         RandomSelection_chosen_string = string_null;
1691         RandomSelection_best_priority = -1;
1692 }
1693 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1694 {
1695         if(priority > RandomSelection_best_priority)
1696         {
1697                 RandomSelection_best_priority = priority;
1698                 RandomSelection_chosen_ent = e;
1699                 RandomSelection_chosen_float = f;
1700                 RandomSelection_chosen_string = s;
1701                 RandomSelection_totalweight = weight;
1702         }
1703         else if(priority == RandomSelection_best_priority)
1704         {
1705                 RandomSelection_totalweight += weight;
1706                 if(random() * RandomSelection_totalweight <= weight)
1707                 {
1708                         RandomSelection_chosen_ent = e;
1709                         RandomSelection_chosen_float = f;
1710                         RandomSelection_chosen_string = s;
1711                 }
1712         }
1713 }
1714
1715 vector healtharmor_maxdamage(float h, float a, float armorblock)
1716 {
1717         // NOTE: we'll always choose the SMALLER value...
1718         float healthdamage, armordamage, armorideal;
1719         vector v;
1720         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1721         armordamage = a + (h - 1); // damage we can take if we could use more armor
1722         armorideal = healthdamage * armorblock;
1723         v_y = armorideal;
1724         if(armordamage < healthdamage)
1725         {
1726                 v_x = armordamage;
1727                 v_z = 1;
1728         }
1729         else
1730         {
1731                 v_x = healthdamage;
1732                 v_z = 0;
1733         }
1734         return v;
1735 }
1736
1737 vector healtharmor_applydamage(float a, float armorblock, float damage)
1738 {
1739         vector v;
1740         v_y = bound(0, damage * armorblock, a); // save
1741         v_x = bound(0, damage - v_y, damage); // take
1742         v_z = 0;
1743         return v;
1744 }
1745
1746 string getcurrentmod()
1747 {
1748         float n;
1749         string m;
1750         m = cvar_string("fs_gamedir");
1751         n = tokenize_console(m);
1752         if(n == 0)
1753                 return "data";
1754         else
1755                 return argv(n - 1);
1756 }
1757
1758 #ifndef MENUQC
1759 #ifdef CSQC
1760 float ReadInt24_t()
1761 {
1762         float v;
1763         v = ReadShort() * 256; // note: this is signed
1764         v += ReadByte(); // note: this is unsigned
1765         return v;
1766 }
1767 #else
1768 void WriteInt24_t(float dest, float val)
1769 {
1770         float v;
1771         WriteShort(dest, (v = floor(val / 256)));
1772         WriteByte(dest, val - v * 256); // 0..255
1773 }
1774 #endif
1775 #endif
1776
1777 float float2range11(float f)
1778 {
1779         // continuous function mapping all reals into -1..1
1780         return f / (fabs(f) + 1);
1781 }
1782
1783 float float2range01(float f)
1784 {
1785         // continuous function mapping all reals into 0..1
1786         return 0.5 + 0.5 * float2range11(f);
1787 }
1788
1789 // from the GNU Scientific Library
1790 float gsl_ran_gaussian_lastvalue;
1791 float gsl_ran_gaussian_lastvalue_set;
1792 float gsl_ran_gaussian(float sigma)
1793 {
1794         float a, b;
1795         if(gsl_ran_gaussian_lastvalue_set)
1796         {
1797                 gsl_ran_gaussian_lastvalue_set = 0;
1798                 return sigma * gsl_ran_gaussian_lastvalue;
1799         }
1800         else
1801         {
1802                 a = random() * 2 * M_PI;
1803                 b = sqrt(-2 * log(random()));
1804                 gsl_ran_gaussian_lastvalue = cos(a) * b;
1805                 gsl_ran_gaussian_lastvalue_set = 1;
1806                 return sigma * sin(a) * b;
1807         }
1808 }
1809
1810 string car(string s)
1811 {
1812         float o;
1813         o = strstrofs(s, " ", 0);
1814         if(o < 0)
1815                 return s;
1816         return substring(s, 0, o);
1817 }
1818 string cdr(string s)
1819 {
1820         float o;
1821         o = strstrofs(s, " ", 0);
1822         if(o < 0)
1823                 return string_null;
1824         return substring(s, o + 1, strlen(s) - (o + 1));
1825 }
1826 float matchacl(string acl, string str)
1827 {
1828         string t, s;
1829         float r, d;
1830         r = 0;
1831         while(acl)
1832         {
1833                 t = car(acl); acl = cdr(acl);
1834                 d = 1;
1835                 if(substring(t, 0, 1) == "-")
1836                 {
1837                         d = -1;
1838                         t = substring(t, 1, strlen(t) - 1);
1839                 }
1840                 else if(substring(t, 0, 1) == "+")
1841                         t = substring(t, 1, strlen(t) - 1);
1842                 if(substring(t, -1, 1) == "*")
1843                 {
1844                         t = substring(t, 0, strlen(t) - 1);
1845                         s = substring(s, 0, strlen(t));
1846                 }
1847                 else
1848                         s = str;
1849
1850                 if(s == t)
1851                 {
1852                         r = d;
1853                 }
1854         }
1855         return r;
1856 }
1857 float startsWith(string haystack, string needle)
1858 {
1859         return substring(haystack, 0, strlen(needle)) == needle;
1860 }
1861 float startsWithNocase(string haystack, string needle)
1862 {
1863         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1864 }
1865
1866 string get_model_datafilename(string m, float sk, string fil)
1867 {
1868         if(m)
1869                 m = strcat(m, "_");
1870         else
1871                 m = "models/player/*_";
1872         if(sk >= 0)
1873                 m = strcat(m, ftos(sk));
1874         else
1875                 m = strcat(m, "*");
1876         return strcat(m, ".", fil);
1877 }
1878
1879 float get_model_parameters(string m, float sk)
1880 {
1881         string fn, s, c;
1882         float fh;
1883
1884         get_model_parameters_modelname = string_null;
1885         get_model_parameters_modelskin = -1;
1886         get_model_parameters_name = string_null;
1887         get_model_parameters_species = -1;
1888         get_model_parameters_sex = string_null;
1889         get_model_parameters_weight = -1;
1890         get_model_parameters_age = -1;
1891         get_model_parameters_desc = string_null;
1892
1893         if not(m)
1894                 return 1;
1895         if(sk < 0)
1896         {
1897                 if(substring(m, -4, -1) != ".txt")
1898                         return 0;
1899                 if(substring(m, -6, 1) != "_")
1900                         return 0;
1901                 sk = stof(substring(m, -5, 1));
1902                 m = substring(m, 0, -7);
1903         }
1904
1905         fn = get_model_datafilename(m, sk, "txt");
1906         fh = fopen(fn, FILE_READ);
1907         if(fh < 0)
1908                 return 0;
1909
1910         get_model_parameters_modelname = m;
1911         get_model_parameters_modelskin = sk;
1912         while((s = fgets(fh)))
1913         {
1914                 if(s == "")
1915                         break; // next lines will be description
1916                 c = car(s);
1917                 s = cdr(s);
1918                 if(c == "name")
1919                         get_model_parameters_name = s;
1920                 if(c == "species")
1921                         switch(s)
1922                         {
1923                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
1924                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
1925                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1926                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1927                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1928                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
1929                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
1930                         }
1931                 if(c == "sex")
1932                         get_model_parameters_sex = s;
1933                 if(c == "weight")
1934                         get_model_parameters_weight = stof(s);
1935                 if(c == "age")
1936                         get_model_parameters_age = stof(s);
1937         }
1938
1939         while((s = fgets(fh)))
1940         {
1941                 if(get_model_parameters_desc)
1942                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1943                 if(s != "")
1944                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1945         }
1946
1947         fclose(fh);
1948
1949         return 1;
1950 }
1951
1952 // return name of given panel id
1953 string HUD_Panel_GetName(float id)
1954 {
1955         switch(id) {
1956                 case HUD_PANEL_WEAPONICONS: return "weaponicons"; break;
1957                 case HUD_PANEL_INVENTORY: return "inventory"; break;
1958                 case HUD_PANEL_POWERUPS: return "powerups"; break;
1959                 case HUD_PANEL_HEALTHARMOR: return "healtharmor"; break;
1960                 case HUD_PANEL_NOTIFY: return "notify"; break;
1961                 case HUD_PANEL_TIMER: return "timer"; break;
1962                 case HUD_PANEL_RADAR: return "radar"; break;
1963                 case HUD_PANEL_SCORE: return "score"; break;
1964                 case HUD_PANEL_RACETIMER: return "racetimer"; break;
1965                 case HUD_PANEL_VOTE: return "vote"; break;
1966                 case HUD_PANEL_MODICONS: return "modicons"; break;
1967                 case HUD_PANEL_PRESSEDKEYS: return "pressedkeys"; break;
1968                 case HUD_PANEL_CHAT: return "chat"; break;
1969                 case HUD_PANEL_ENGINEINFO: return "engineinfo"; break;
1970                 default: return "";
1971         }
1972 }