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