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