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