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