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