]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
Create a sound list
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / util.qc
1 #include "util.qh"
2
3 #if defined(CSQC)
4         #include "../dpdefs/csprogsdefs.qh"
5     #include "../client/defs.qh"
6     #include "constants.qh"
7         #include "../client/mutators/events.qh"
8     #include "mapinfo.qh"
9     #include "notifications.qh"
10     #include "deathtypes.qh"
11 #elif defined(MENUQC)
12 #elif defined(SVQC)
13         #include "../dpdefs/progsdefs.qh"
14     #include "../dpdefs/dpextensions.qh"
15     #include "constants.qh"
16     #include "../server/autocvars.qh"
17     #include "../server/defs.qh"
18         #include "../server/mutators/events.qh"
19     #include "notifications.qh"
20     #include "deathtypes.qh"
21     #include "mapinfo.qh"
22 #endif
23
24 string wordwrap_buffer;
25
26 void wordwrap_buffer_put(string s)
27 {
28         wordwrap_buffer = strcat(wordwrap_buffer, s);
29 }
30
31 string wordwrap(string s, float l)
32 {
33         string r;
34         wordwrap_buffer = "";
35         wordwrap_cb(s, l, wordwrap_buffer_put);
36         r = wordwrap_buffer;
37         wordwrap_buffer = "";
38         return r;
39 }
40
41 #ifndef MENUQC
42 #ifndef CSQC
43 void wordwrap_buffer_sprint(string s)
44 {SELFPARAM();
45         wordwrap_buffer = strcat(wordwrap_buffer, s);
46         if(s == "\n")
47         {
48                 sprint(self, wordwrap_buffer);
49                 wordwrap_buffer = "";
50         }
51 }
52
53 void wordwrap_sprint(string s, float l)
54 {SELFPARAM();
55         wordwrap_buffer = "";
56         wordwrap_cb(s, l, wordwrap_buffer_sprint);
57         if(wordwrap_buffer != "")
58                 sprint(self, strcat(wordwrap_buffer, "\n"));
59         wordwrap_buffer = "";
60         return;
61 }
62 #endif
63 #endif
64
65 #ifndef SVQC
66 string draw_UseSkinFor(string pic)
67 {
68         if(substring(pic, 0, 1) == "/")
69                 return substring(pic, 1, strlen(pic)-1);
70         else
71                 return strcat(draw_currentSkin, "/", pic);
72 }
73 #endif
74
75 string unescape(string in)
76 {
77         float i, len;
78         string str, s;
79
80         // but it doesn't seem to be necessary in my tests at least
81         in = strzone(in);
82
83         len = strlen(in);
84         str = "";
85         for(i = 0; i < len; ++i)
86         {
87                 s = substring(in, i, 1);
88                 if(s == "\\")
89                 {
90                         s = substring(in, i+1, 1);
91                         if(s == "n")
92                                 str = strcat(str, "\n");
93                         else if(s == "\\")
94                                 str = strcat(str, "\\");
95                         else
96                                 str = strcat(str, substring(in, i, 2));
97                         ++i;
98                 } else
99                         str = strcat(str, s);
100         }
101
102         strunzone(in);
103         return str;
104 }
105
106 void wordwrap_cb(string s, float l, void(string) callback)
107 {
108         string c;
109         float lleft, i, j, wlen;
110
111         s = strzone(s);
112         lleft = l;
113         for (i = 0;i < strlen(s);++i)
114         {
115                 if (substring(s, i, 2) == "\\n")
116                 {
117                         callback("\n");
118                         lleft = l;
119                         ++i;
120                 }
121                 else if (substring(s, i, 1) == "\n")
122                 {
123                         callback("\n");
124                         lleft = l;
125                 }
126                 else if (substring(s, i, 1) == " ")
127                 {
128                         if (lleft > 0)
129                         {
130                                 callback(" ");
131                                 lleft = lleft - 1;
132                         }
133                 }
134                 else
135                 {
136                         for (j = i+1;j < strlen(s);++j)
137                                 //    ^^ this skips over the first character of a word, which
138                                 //       is ALWAYS part of the word
139                                 //       this is safe since if i+1 == strlen(s), i will become
140                                 //       strlen(s)-1 at the end of this block and the function
141                                 //       will terminate. A space can't be the first character we
142                                 //       read here, and neither can a \n be the start, since these
143                                 //       two cases have been handled above.
144                         {
145                                 c = substring(s, j, 1);
146                                 if (c == " ")
147                                         break;
148                                 if (c == "\\")
149                                         break;
150                                 if (c == "\n")
151                                         break;
152                                 // we need to keep this tempstring alive even if substring is
153                                 // called repeatedly, so call strcat even though we're not
154                                 // doing anything
155                                 callback("");
156                         }
157                         wlen = j - i;
158                         if (lleft < wlen)
159                         {
160                                 callback("\n");
161                                 lleft = l;
162                         }
163                         callback(substring(s, i, wlen));
164                         lleft = lleft - wlen;
165                         i = j - 1;
166                 }
167         }
168         strunzone(s);
169 }
170
171 float dist_point_line(vector p, vector l0, vector ldir)
172 {
173         ldir = normalize(ldir);
174
175         // remove the component in line direction
176         p = p - (p * ldir) * ldir;
177
178         // vlen of the remaining vector
179         return vlen(p);
180 }
181
182 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
183 {
184         entity e;
185         e = start;
186         funcPre(pass, e);
187         while (e.(downleft))
188         {
189                 e = e.(downleft);
190                 funcPre(pass, e);
191         }
192         funcPost(pass, e);
193         while(e != start)
194         {
195                 if (e.(right))
196                 {
197                         e = e.(right);
198                         funcPre(pass, e);
199                         while (e.(downleft))
200                         {
201                                 e = e.(downleft);
202                                 funcPre(pass, e);
203                         }
204                 }
205                 else
206                         e = e.(up);
207                 funcPost(pass, e);
208         }
209 }
210
211 float median(float a, float b, float c)
212 {
213         if(a < c)
214                 return bound(a, b, c);
215         return bound(c, b, a);
216 }
217
218 // converts a number to a string with the indicated number of decimals
219 // works for up to 10 decimals!
220 string ftos_decimals(float number, float decimals)
221 {
222         // inhibit stupid negative zero
223         if(number == 0)
224                 number = 0;
225         // we have sprintf...
226         return sprintf("%.*f", decimals, number);
227 }
228
229 vector colormapPaletteColor(float c, float isPants)
230 {
231         switch(c)
232         {
233                 case  0: return '1.000000 1.000000 1.000000';
234                 case  1: return '1.000000 0.333333 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.666667 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.000000 0.000000 1.000000';
245                 case 12: return '1.000000 1.000000 0.000000';
246                 case 13: return '0.000000 0.333333 1.000000';
247                 case 14: return '1.000000 0.666667 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 (!s)
268                 return s;
269         sc = strcat(s, "");
270         strunzone(s);
271         return sc;
272 }
273
274 // Databases (hash tables)
275 const float 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                 LOG_INFO(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 int db_create()
293 {
294         return buf_create();
295 }
296
297 int 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         l = fgets(fh);
308         if(stof(l) == DB_BUCKETS)
309         {
310                 i = 0;
311                 while((l = fgets(fh)))
312                 {
313                         if(l != "")
314                                 bufstr_set(db, i, l);
315                         ++i;
316                 }
317         }
318         else
319         {
320                 // different count of buckets, or a dump?
321                 // need to reorganize the database then (SLOW)
322                 //
323                 // note: we also parse the first line (l) in case the DB file is
324                 // missing the bucket count
325                 do
326                 {
327                         n = tokenizebyseparator(l, "\\");
328                         for(j = 2; j < n; j += 2)
329                                 db_put(db, argv(j-1), uri_unescape(argv(j)));
330                 }
331                 while((l = fgets(fh)));
332         }
333         fclose(fh);
334         return db;
335 }
336
337 void db_dump(float db, string pFilename)
338 {
339         float fh, i, j, n, m;
340         fh = fopen(pFilename, FILE_WRITE);
341         if(fh < 0)
342                 error(strcat("Can't dump DB to ", pFilename));
343         n = buf_getsize(db);
344         fputs(fh, "0\n");
345         for(i = 0; i < n; ++i)
346         {
347                 m = tokenizebyseparator(bufstr_get(db, i), "\\");
348                 for(j = 2; j < m; j += 2)
349                         fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
350         }
351         fclose(fh);
352 }
353
354 void db_close(float db)
355 {
356         buf_del(db);
357 }
358
359 string db_get(float db, string pKey)
360 {
361         float h;
362         h = crc16(false, pKey) % DB_BUCKETS;
363         return uri_unescape(infoget(bufstr_get(db, h), pKey));
364 }
365
366 void db_put(float db, string pKey, string pValue)
367 {
368         float h;
369         h = crc16(false, pKey) % DB_BUCKETS;
370         bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
371 }
372
373 void db_test()
374 {
375         float db, i;
376         LOG_INFO("LOAD...\n");
377         db = db_load("foo.db");
378         LOG_INFO("LOADED. FILL...\n");
379         for(i = 0; i < DB_BUCKETS; ++i)
380                 db_put(db, ftos(random()), "X");
381         LOG_INFO("FILLED. SAVE...\n");
382         db_save(db, "foo.db");
383         LOG_INFO("SAVED. CLOSE...\n");
384         db_close(db);
385         LOG_INFO("CLOSED.\n");
386 }
387
388 // Multiline text file buffers
389 int buf_load(string pFilename)
390 {
391         float buf, fh, i;
392         string l;
393         buf = buf_create();
394         if(buf < 0)
395                 return -1;
396         fh = fopen(pFilename, FILE_READ);
397         if(fh < 0)
398         {
399                 buf_del(buf);
400                 return -1;
401         }
402         i = 0;
403         while((l = fgets(fh)))
404         {
405                 bufstr_set(buf, i, l);
406                 ++i;
407         }
408         fclose(fh);
409         return buf;
410 }
411
412 void buf_save(float buf, string pFilename)
413 {
414         float fh, i, n;
415         fh = fopen(pFilename, FILE_WRITE);
416         if(fh < 0)
417                 error(strcat("Can't write buf to ", pFilename));
418         n = buf_getsize(buf);
419         for(i = 0; i < n; ++i)
420                 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
421         fclose(fh);
422 }
423
424 string format_time(float seconds)
425 {
426         float days, hours, minutes;
427         seconds = floor(seconds + 0.5);
428         days = floor(seconds / 864000);
429         seconds -= days * 864000;
430         hours = floor(seconds / 36000);
431         seconds -= hours * 36000;
432         minutes = floor(seconds / 600);
433         seconds -= minutes * 600;
434         if (days > 0)
435                 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
436         else
437                 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
438 }
439
440 string mmsss(float tenths)
441 {
442         float minutes;
443         string s;
444         tenths = floor(tenths + 0.5);
445         minutes = floor(tenths / 600);
446         tenths -= minutes * 600;
447         s = ftos(1000 + tenths);
448         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
449 }
450
451 string mmssss(float hundredths)
452 {
453         float minutes;
454         string s;
455         hundredths = floor(hundredths + 0.5);
456         minutes = floor(hundredths / 6000);
457         hundredths -= minutes * 6000;
458         s = ftos(10000 + hundredths);
459         return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
460 }
461
462 string ScoreString(int pFlags, float pValue)
463 {
464         string valstr;
465         float l;
466
467         pValue = floor(pValue + 0.5); // round
468
469         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
470                 valstr = "";
471         else if(pFlags & SFL_RANK)
472         {
473                 valstr = ftos(pValue);
474                 l = strlen(valstr);
475                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
476                         valstr = strcat(valstr, "th");
477                 else if(substring(valstr, l - 1, 1) == "1")
478                         valstr = strcat(valstr, "st");
479                 else if(substring(valstr, l - 1, 1) == "2")
480                         valstr = strcat(valstr, "nd");
481                 else if(substring(valstr, l - 1, 1) == "3")
482                         valstr = strcat(valstr, "rd");
483                 else
484                         valstr = strcat(valstr, "th");
485         }
486         else if(pFlags & SFL_TIME)
487                 valstr = TIME_ENCODED_TOSTRING(pValue);
488         else
489                 valstr = ftos(pValue);
490
491         return valstr;
492 }
493
494 // compressed vector format:
495 // like MD3, just even shorter
496 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
497 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
498 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
499 //     length = 2^(length_encoded/8) / 8
500 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
501 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
502 // the special value 0 indicates the zero vector
503
504 float lengthLogTable[128];
505
506 float invertLengthLog(float x)
507 {
508         int l, r, m;
509
510         if(x >= lengthLogTable[127])
511                 return 127;
512         if(x <= lengthLogTable[0])
513                 return 0;
514
515         l = 0;
516         r = 127;
517
518         while(r - l > 1)
519         {
520                 m = floor((l + r) / 2);
521                 if(lengthLogTable[m] < x)
522                         l = m;
523                 else
524                         r = m;
525         }
526
527         // now: r is >=, l is <
528         float lerr = (x - lengthLogTable[l]);
529         float rerr = (lengthLogTable[r] - x);
530         if(lerr < rerr)
531                 return l;
532         return r;
533 }
534
535 vector decompressShortVector(int data)
536 {
537         vector out;
538         if(data == 0)
539                 return '0 0 0';
540         float p = (data & 0xF000) / 0x1000;
541         float y = (data & 0x0F80) / 0x80;
542         int len = (data & 0x007F);
543
544         //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
545
546         if(p == 0)
547         {
548                 out.x = 0;
549                 out.y = 0;
550                 if(y == 31)
551                         out.z = -1;
552                 else
553                         out.z = +1;
554         }
555         else
556         {
557                 y   = .19634954084936207740 * y;
558                 p = .19634954084936207740 * p - 1.57079632679489661922;
559                 out.x = cos(y) *  cos(p);
560                 out.y = sin(y) *  cos(p);
561                 out.z =          -sin(p);
562         }
563
564         //print("decompressed: ", vtos(out), "\n");
565
566         return out * lengthLogTable[len];
567 }
568
569 float compressShortVector(vector vec)
570 {
571         vector ang;
572         float p, y, len;
573         if(vlen(vec) == 0)
574                 return 0;
575         //print("compress: ", vtos(vec), "\n");
576         ang = vectoangles(vec);
577         ang.x = -ang.x;
578         if(ang.x < -90)
579                 ang.x += 360;
580         if(ang.x < -90 && ang.x > +90)
581                 error("BOGUS vectoangles");
582         //print("angles: ", vtos(ang), "\n");
583
584         p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
585         if(p == 0)
586         {
587                 if(vec.z < 0)
588                         y = 31;
589                 else
590                         y = 30;
591         }
592         else
593                 y = floor(0.5 + ang.y * 32 / 360)          & 31; // 0..360 to 0..32
594         len = invertLengthLog(vlen(vec));
595
596         //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
597
598         return (p * 0x1000) + (y * 0x80) + len;
599 }
600
601 void compressShortVector_init()
602 {
603         float l = 1;
604         float f = pow(2, 1/8);
605         int i;
606         for(i = 0; i < 128; ++i)
607         {
608                 lengthLogTable[i] = l;
609                 l *= f;
610         }
611
612         if(cvar("developer"))
613         {
614                 LOG_INFO("Verifying vector compression table...\n");
615                 for(i = 0x0F00; i < 0xFFFF; ++i)
616                         if(i != compressShortVector(decompressShortVector(i)))
617                         {
618                                 LOG_INFO("BROKEN vector compression: ", ftos(i));
619                                 LOG_INFO(" -> ", vtos(decompressShortVector(i)));
620                                 LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
621                                 LOG_INFO("\n");
622                                 error("b0rk");
623                         }
624                 LOG_INFO("Done.\n");
625         }
626 }
627
628 #ifndef MENUQC
629 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
630 {
631         traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
632         traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
633         traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
634         traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
635         traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
636         traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
637         traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
638         traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
639         traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
640         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
641         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
642         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
643         return 1;
644 }
645 #endif
646
647 string fixPriorityList(string order, float from, float to, float subtract, float complete)
648 {
649         string neworder;
650         float i, n, w;
651
652         n = tokenize_console(order);
653         neworder = "";
654         for(i = 0; i < n; ++i)
655         {
656                 w = stof(argv(i));
657                 if(w == floor(w))
658                 {
659                         if(w >= from && w <= to)
660                                 neworder = strcat(neworder, ftos(w), " ");
661                         else
662                         {
663                                 w -= subtract;
664                                 if(w >= from && w <= to)
665                                         neworder = strcat(neworder, ftos(w), " ");
666                         }
667                 }
668         }
669
670         if(complete)
671         {
672                 n = tokenize_console(neworder);
673                 for(w = to; w >= from; --w)
674                 {
675                         for(i = 0; i < n; ++i)
676                                 if(stof(argv(i)) == w)
677                                         break;
678                         if(i == n) // not found
679                                 neworder = strcat(neworder, ftos(w), " ");
680                 }
681         }
682
683         return substring(neworder, 0, strlen(neworder) - 1);
684 }
685
686 string mapPriorityList(string order, string(string) mapfunc)
687 {
688         string neworder;
689         float i, n;
690
691         n = tokenize_console(order);
692         neworder = "";
693         for(i = 0; i < n; ++i)
694                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
695
696         return substring(neworder, 0, strlen(neworder) - 1);
697 }
698
699 string swapInPriorityList(string order, float i, float j)
700 {
701         string s;
702         float w, n;
703
704         n = tokenize_console(order);
705
706         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
707         {
708                 s = "";
709                 for(w = 0; w < n; ++w)
710                 {
711                         if(w == i)
712                                 s = strcat(s, argv(j), " ");
713                         else if(w == j)
714                                 s = strcat(s, argv(i), " ");
715                         else
716                                 s = strcat(s, argv(w), " ");
717                 }
718                 return substring(s, 0, strlen(s) - 1);
719         }
720
721         return order;
722 }
723
724 float cvar_value_issafe(string s)
725 {
726         if(strstrofs(s, "\"", 0) >= 0)
727                 return 0;
728         if(strstrofs(s, "\\", 0) >= 0)
729                 return 0;
730         if(strstrofs(s, ";", 0) >= 0)
731                 return 0;
732         if(strstrofs(s, "$", 0) >= 0)
733                 return 0;
734         if(strstrofs(s, "\r", 0) >= 0)
735                 return 0;
736         if(strstrofs(s, "\n", 0) >= 0)
737                 return 0;
738         return 1;
739 }
740
741 #ifndef MENUQC
742 void get_mi_min_max(float mode)
743 {
744         vector mi, ma;
745
746         if(mi_shortname)
747                 strunzone(mi_shortname);
748         mi_shortname = mapname;
749         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
750                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
751         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
752                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
753         mi_shortname = strzone(mi_shortname);
754
755 #ifdef CSQC
756         mi = world.mins;
757         ma = world.maxs;
758 #else
759         mi = world.absmin;
760         ma = world.absmax;
761 #endif
762
763         mi_min = mi;
764         mi_max = ma;
765         MapInfo_Get_ByName(mi_shortname, 0, 0);
766         if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
767         {
768                 mi_min = MapInfo_Map_mins;
769                 mi_max = MapInfo_Map_maxs;
770         }
771         else
772         {
773                 // not specified
774                 if(mode)
775                 {
776                         // be clever
777                         tracebox('1 0 0' * mi.x,
778                                          '0 1 0' * mi.y + '0 0 1' * mi.z,
779                                          '0 1 0' * ma.y + '0 0 1' * ma.z,
780                                          '1 0 0' * ma.x,
781                                          MOVE_WORLDONLY,
782                                          world);
783                         if(!trace_startsolid)
784                                 mi_min.x = trace_endpos.x;
785
786                         tracebox('0 1 0' * mi.y,
787                                          '1 0 0' * mi.x + '0 0 1' * mi.z,
788                                          '1 0 0' * ma.x + '0 0 1' * ma.z,
789                                          '0 1 0' * ma.y,
790                                          MOVE_WORLDONLY,
791                                          world);
792                         if(!trace_startsolid)
793                                 mi_min.y = trace_endpos.y;
794
795                         tracebox('0 0 1' * mi.z,
796                                          '1 0 0' * mi.x + '0 1 0' * mi.y,
797                                          '1 0 0' * ma.x + '0 1 0' * ma.y,
798                                          '0 0 1' * ma.z,
799                                          MOVE_WORLDONLY,
800                                          world);
801                         if(!trace_startsolid)
802                                 mi_min.z = trace_endpos.z;
803
804                         tracebox('1 0 0' * ma.x,
805                                          '0 1 0' * mi.y + '0 0 1' * mi.z,
806                                          '0 1 0' * ma.y + '0 0 1' * ma.z,
807                                          '1 0 0' * mi.x,
808                                          MOVE_WORLDONLY,
809                                          world);
810                         if(!trace_startsolid)
811                                 mi_max.x = trace_endpos.x;
812
813                         tracebox('0 1 0' * ma.y,
814                                          '1 0 0' * mi.x + '0 0 1' * mi.z,
815                                          '1 0 0' * ma.x + '0 0 1' * ma.z,
816                                          '0 1 0' * mi.y,
817                                          MOVE_WORLDONLY,
818                                          world);
819                         if(!trace_startsolid)
820                                 mi_max.y = trace_endpos.y;
821
822                         tracebox('0 0 1' * ma.z,
823                                          '1 0 0' * mi.x + '0 1 0' * mi.y,
824                                          '1 0 0' * ma.x + '0 1 0' * ma.y,
825                                          '0 0 1' * mi.z,
826                                          MOVE_WORLDONLY,
827                                          world);
828                         if(!trace_startsolid)
829                                 mi_max.z = trace_endpos.z;
830                 }
831         }
832 }
833
834 void get_mi_min_max_texcoords(float mode)
835 {
836         vector extend;
837
838         get_mi_min_max(mode);
839
840         mi_picmin = mi_min;
841         mi_picmax = mi_max;
842
843         // extend mi_picmax to get a square aspect ratio
844         // center the map in that area
845         extend = mi_picmax - mi_picmin;
846         if(extend.y > extend.x)
847         {
848                 mi_picmin.x -= (extend.y - extend.x) * 0.5;
849                 mi_picmax.x += (extend.y - extend.x) * 0.5;
850         }
851         else
852         {
853                 mi_picmin.y -= (extend.x - extend.y) * 0.5;
854                 mi_picmax.y += (extend.x - extend.y) * 0.5;
855         }
856
857         // add another some percent
858         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
859         mi_picmin -= extend;
860         mi_picmax += extend;
861
862         // calculate the texcoords
863         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
864         // first the two corners of the origin
865         mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
866         mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
867         mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
868         mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
869         // then the other corners
870         mi_pictexcoord1_x = mi_pictexcoord0_x;
871         mi_pictexcoord1_y = mi_pictexcoord2_y;
872         mi_pictexcoord3_x = mi_pictexcoord2_x;
873         mi_pictexcoord3_y = mi_pictexcoord0_y;
874 }
875 #endif
876
877 float cvar_settemp(string tmp_cvar, string tmp_value)
878 {
879         float created_saved_value;
880         entity e;
881
882         created_saved_value = 0;
883
884         if (!(tmp_cvar || tmp_value))
885         {
886                 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !\n");
887                 return 0;
888         }
889
890         if(!cvar_type(tmp_cvar))
891         {
892                 LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
893                 return 0;
894         }
895
896         for(e = world; (e = find(e, classname, "saved_cvar_value")); )
897                 if(e.netname == tmp_cvar)
898                         created_saved_value = -1; // skip creation
899
900         if(created_saved_value != -1)
901         {
902                 // creating a new entity to keep track of this cvar
903                 e = spawn();
904                 e.classname = "saved_cvar_value";
905                 e.netname = strzone(tmp_cvar);
906                 e.message = strzone(cvar_string(tmp_cvar));
907                 created_saved_value = 1;
908         }
909
910         // update the cvar to the value given
911         cvar_set(tmp_cvar, tmp_value);
912
913         return created_saved_value;
914 }
915
916 float cvar_settemp_restore()
917 {
918         float i = 0;
919         entity e = world;
920         while((e = find(e, classname, "saved_cvar_value")))
921         {
922                 if(cvar_type(e.netname))
923                 {
924                         cvar_set(e.netname, e.message);
925                         remove(e);
926                         ++i;
927                 }
928                 else
929                         LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
930         }
931
932         return i;
933 }
934
935 float almost_equals(float a, float b)
936 {
937         float eps;
938         eps = (max(a, -a) + max(b, -b)) * 0.001;
939         if(a - b < eps && b - a < eps)
940                 return true;
941         return false;
942 }
943
944 float almost_in_bounds(float a, float b, float c)
945 {
946         float eps;
947         eps = (max(a, -a) + max(c, -c)) * 0.001;
948         if(a > c)
949                 eps = -eps;
950         return b == median(a - eps, b, c + eps);
951 }
952
953 float power2of(float e)
954 {
955         return pow(2, e);
956 }
957 float log2of(float x)
958 {
959         // NOTE: generated code
960         if(x > 2048)
961                 if(x > 131072)
962                         if(x > 1048576)
963                                 if(x > 4194304)
964                                         return 23;
965                                 else
966                                         if(x > 2097152)
967                                                 return 22;
968                                         else
969                                                 return 21;
970                         else
971                                 if(x > 524288)
972                                         return 20;
973                                 else
974                                         if(x > 262144)
975                                                 return 19;
976                                         else
977                                                 return 18;
978                 else
979                         if(x > 16384)
980                                 if(x > 65536)
981                                         return 17;
982                                 else
983                                         if(x > 32768)
984                                                 return 16;
985                                         else
986                                                 return 15;
987                         else
988                                 if(x > 8192)
989                                         return 14;
990                                 else
991                                         if(x > 4096)
992                                                 return 13;
993                                         else
994                                                 return 12;
995         else
996                 if(x > 32)
997                         if(x > 256)
998                                 if(x > 1024)
999                                         return 11;
1000                                 else
1001                                         if(x > 512)
1002                                                 return 10;
1003                                         else
1004                                                 return 9;
1005                         else
1006                                 if(x > 128)
1007                                         return 8;
1008                                 else
1009                                         if(x > 64)
1010                                                 return 7;
1011                                         else
1012                                                 return 6;
1013                 else
1014                         if(x > 4)
1015                                 if(x > 16)
1016                                         return 5;
1017                                 else
1018                                         if(x > 8)
1019                                                 return 4;
1020                                         else
1021                                                 return 3;
1022                         else
1023                                 if(x > 2)
1024                                         return 2;
1025                                 else
1026                                         if(x > 1)
1027                                                 return 1;
1028                                         else
1029                                                 return 0;
1030 }
1031
1032 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1033 {
1034         if(mi == ma)
1035                 return 0;
1036         else if(ma == rgb.x)
1037         {
1038                 if(rgb.y >= rgb.z)
1039                         return (rgb.y - rgb.z) / (ma - mi);
1040                 else
1041                         return (rgb.y - rgb.z) / (ma - mi) + 6;
1042         }
1043         else if(ma == rgb.y)
1044                 return (rgb.z - rgb.x) / (ma - mi) + 2;
1045         else // if(ma == rgb_z)
1046                 return (rgb.x - rgb.y) / (ma - mi) + 4;
1047 }
1048
1049 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1050 {
1051         vector rgb;
1052
1053         hue -= 6 * floor(hue / 6);
1054
1055         //else if(ma == rgb_x)
1056         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1057         if(hue <= 1)
1058         {
1059                 rgb.x = ma;
1060                 rgb.y = hue * (ma - mi) + mi;
1061                 rgb.z = mi;
1062         }
1063         //else if(ma == rgb_y)
1064         //      hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1065         else if(hue <= 2)
1066         {
1067                 rgb.x = (2 - hue) * (ma - mi) + mi;
1068                 rgb.y = ma;
1069                 rgb.z = mi;
1070         }
1071         else if(hue <= 3)
1072         {
1073                 rgb.x = mi;
1074                 rgb.y = ma;
1075                 rgb.z = (hue - 2) * (ma - mi) + mi;
1076         }
1077         //else // if(ma == rgb_z)
1078         //      hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1079         else if(hue <= 4)
1080         {
1081                 rgb.x = mi;
1082                 rgb.y = (4 - hue) * (ma - mi) + mi;
1083                 rgb.z = ma;
1084         }
1085         else if(hue <= 5)
1086         {
1087                 rgb.x = (hue - 4) * (ma - mi) + mi;
1088                 rgb.y = mi;
1089                 rgb.z = ma;
1090         }
1091         //else if(ma == rgb_x)
1092         //      hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1093         else // if(hue <= 6)
1094         {
1095                 rgb.x = ma;
1096                 rgb.y = mi;
1097                 rgb.z = (6 - hue) * (ma - mi) + mi;
1098         }
1099
1100         return rgb;
1101 }
1102
1103 vector rgb_to_hsv(vector rgb)
1104 {
1105         float mi, ma;
1106         vector hsv;
1107
1108         mi = min(rgb.x, rgb.y, rgb.z);
1109         ma = max(rgb.x, rgb.y, rgb.z);
1110
1111         hsv.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1112         hsv.z = ma;
1113
1114         if(ma == 0)
1115                 hsv.y = 0;
1116         else
1117                 hsv.y = 1 - mi/ma;
1118
1119         return hsv;
1120 }
1121
1122 vector hsv_to_rgb(vector hsv)
1123 {
1124         return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1125 }
1126
1127 vector rgb_to_hsl(vector rgb)
1128 {
1129         float mi, ma;
1130         vector hsl;
1131
1132         mi = min(rgb.x, rgb.y, rgb.z);
1133         ma = max(rgb.x, rgb.y, rgb.z);
1134
1135         hsl.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1136
1137         hsl.z = 0.5 * (mi + ma);
1138         if(mi == ma)
1139                 hsl.y = 0;
1140         else if(hsl.z <= 0.5)
1141                 hsl.y = (ma - mi) / (2*hsl.z);
1142         else // if(hsl_z > 0.5)
1143                 hsl.y = (ma - mi) / (2 - 2*hsl.z);
1144
1145         return hsl;
1146 }
1147
1148 vector hsl_to_rgb(vector hsl)
1149 {
1150         float mi, ma, maminusmi;
1151
1152         if(hsl.z <= 0.5)
1153                 maminusmi = hsl.y * 2 * hsl.z;
1154         else
1155                 maminusmi = hsl.y * (2 - 2 * hsl.z);
1156
1157         // hsl_z     = 0.5 * mi + 0.5 * ma
1158         // maminusmi =     - mi +       ma
1159         mi = hsl.z - 0.5 * maminusmi;
1160         ma = hsl.z + 0.5 * maminusmi;
1161
1162         return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1163 }
1164
1165 string rgb_to_hexcolor(vector rgb)
1166 {
1167         return
1168                 strcat(
1169                         "^x",
1170                         DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1171                         DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1172                         DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1173                 );
1174 }
1175
1176 // requires that m2>m1 in all coordinates, and that m4>m3
1177 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;}
1178
1179 // requires the same, but is a stronger condition
1180 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;}
1181
1182 #ifndef MENUQC
1183 #endif
1184
1185 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1186 {
1187         // STOP.
1188         // The following function is SLOW.
1189         // For your safety and for the protection of those around you...
1190         // DO NOT CALL THIS AT HOME.
1191         // No really, don't.
1192         if(w(theText, theSize) <= maxWidth)
1193                 return strlen(theText); // yeah!
1194
1195         // binary search for right place to cut string
1196         float ch;
1197         float left, right, middle; // this always works
1198         left = 0;
1199         right = strlen(theText); // this always fails
1200         do
1201         {
1202                 middle = floor((left + right) / 2);
1203                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1204                         left = middle;
1205                 else
1206                         right = middle;
1207         }
1208         while(left < right - 1);
1209
1210         if(w("^7", theSize) == 0) // detect color codes support in the width function
1211         {
1212                 // NOTE: when color codes are involved, this binary search is,
1213                 // mathematically, BROKEN. However, it is obviously guaranteed to
1214                 // terminate, as the range still halves each time - but nevertheless, it is
1215                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1216                 // range, and "right" is outside).
1217
1218                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1219                 // and decrease left on the basis of the chars detected of the truncated tag
1220                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1221                 // (sometimes too much but with a correct result)
1222                 // it fixes also ^[0-9]
1223                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1224                         left-=1;
1225
1226                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1227                         left-=2;
1228                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1229                         {
1230                                 ch = str2chr(theText, left-1);
1231                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1232                                         left-=3;
1233                         }
1234                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1235                         {
1236                                 ch = str2chr(theText, left-2);
1237                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1238                                 {
1239                                         ch = str2chr(theText, left-1);
1240                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1241                                                 left-=4;
1242                                 }
1243                         }
1244         }
1245
1246         return left;
1247 }
1248
1249 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1250 {
1251         // STOP.
1252         // The following function is SLOW.
1253         // For your safety and for the protection of those around you...
1254         // DO NOT CALL THIS AT HOME.
1255         // No really, don't.
1256         if(w(theText) <= maxWidth)
1257                 return strlen(theText); // yeah!
1258
1259         // binary search for right place to cut string
1260         float ch;
1261         float left, right, middle; // this always works
1262         left = 0;
1263         right = strlen(theText); // this always fails
1264         do
1265         {
1266                 middle = floor((left + right) / 2);
1267                 if(w(substring(theText, 0, middle)) <= maxWidth)
1268                         left = middle;
1269                 else
1270                         right = middle;
1271         }
1272         while(left < right - 1);
1273
1274         if(w("^7") == 0) // detect color codes support in the width function
1275         {
1276                 // NOTE: when color codes are involved, this binary search is,
1277                 // mathematically, BROKEN. However, it is obviously guaranteed to
1278                 // terminate, as the range still halves each time - but nevertheless, it is
1279                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1280                 // range, and "right" is outside).
1281
1282                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1283                 // and decrease left on the basis of the chars detected of the truncated tag
1284                 // Even if the ^xrgb tag is not complete/correct, left is decreased
1285                 // (sometimes too much but with a correct result)
1286                 // it fixes also ^[0-9]
1287                 while(left >= 1 && substring(theText, left-1, 1) == "^")
1288                         left-=1;
1289
1290                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1291                         left-=2;
1292                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1293                         {
1294                                 ch = str2chr(theText, left-1);
1295                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1296                                         left-=3;
1297                         }
1298                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1299                         {
1300                                 ch = str2chr(theText, left-2);
1301                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1302                                 {
1303                                         ch = str2chr(theText, left-1);
1304                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1305                                                 left-=4;
1306                                 }
1307                         }
1308         }
1309
1310         return left;
1311 }
1312
1313 string find_last_color_code(string s)
1314 {
1315         int start = strstrofs(s, "^", 0);
1316         if (start == -1) // no caret found
1317                 return "";
1318         int len = strlen(s)-1;
1319         int i;
1320         for(i = len; i >= start; --i)
1321         {
1322                 if(substring(s, i, 1) != "^")
1323                         continue;
1324
1325                 int carets = 1;
1326                 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1327                         ++carets;
1328
1329                 // check if carets aren't all escaped
1330                 if (carets & 1)
1331                 {
1332                         if(i+1 <= len)
1333                         if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1334                                 return substring(s, i, 2);
1335
1336                         if(i+4 <= len)
1337                         if(substring(s, i+1, 1) == "x")
1338                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1339                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1340                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1341                                 return substring(s, i, 5);
1342                 }
1343                 i -= carets; // this also skips one char before the carets
1344         }
1345
1346         return "";
1347 }
1348
1349 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1350 {
1351         float cantake;
1352         float take;
1353         string s;
1354
1355         s = getWrappedLine_remaining;
1356
1357         if(w <= 0)
1358         {
1359                 getWrappedLine_remaining = string_null;
1360                 return s; // the line has no size ANYWAY, nothing would be displayed.
1361         }
1362
1363         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1364         if(cantake > 0 && cantake < strlen(s))
1365         {
1366                 take = cantake - 1;
1367                 while(take > 0 && substring(s, take, 1) != " ")
1368                         --take;
1369                 if(take == 0)
1370                 {
1371                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1372                         if(getWrappedLine_remaining == "")
1373                                 getWrappedLine_remaining = string_null;
1374                         else if (tw("^7", theFontSize) == 0)
1375                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1376                         return substring(s, 0, cantake);
1377                 }
1378                 else
1379                 {
1380                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1381                         if(getWrappedLine_remaining == "")
1382                                 getWrappedLine_remaining = string_null;
1383                         else if (tw("^7", theFontSize) == 0)
1384                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1385                         return substring(s, 0, take);
1386                 }
1387         }
1388         else
1389         {
1390                 getWrappedLine_remaining = string_null;
1391                 return s;
1392         }
1393 }
1394
1395 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1396 {
1397         float cantake;
1398         float take;
1399         string s;
1400
1401         s = getWrappedLine_remaining;
1402
1403         if(w <= 0)
1404         {
1405                 getWrappedLine_remaining = string_null;
1406                 return s; // the line has no size ANYWAY, nothing would be displayed.
1407         }
1408
1409         cantake = textLengthUpToLength(s, w, tw);
1410         if(cantake > 0 && cantake < strlen(s))
1411         {
1412                 take = cantake - 1;
1413                 while(take > 0 && substring(s, take, 1) != " ")
1414                         --take;
1415                 if(take == 0)
1416                 {
1417                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1418                         if(getWrappedLine_remaining == "")
1419                                 getWrappedLine_remaining = string_null;
1420                         else if (tw("^7") == 0)
1421                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1422                         return substring(s, 0, cantake);
1423                 }
1424                 else
1425                 {
1426                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1427                         if(getWrappedLine_remaining == "")
1428                                 getWrappedLine_remaining = string_null;
1429                         else if (tw("^7") == 0)
1430                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1431                         return substring(s, 0, take);
1432                 }
1433         }
1434         else
1435         {
1436                 getWrappedLine_remaining = string_null;
1437                 return s;
1438         }
1439 }
1440
1441 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1442 {
1443         if(tw(theText, theFontSize) <= maxWidth)
1444                 return theText;
1445         else
1446                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1447 }
1448
1449 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1450 {
1451         if(tw(theText) <= maxWidth)
1452                 return theText;
1453         else
1454                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1455 }
1456
1457 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1458 {
1459         string subpattern, subpattern2, subpattern3, subpattern4;
1460         subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1461         if(tp)
1462                 subpattern2 = ",teams,";
1463         else
1464                 subpattern2 = ",noteams,";
1465         if(ts)
1466                 subpattern3 = ",teamspawns,";
1467         else
1468                 subpattern3 = ",noteamspawns,";
1469         if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1470                 subpattern4 = ",race,";
1471         else
1472                 subpattern4 = string_null;
1473
1474         if(substring(pattern, 0, 1) == "-")
1475         {
1476                 pattern = substring(pattern, 1, strlen(pattern) - 1);
1477                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1478                         return 0;
1479                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1480                         return 0;
1481                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1482                         return 0;
1483                 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1484                         return 0;
1485         }
1486         else
1487         {
1488                 if(substring(pattern, 0, 1) == "+")
1489                         pattern = substring(pattern, 1, strlen(pattern) - 1);
1490                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1491                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1492                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1493                 {
1494                         if (!subpattern4)
1495                                 return 0;
1496                         if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1497                                 return 0;
1498                 }
1499         }
1500         return 1;
1501 }
1502
1503 void shuffle(float n, swapfunc_t swap, entity pass)
1504 {
1505         float i, j;
1506         for(i = 1; i < n; ++i)
1507         {
1508                 // swap i-th item at a random position from 0 to i
1509                 // proof for even distribution:
1510                 //   n = 1: obvious
1511                 //   n -> n+1:
1512                 //     item n+1 gets at any position with chance 1/(n+1)
1513                 //     all others will get their 1/n chance reduced by factor n/(n+1)
1514                 //     to be on place n+1, their chance will be 1/(n+1)
1515                 //     1/n * n/(n+1) = 1/(n+1)
1516                 //     q.e.d.
1517                 j = floor(random() * (i + 1));
1518                 if(j != i)
1519                         swap(j, i, pass);
1520         }
1521 }
1522
1523 string substring_range(string s, float b, float e)
1524 {
1525         return substring(s, b, e - b);
1526 }
1527
1528 string swapwords(string str, float i, float j)
1529 {
1530         float n;
1531         string s1, s2, s3, s4, s5;
1532         float si, ei, sj, ej, s0, en;
1533         n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1534         si = argv_start_index(i);
1535         sj = argv_start_index(j);
1536         ei = argv_end_index(i);
1537         ej = argv_end_index(j);
1538         s0 = argv_start_index(0);
1539         en = argv_end_index(n-1);
1540         s1 = substring_range(str, s0, si);
1541         s2 = substring_range(str, si, ei);
1542         s3 = substring_range(str, ei, sj);
1543         s4 = substring_range(str, sj, ej);
1544         s5 = substring_range(str, ej, en);
1545         return strcat(s1, s4, s3, s2, s5);
1546 }
1547
1548 string _shufflewords_str;
1549 void _shufflewords_swapfunc(float i, float j, entity pass)
1550 {
1551         _shufflewords_str = swapwords(_shufflewords_str, i, j);
1552 }
1553 string shufflewords(string str)
1554 {
1555         float n;
1556         _shufflewords_str = str;
1557         n = tokenizebyseparator(str, " ");
1558         shuffle(n, _shufflewords_swapfunc, world);
1559         str = _shufflewords_str;
1560         _shufflewords_str = string_null;
1561         return str;
1562 }
1563
1564 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1565 {
1566         vector v;
1567         float D;
1568         v = '0 0 0';
1569         if(a == 0)
1570         {
1571                 if(b != 0)
1572                 {
1573                         v.x = v.y = -c / b;
1574                         v.z = 1;
1575                 }
1576                 else
1577                 {
1578                         if(c == 0)
1579                         {
1580                                 // actually, every number solves the equation!
1581                                 v.z = 1;
1582                         }
1583                 }
1584         }
1585         else
1586         {
1587                 D = b*b - 4*a*c;
1588                 if(D >= 0)
1589                 {
1590                         D = sqrt(D);
1591                         if(a > 0) // put the smaller solution first
1592                         {
1593                                 v.x = ((-b)-D) / (2*a);
1594                                 v.y = ((-b)+D) / (2*a);
1595                         }
1596                         else
1597                         {
1598                                 v.x = (-b+D) / (2*a);
1599                                 v.y = (-b-D) / (2*a);
1600                         }
1601                         v.z = 1;
1602                 }
1603                 else
1604                 {
1605                         // complex solutions!
1606                         D = sqrt(-D);
1607                         v.x = -b / (2*a);
1608                         if(a > 0)
1609                                 v.y =  D / (2*a);
1610                         else
1611                                 v.y = -D / (2*a);
1612                         v.z = 0;
1613                 }
1614         }
1615         return v;
1616 }
1617
1618 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1619 {
1620         vector ret;
1621
1622         // make origin and speed relative
1623         eorg -= myorg;
1624         if(newton_style)
1625                 evel -= myvel;
1626
1627         // now solve for ret, ret normalized:
1628         //   eorg + t * evel == t * ret * spd
1629         // or, rather, solve for t:
1630         //   |eorg + t * evel| == t * spd
1631         //   eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1632         //   t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1633         vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1634         // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1635         // q = (eorg * eorg) / (evel * evel - spd * spd)
1636         if(!solution.z) // no real solution
1637         {
1638                 // happens if D < 0
1639                 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1640                 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1641                 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1642                 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1643                 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1644                 // spd < |evel| * sin angle(evel, eorg)
1645                 return '0 0 0';
1646         }
1647         else if(solution.x > 0)
1648         {
1649                 // both solutions > 0: take the smaller one
1650                 // happens if p < 0 and q > 0
1651                 ret = normalize(eorg + solution.x * evel);
1652         }
1653         else if(solution.y > 0)
1654         {
1655                 // one solution > 0: take the larger one
1656                 // happens if q < 0 or q == 0 and p < 0
1657                 ret = normalize(eorg + solution.y * evel);
1658         }
1659         else
1660         {
1661                 // no solution > 0: reject
1662                 // happens if p > 0 and q >= 0
1663                 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1664                 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1665                 //
1666                 // |evel| >= spd
1667                 // eorg * evel > 0
1668                 //
1669                 // "Enemy is moving away from me at more than spd"
1670                 return '0 0 0';
1671         }
1672
1673         // NOTE: we always got a solution if spd > |evel|
1674
1675         if(newton_style == 2)
1676                 ret = normalize(ret * spd + myvel);
1677
1678         return ret;
1679 }
1680
1681 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1682 {
1683         if(!newton_style)
1684                 return spd * mydir;
1685
1686         if(newton_style == 2)
1687         {
1688                 // true Newtonian projectiles with automatic aim adjustment
1689                 //
1690                 // solve: |outspeed * mydir - myvel| = spd
1691                 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1692                 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1693                 // PLUS SIGN!
1694                 // not defined?
1695                 // then...
1696                 // myvel^2 - (mydir * myvel)^2 > spd^2
1697                 // velocity without mydir component > spd
1698                 // fire at smallest possible spd that works?
1699                 // |(mydir * myvel) * myvel - myvel| = spd
1700
1701                 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1702
1703                 float outspeed;
1704                 if(solution.z)
1705                         outspeed = solution.y; // the larger one
1706                 else
1707                 {
1708                         //outspeed = 0; // slowest possible shot
1709                         outspeed = solution.x; // the real part (that is, the average!)
1710                         //dprint("impossible shot, adjusting\n");
1711                 }
1712
1713                 outspeed = bound(spd * mi, outspeed, spd * ma);
1714                 return mydir * outspeed;
1715         }
1716
1717         // real Newtonian
1718         return myvel + spd * mydir;
1719 }
1720
1721 float compressShotOrigin(vector v)
1722 {
1723         float x, y, z;
1724         x = rint(v.x * 2);
1725         y = rint(v.y * 4) + 128;
1726         z = rint(v.z * 4) + 128;
1727         if(x > 255 || x < 0)
1728         {
1729                 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1730                 x = bound(0, x, 255);
1731         }
1732         if(y > 255 || y < 0)
1733         {
1734                 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1735                 y = bound(0, y, 255);
1736         }
1737         if(z > 255 || z < 0)
1738         {
1739                 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1740                 z = bound(0, z, 255);
1741         }
1742         return x * 0x10000 + y * 0x100 + z;
1743 }
1744 vector decompressShotOrigin(int f)
1745 {
1746         vector v;
1747         v.x = ((f & 0xFF0000) / 0x10000) / 2;
1748         v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1749         v.z = ((f & 0xFF) - 128) / 4;
1750         return v;
1751 }
1752
1753 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1754 {
1755         float start, end, root, child;
1756
1757         // heapify
1758         start = floor((n - 2) / 2);
1759         while(start >= 0)
1760         {
1761                 // siftdown(start, count-1);
1762                 root = start;
1763                 while(root * 2 + 1 <= n-1)
1764                 {
1765                         child = root * 2 + 1;
1766                         if(child < n-1)
1767                                 if(cmp(child, child+1, pass) < 0)
1768                                         ++child;
1769                         if(cmp(root, child, pass) < 0)
1770                         {
1771                                 swap(root, child, pass);
1772                                 root = child;
1773                         }
1774                         else
1775                                 break;
1776                 }
1777                 // end of siftdown
1778                 --start;
1779         }
1780
1781         // extract
1782         end = n - 1;
1783         while(end > 0)
1784         {
1785                 swap(0, end, pass);
1786                 --end;
1787                 // siftdown(0, end);
1788                 root = 0;
1789                 while(root * 2 + 1 <= end)
1790                 {
1791                         child = root * 2 + 1;
1792                         if(child < end && cmp(child, child+1, pass) < 0)
1793                                 ++child;
1794                         if(cmp(root, child, pass) < 0)
1795                         {
1796                                 swap(root, child, pass);
1797                                 root = child;
1798                         }
1799                         else
1800                                 break;
1801                 }
1802                 // end of siftdown
1803         }
1804 }
1805
1806 void RandomSelection_Init()
1807 {
1808         RandomSelection_totalweight = 0;
1809         RandomSelection_chosen_ent = world;
1810         RandomSelection_chosen_float = 0;
1811         RandomSelection_chosen_string = string_null;
1812         RandomSelection_best_priority = -1;
1813 }
1814 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1815 {
1816         if(priority > RandomSelection_best_priority)
1817         {
1818                 RandomSelection_best_priority = priority;
1819                 RandomSelection_chosen_ent = e;
1820                 RandomSelection_chosen_float = f;
1821                 RandomSelection_chosen_string = s;
1822                 RandomSelection_totalweight = weight;
1823         }
1824         else if(priority == RandomSelection_best_priority)
1825         {
1826                 RandomSelection_totalweight += weight;
1827                 if(random() * RandomSelection_totalweight <= weight)
1828                 {
1829                         RandomSelection_chosen_ent = e;
1830                         RandomSelection_chosen_float = f;
1831                         RandomSelection_chosen_string = s;
1832                 }
1833         }
1834 }
1835
1836 #ifndef MENUQC
1837 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1838 {
1839         // NOTE: we'll always choose the SMALLER value...
1840         float healthdamage, armordamage, armorideal;
1841         if (deathtype == DEATH_DROWN)  // Why should armor help here...
1842                 armorblock = 0;
1843         vector v;
1844         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1845         armordamage = a + (h - 1); // damage we can take if we could use more armor
1846         armorideal = healthdamage * armorblock;
1847         v.y = armorideal;
1848         if(armordamage < healthdamage)
1849         {
1850                 v.x = armordamage;
1851                 v.z = 1;
1852         }
1853         else
1854         {
1855                 v.x = healthdamage;
1856                 v.z = 0;
1857         }
1858         return v;
1859 }
1860
1861 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1862 {
1863         vector v;
1864         if (deathtype == DEATH_DROWN)  // Why should armor help here...
1865                 armorblock = 0;
1866         v.y = bound(0, damage * armorblock, a); // save
1867         v.x = bound(0, damage - v.y, damage); // take
1868         v.z = 0;
1869         return v;
1870 }
1871 #endif
1872
1873 string getcurrentmod()
1874 {
1875         float n;
1876         string m;
1877         m = cvar_string("fs_gamedir");
1878         n = tokenize_console(m);
1879         if(n == 0)
1880                 return "data";
1881         else
1882                 return argv(n - 1);
1883 }
1884
1885 #ifndef MENUQC
1886 #ifdef CSQC
1887 int ReadInt24_t()
1888 {
1889         int v = ReadShort() * 256; // note: this is signed
1890         v += ReadByte(); // note: this is unsigned
1891         return v;
1892 }
1893 vector ReadInt48_t()
1894 {
1895         vector v;
1896         v.x = ReadInt24_t();
1897         v.y = ReadInt24_t();
1898         v.z = 0;
1899         return v;
1900 }
1901 vector ReadInt72_t()
1902 {
1903         vector v;
1904         v.x = ReadInt24_t();
1905         v.y = ReadInt24_t();
1906         v.z = ReadInt24_t();
1907         return v;
1908 }
1909 #else
1910 void WriteInt24_t(float dst, float val)
1911 {
1912         float v;
1913         WriteShort(dst, (v = floor(val / 256)));
1914         WriteByte(dst, val - v * 256); // 0..255
1915 }
1916 void WriteInt48_t(float dst, vector val)
1917 {
1918         WriteInt24_t(dst, val.x);
1919         WriteInt24_t(dst, val.y);
1920 }
1921 void WriteInt72_t(float dst, vector val)
1922 {
1923         WriteInt24_t(dst, val.x);
1924         WriteInt24_t(dst, val.y);
1925         WriteInt24_t(dst, val.z);
1926 }
1927 #endif
1928 #endif
1929
1930 float float2range11(float f)
1931 {
1932         // continuous function mapping all reals into -1..1
1933         return f / (fabs(f) + 1);
1934 }
1935
1936 float float2range01(float f)
1937 {
1938         // continuous function mapping all reals into 0..1
1939         return 0.5 + 0.5 * float2range11(f);
1940 }
1941
1942 // from the GNU Scientific Library
1943 float gsl_ran_gaussian_lastvalue;
1944 float gsl_ran_gaussian_lastvalue_set;
1945 float gsl_ran_gaussian(float sigma)
1946 {
1947         float a, b;
1948         if(gsl_ran_gaussian_lastvalue_set)
1949         {
1950                 gsl_ran_gaussian_lastvalue_set = 0;
1951                 return sigma * gsl_ran_gaussian_lastvalue;
1952         }
1953         else
1954         {
1955                 a = random() * 2 * M_PI;
1956                 b = sqrt(-2 * log(random()));
1957                 gsl_ran_gaussian_lastvalue = cos(a) * b;
1958                 gsl_ran_gaussian_lastvalue_set = 1;
1959                 return sigma * sin(a) * b;
1960         }
1961 }
1962
1963 string car(string s)
1964 {
1965         float o;
1966         o = strstrofs(s, " ", 0);
1967         if(o < 0)
1968                 return s;
1969         return substring(s, 0, o);
1970 }
1971 string cdr(string s)
1972 {
1973         float o;
1974         o = strstrofs(s, " ", 0);
1975         if(o < 0)
1976                 return string_null;
1977         return substring(s, o + 1, strlen(s) - (o + 1));
1978 }
1979 float matchacl(string acl, string str)
1980 {
1981         string t, s;
1982         float r, d;
1983         r = 0;
1984         while(acl)
1985         {
1986                 t = car(acl); acl = cdr(acl);
1987
1988                 d = 1;
1989                 if(substring(t, 0, 1) == "-")
1990                 {
1991                         d = -1;
1992                         t = substring(t, 1, strlen(t) - 1);
1993                 }
1994                 else if(substring(t, 0, 1) == "+")
1995                         t = substring(t, 1, strlen(t) - 1);
1996
1997                 if(substring(t, -1, 1) == "*")
1998                 {
1999                         t = substring(t, 0, strlen(t) - 1);
2000                         s = substring(str, 0, strlen(t));
2001                 }
2002                 else
2003                         s = str;
2004
2005                 if(s == t)
2006                 {
2007                         r = d;
2008                 }
2009         }
2010         return r;
2011 }
2012 float startsWith(string haystack, string needle)
2013 {
2014         return substring(haystack, 0, strlen(needle)) == needle;
2015 }
2016 float startsWithNocase(string haystack, string needle)
2017 {
2018         return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2019 }
2020
2021 string get_model_datafilename(string m, float sk, string fil)
2022 {
2023         if(m)
2024                 m = strcat(m, "_");
2025         else
2026                 m = "models/player/*_";
2027         if(sk >= 0)
2028                 m = strcat(m, ftos(sk));
2029         else
2030                 m = strcat(m, "*");
2031         return strcat(m, ".", fil);
2032 }
2033
2034 float get_model_parameters(string m, float sk)
2035 {
2036         get_model_parameters_modelname = string_null;
2037         get_model_parameters_modelskin = -1;
2038         get_model_parameters_name = string_null;
2039         get_model_parameters_species = -1;
2040         get_model_parameters_sex = string_null;
2041         get_model_parameters_weight = -1;
2042         get_model_parameters_age = -1;
2043         get_model_parameters_desc = string_null;
2044         get_model_parameters_bone_upperbody = string_null;
2045         get_model_parameters_bone_weapon = string_null;
2046         for(int i = 0; i < MAX_AIM_BONES; ++i)
2047         {
2048                 get_model_parameters_bone_aim[i] = string_null;
2049                 get_model_parameters_bone_aimweight[i] = 0;
2050         }
2051         get_model_parameters_fixbone = 0;
2052
2053 #ifndef MENUQC
2054         MUTATOR_CALLHOOK(ClearModelParams);
2055 #endif
2056
2057         if (!m)
2058                 return 1;
2059
2060         if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2061                 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2062
2063         if(sk < 0)
2064         {
2065                 if(substring(m, -4, -1) != ".txt")
2066                         return 0;
2067                 if(substring(m, -6, 1) != "_")
2068                         return 0;
2069                 sk = stof(substring(m, -5, 1));
2070                 m = substring(m, 0, -7);
2071         }
2072
2073         string fn = get_model_datafilename(m, sk, "txt");
2074         int fh = fopen(fn, FILE_READ);
2075         if(fh < 0)
2076         {
2077                 sk = 0;
2078                 fn = get_model_datafilename(m, sk, "txt");
2079                 fh = fopen(fn, FILE_READ);
2080                 if(fh < 0)
2081                         return 0;
2082         }
2083
2084         get_model_parameters_modelname = m;
2085         get_model_parameters_modelskin = sk;
2086         string s, c;
2087         while((s = fgets(fh)))
2088         {
2089                 if(s == "")
2090                         break; // next lines will be description
2091                 c = car(s);
2092                 s = cdr(s);
2093                 if(c == "name")
2094                         get_model_parameters_name = s;
2095                 if(c == "species")
2096                         switch(s)
2097                         {
2098                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
2099                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
2100                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2101                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2102                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2103                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
2104                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
2105                         }
2106                 if(c == "sex")
2107                         get_model_parameters_sex = s;
2108                 if(c == "weight")
2109                         get_model_parameters_weight = stof(s);
2110                 if(c == "age")
2111                         get_model_parameters_age = stof(s);
2112                 if(c == "description")
2113                         get_model_parameters_description = s;
2114                 if(c == "bone_upperbody")
2115                         get_model_parameters_bone_upperbody = s;
2116                 if(c == "bone_weapon")
2117                         get_model_parameters_bone_weapon = s;
2118         #ifndef MENUQC
2119                 MUTATOR_CALLHOOK(GetModelParams, c, s);
2120         #endif
2121                 for(int i = 0; i < MAX_AIM_BONES; ++i)
2122                         if(c == strcat("bone_aim", ftos(i)))
2123                         {
2124                                 get_model_parameters_bone_aimweight[i] = stof(car(s));
2125                                 get_model_parameters_bone_aim[i] = cdr(s);
2126                         }
2127                 if(c == "fixbone")
2128                         get_model_parameters_fixbone = stof(s);
2129         }
2130
2131         while((s = fgets(fh)))
2132         {
2133                 if(get_model_parameters_desc)
2134                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2135                 if(s != "")
2136                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2137         }
2138
2139         fclose(fh);
2140
2141         return 1;
2142 }
2143
2144 vector vec2(vector v)
2145 {
2146         v.z = 0;
2147         return v;
2148 }
2149
2150 #ifndef MENUQC
2151 vector NearestPointOnBox(entity box, vector org)
2152 {
2153         vector m1, m2, nearest;
2154
2155         m1 = box.mins + box.origin;
2156         m2 = box.maxs + box.origin;
2157
2158         nearest.x = bound(m1_x, org.x, m2_x);
2159         nearest.y = bound(m1_y, org.y, m2_y);
2160         nearest.z = bound(m1_z, org.z, m2_z);
2161
2162         return nearest;
2163 }
2164 #endif
2165
2166 float vercmp_recursive(string v1, string v2)
2167 {
2168         float dot1, dot2;
2169         string s1, s2;
2170         float r;
2171
2172         dot1 = strstrofs(v1, ".", 0);
2173         dot2 = strstrofs(v2, ".", 0);
2174         if(dot1 == -1)
2175                 s1 = v1;
2176         else
2177                 s1 = substring(v1, 0, dot1);
2178         if(dot2 == -1)
2179                 s2 = v2;
2180         else
2181                 s2 = substring(v2, 0, dot2);
2182
2183         r = stof(s1) - stof(s2);
2184         if(r != 0)
2185                 return r;
2186
2187         r = strcasecmp(s1, s2);
2188         if(r != 0)
2189                 return r;
2190
2191         if(dot1 == -1)
2192                 if(dot2 == -1)
2193                         return 0;
2194                 else
2195                         return -1;
2196         else
2197                 if(dot2 == -1)
2198                         return 1;
2199                 else
2200                         return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2201 }
2202
2203 float vercmp(string v1, string v2)
2204 {
2205         if(strcasecmp(v1, v2) == 0) // early out check
2206                 return 0;
2207
2208         // "git" beats all
2209         if(v1 == "git")
2210                 return 1;
2211         if(v2 == "git")
2212                 return -1;
2213
2214         return vercmp_recursive(v1, v2);
2215 }
2216
2217 float u8_strsize(string s)
2218 {
2219         float l, i, c;
2220         l = 0;
2221         for(i = 0; ; ++i)
2222         {
2223                 c = str2chr(s, i);
2224                 if(c <= 0)
2225                         break;
2226                 ++l;
2227                 if(c >= 0x80)
2228                         ++l;
2229                 if(c >= 0x800)
2230                         ++l;
2231                 if(c >= 0x10000)
2232                         ++l;
2233         }
2234         return l;
2235 }
2236
2237 // x-encoding (encoding as zero length invisible string)
2238 const string XENCODE_2  = "xX";
2239 const string XENCODE_22 = "0123456789abcdefABCDEF";
2240 string xencode(int f)
2241 {
2242         float a, b, c, d;
2243         d = f % 22; f = floor(f / 22);
2244         c = f % 22; f = floor(f / 22);
2245         b = f % 22; f = floor(f / 22);
2246         a = f %  2; // f = floor(f /  2);
2247         return strcat(
2248                 "^",
2249                 substring(XENCODE_2,  a, 1),
2250                 substring(XENCODE_22, b, 1),
2251                 substring(XENCODE_22, c, 1),
2252                 substring(XENCODE_22, d, 1)
2253         );
2254 }
2255 float xdecode(string s)
2256 {
2257         float a, b, c, d;
2258         if(substring(s, 0, 1) != "^")
2259                 return -1;
2260         if(strlen(s) < 5)
2261                 return -1;
2262         a = strstrofs(XENCODE_2,  substring(s, 1, 1), 0);
2263         b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2264         c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2265         d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2266         if(a < 0 || b < 0 || c < 0 || d < 0)
2267                 return -1;
2268         return ((a * 22 + b) * 22 + c) * 22 + d;
2269 }
2270
2271 int lowestbit(int f)
2272 {
2273         f &= ~(f * 2);
2274         f &= ~(f * 4);
2275         f &= ~(f * 16);
2276         f &= ~(f * 256);
2277         f &= ~(f * 65536);
2278         return f;
2279 }
2280
2281 /*
2282 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2283 {
2284         if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2285                 return input;
2286         else
2287                 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2288 }*/
2289
2290 // escape the string to make it safe for consoles
2291 string MakeConsoleSafe(string input)
2292 {
2293         input = strreplace("\n", "", input);
2294         input = strreplace("\\", "\\\\", input);
2295         input = strreplace("$", "$$", input);
2296         input = strreplace("\"", "\\\"", input);
2297         return input;
2298 }
2299
2300 #ifdef CSQC
2301 entity ReadCSQCEntity()
2302 {
2303         int f = ReadShort();
2304         if(f == 0)
2305                 return world;
2306         return findfloat(world, entnum, f);
2307 }
2308 #endif
2309
2310 float shutdown_running;
2311 #ifdef SVQC
2312 void SV_Shutdown()
2313 #endif
2314 #ifdef CSQC
2315 void CSQC_Shutdown()
2316 #endif
2317 #ifdef MENUQC
2318 void m_shutdown()
2319 #endif
2320 {
2321         if(shutdown_running)
2322         {
2323                 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
2324         }
2325         else
2326         {
2327                 shutdown_running = 1;
2328                 Shutdown();
2329         }
2330         cvar_settemp_restore(); // this must be done LAST, but in any case
2331 }
2332
2333 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2334 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2335 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2336 // this will use the value:
2337 //   128
2338 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2339 // accuracy at x is 1/derivative, i.e.
2340 //   APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2341 #ifdef SVQC
2342 void WriteApproxPastTime(float dst, float t)
2343 {
2344         float dt = time - t;
2345
2346         // warning: this is approximate; do not resend when you don't have to!
2347         // be careful with sendflags here!
2348         // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2349
2350         // map to range...
2351         dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2352
2353         // round...
2354         dt = rint(bound(0, dt, 255));
2355
2356         WriteByte(dst, dt);
2357 }
2358 #endif
2359 #ifdef CSQC
2360 float ReadApproxPastTime()
2361 {
2362         float dt = ReadByte();
2363
2364         // map from range...PPROXPASTTIME_MAX / 256
2365         dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2366
2367         return servertime - dt;
2368 }
2369 #endif
2370
2371 #ifndef MENUQC
2372 .float skeleton_bones_index;
2373 void Skeleton_SetBones(entity e)
2374 {
2375         // set skeleton_bones to the total number of bones on the model
2376         if(e.skeleton_bones_index == e.modelindex)
2377                 return; // same model, nothing to update
2378
2379         float skelindex;
2380         skelindex = skel_create(e.modelindex);
2381         e.skeleton_bones = skel_get_numbones(skelindex);
2382         skel_delete(skelindex);
2383         e.skeleton_bones_index = e.modelindex;
2384 }
2385 #endif
2386
2387 string to_execute_next_frame;
2388 void execute_next_frame()
2389 {
2390         if(to_execute_next_frame)
2391         {
2392                 localcmd("\n", to_execute_next_frame, "\n");
2393                 strunzone(to_execute_next_frame);
2394                 to_execute_next_frame = string_null;
2395         }
2396 }
2397 void queue_to_execute_next_frame(string s)
2398 {
2399         if(to_execute_next_frame)
2400         {
2401                 s = strcat(s, "\n", to_execute_next_frame);
2402                 strunzone(to_execute_next_frame);
2403         }
2404         to_execute_next_frame = strzone(s);
2405 }
2406
2407 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2408 {
2409         return
2410                 (((     startspeedfactor + endspeedfactor - 2
2411                 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2412                 ) * x + startspeedfactor
2413                 ) * x;
2414 }
2415
2416 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2417 {
2418         if(startspeedfactor < 0 || endspeedfactor < 0)
2419                 return false;
2420
2421         /*
2422         // if this is the case, the possible zeros of the first derivative are outside
2423         // 0..1
2424         We can calculate this condition as condition
2425         if(se <= 3)
2426                 return true;
2427         */
2428
2429         // better, see below:
2430         if(startspeedfactor <= 3 && endspeedfactor <= 3)
2431                 return true;
2432
2433         // if this is the case, the first derivative has no zeros at all
2434         float se = startspeedfactor + endspeedfactor;
2435         float s_e = startspeedfactor - endspeedfactor;
2436         if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2437                 return true;
2438
2439         // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2440         // we also get s_e <= 6 - se
2441         // 3 * (se - 4)^2 + (6 - se)^2
2442         // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2443         // Therefore, above "better" check works!
2444
2445         return false;
2446
2447         // known good cases:
2448         // (0, [0..3])
2449         // (0.5, [0..3.8])
2450         // (1, [0..4])
2451         // (1.5, [0..3.9])
2452         // (2, [0..3.7])
2453         // (2.5, [0..3.4])
2454         // (3, [0..3])
2455         // (3.5, [0.2..2.3])
2456         // (4, 1)
2457
2458         /*
2459            On another note:
2460            inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2461
2462            s + e - 2 == 0: no inflection
2463
2464            s + e > 2:
2465            0 < inflection < 1 if:
2466            0 < 2s + e - 3 < 3s + 3e - 6
2467            2s + e > 3 and 2e + s > 3
2468
2469            s + e < 2:
2470            0 < inflection < 1 if:
2471            0 > 2s + e - 3 > 3s + 3e - 6
2472            2s + e < 3 and 2e + s < 3
2473
2474            Therefore: there is an inflection point iff:
2475            e outside (3 - s)/2 .. 3 - s*2
2476
2477            in other words, if (s,e) in triangle (1,1)(0,3)(0,1.5) or in triangle (1,1)(3,0)(1.5,0)
2478         */
2479 }
2480
2481 .float FindConnectedComponent_processing;
2482 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2483 {
2484         entity queue_start, queue_end;
2485
2486         // we build a queue of to-be-processed entities.
2487         // queue_start is the next entity to be checked for neighbors
2488         // queue_end is the last entity added
2489
2490         if(e.FindConnectedComponent_processing)
2491                 error("recursion or broken cleanup");
2492
2493         // start with a 1-element queue
2494         queue_start = queue_end = e;
2495         queue_end.(fld) = world;
2496         queue_end.FindConnectedComponent_processing = 1;
2497
2498         // for each queued item:
2499         for (; queue_start; queue_start = queue_start.(fld))
2500         {
2501                 // find all neighbors of queue_start
2502                 entity t;
2503                 for(t = world; (t = nxt(t, queue_start, pass)); )
2504                 {
2505                         if(t.FindConnectedComponent_processing)
2506                                 continue;
2507                         if(iscon(t, queue_start, pass))
2508                         {
2509                                 // it is connected? ADD IT. It will look for neighbors soon too.
2510                                 queue_end.(fld) = t;
2511                                 queue_end = t;
2512                                 queue_end.(fld) = world;
2513                                 queue_end.FindConnectedComponent_processing = 1;
2514                         }
2515                 }
2516         }
2517
2518         // unmark
2519         for (queue_start = e; queue_start; queue_start = queue_start.(fld))
2520                 queue_start.FindConnectedComponent_processing = 0;
2521 }
2522
2523 #ifdef SVQC
2524 vector combine_to_vector(float x, float y, float z)
2525 {
2526         vector result; result_x = x; result_y = y; result_z = z;
2527         return result;
2528 }
2529
2530 vector get_corner_position(entity box, float corner)
2531 {
2532         switch(corner)
2533         {
2534                 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2535                 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2536                 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2537                 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2538                 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2539                 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2540                 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2541                 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2542                 default: return '0 0 0';
2543         }
2544 }
2545 #endif
2546
2547 // color code replace, place inside of sprintf and parse the string
2548 string CCR(string input)
2549 {
2550         // See the autocvar declarations in util.qh for default values
2551
2552         // foreground/normal colors
2553         input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2554         input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2555         input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2556         input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2557
2558         // "kill" colors
2559         input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2560         input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2561         input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2562
2563         // background colors
2564         input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2565         input = strreplace("^N", "^7", input); // "none"-- reset to white...
2566         return input;
2567 }
2568
2569 vector vec3(float x, float y, float z)
2570 {
2571         vector v;
2572         v.x = x;
2573         v.y = y;
2574         v.z = z;
2575         return v;
2576 }
2577
2578 #ifndef MENUQC
2579 vector animfixfps(entity e, vector a, vector b)
2580 {
2581         // multi-frame anim: keep as-is
2582         if(a.y == 1)
2583         {
2584                 float dur;
2585                 dur = frameduration(e.modelindex, a.x);
2586                 if(dur <= 0 && b.y)
2587                 {
2588                         a = b;
2589                         dur = frameduration(e.modelindex, a.x);
2590                 }
2591                 if(dur > 0)
2592                         a.z = 1.0 / dur;
2593         }
2594         return a;
2595 }
2596 #endif
2597
2598 #ifdef SVQC
2599 void dedicated_print(string input) // print(), but only print if the server is not local
2600 {
2601         if(server_is_dedicated) { LOG_INFO(input); }
2602 }
2603 #endif
2604
2605 #ifndef MENUQC
2606 float Announcer_PickNumber(float type, float num)
2607 {
2608         switch(type)
2609         {
2610                 case CNT_GAMESTART:
2611                 {
2612                         switch(num)
2613                         {
2614                                 case 10: return ANNCE_NUM_GAMESTART_10;
2615                                 case 9:  return ANNCE_NUM_GAMESTART_9;
2616                                 case 8:  return ANNCE_NUM_GAMESTART_8;
2617                                 case 7:  return ANNCE_NUM_GAMESTART_7;
2618                                 case 6:  return ANNCE_NUM_GAMESTART_6;
2619                                 case 5:  return ANNCE_NUM_GAMESTART_5;
2620                                 case 4:  return ANNCE_NUM_GAMESTART_4;
2621                                 case 3:  return ANNCE_NUM_GAMESTART_3;
2622                                 case 2:  return ANNCE_NUM_GAMESTART_2;
2623                                 case 1:  return ANNCE_NUM_GAMESTART_1;
2624                         }
2625                         break;
2626                 }
2627                 case CNT_IDLE:
2628                 {
2629                         switch(num)
2630                         {
2631                                 case 10: return ANNCE_NUM_IDLE_10;
2632                                 case 9:  return ANNCE_NUM_IDLE_9;
2633                                 case 8:  return ANNCE_NUM_IDLE_8;
2634                                 case 7:  return ANNCE_NUM_IDLE_7;
2635                                 case 6:  return ANNCE_NUM_IDLE_6;
2636                                 case 5:  return ANNCE_NUM_IDLE_5;
2637                                 case 4:  return ANNCE_NUM_IDLE_4;
2638                                 case 3:  return ANNCE_NUM_IDLE_3;
2639                                 case 2:  return ANNCE_NUM_IDLE_2;
2640                                 case 1:  return ANNCE_NUM_IDLE_1;
2641                         }
2642                         break;
2643                 }
2644                 case CNT_KILL:
2645                 {
2646                         switch(num)
2647                         {
2648                                 case 10: return ANNCE_NUM_KILL_10;
2649                                 case 9:  return ANNCE_NUM_KILL_9;
2650                                 case 8:  return ANNCE_NUM_KILL_8;
2651                                 case 7:  return ANNCE_NUM_KILL_7;
2652                                 case 6:  return ANNCE_NUM_KILL_6;
2653                                 case 5:  return ANNCE_NUM_KILL_5;
2654                                 case 4:  return ANNCE_NUM_KILL_4;
2655                                 case 3:  return ANNCE_NUM_KILL_3;
2656                                 case 2:  return ANNCE_NUM_KILL_2;
2657                                 case 1:  return ANNCE_NUM_KILL_1;
2658                         }
2659                         break;
2660                 }
2661                 case CNT_RESPAWN:
2662                 {
2663                         switch(num)
2664                         {
2665                                 case 10: return ANNCE_NUM_RESPAWN_10;
2666                                 case 9:  return ANNCE_NUM_RESPAWN_9;
2667                                 case 8:  return ANNCE_NUM_RESPAWN_8;
2668                                 case 7:  return ANNCE_NUM_RESPAWN_7;
2669                                 case 6:  return ANNCE_NUM_RESPAWN_6;
2670                                 case 5:  return ANNCE_NUM_RESPAWN_5;
2671                                 case 4:  return ANNCE_NUM_RESPAWN_4;
2672                                 case 3:  return ANNCE_NUM_RESPAWN_3;
2673                                 case 2:  return ANNCE_NUM_RESPAWN_2;
2674                                 case 1:  return ANNCE_NUM_RESPAWN_1;
2675                         }
2676                         break;
2677                 }
2678                 case CNT_ROUNDSTART:
2679                 {
2680                         switch(num)
2681                         {
2682                                 case 10: return ANNCE_NUM_ROUNDSTART_10;
2683                                 case 9:  return ANNCE_NUM_ROUNDSTART_9;
2684                                 case 8:  return ANNCE_NUM_ROUNDSTART_8;
2685                                 case 7:  return ANNCE_NUM_ROUNDSTART_7;
2686                                 case 6:  return ANNCE_NUM_ROUNDSTART_6;
2687                                 case 5:  return ANNCE_NUM_ROUNDSTART_5;
2688                                 case 4:  return ANNCE_NUM_ROUNDSTART_4;
2689                                 case 3:  return ANNCE_NUM_ROUNDSTART_3;
2690                                 case 2:  return ANNCE_NUM_ROUNDSTART_2;
2691                                 case 1:  return ANNCE_NUM_ROUNDSTART_1;
2692                         }
2693                         break;
2694                 }
2695                 default:
2696                 {
2697                         switch(num)
2698                         {
2699                                 case 10: return ANNCE_NUM_10;
2700                                 case 9:  return ANNCE_NUM_9;
2701                                 case 8:  return ANNCE_NUM_8;
2702                                 case 7:  return ANNCE_NUM_7;
2703                                 case 6:  return ANNCE_NUM_6;
2704                                 case 5:  return ANNCE_NUM_5;
2705                                 case 4:  return ANNCE_NUM_4;
2706                                 case 3:  return ANNCE_NUM_3;
2707                                 case 2:  return ANNCE_NUM_2;
2708                                 case 1:  return ANNCE_NUM_1;
2709                         }
2710                         break;
2711                 }
2712         }
2713         return NOTIF_ABORT; // abort sending if none of these numbers were right
2714 }
2715 #endif
2716
2717 #ifndef MENUQC
2718 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2719 {
2720         switch(nativecontents)
2721         {
2722                 case CONTENT_EMPTY:
2723                         return 0;
2724                 case CONTENT_SOLID:
2725                         return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2726                 case CONTENT_WATER:
2727                         return DPCONTENTS_WATER;
2728                 case CONTENT_SLIME:
2729                         return DPCONTENTS_SLIME;
2730                 case CONTENT_LAVA:
2731                         return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2732                 case CONTENT_SKY:
2733                         return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2734         }
2735         return 0;
2736 }
2737
2738 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2739 {
2740         if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2741                 return CONTENT_SOLID;
2742         if(supercontents & DPCONTENTS_SKY)
2743                 return CONTENT_SKY;
2744         if(supercontents & DPCONTENTS_LAVA)
2745                 return CONTENT_LAVA;
2746         if(supercontents & DPCONTENTS_SLIME)
2747                 return CONTENT_SLIME;
2748         if(supercontents & DPCONTENTS_WATER)
2749                 return CONTENT_WATER;
2750         return CONTENT_EMPTY;
2751 }
2752 #endif
2753
2754 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2755 {
2756         return
2757                 (c - 2 * b + a) * (t * t) +
2758                 (b - a) * (2 * t) +
2759                 a;
2760 }
2761
2762 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2763 {
2764         return
2765                 (c - 2 * b + a) * (2 * t) +
2766                 (b - a) * 2;
2767 }