]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/util.qc
Merge branch 'master' into terencehill/translate_colors_2
[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/all.qh"
9     #include <common/deathtypes/all.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/all.qh"
17     #include <common/deathtypes/all.qh>
18     #include "mapinfo.qh"
19 #endif
20
21 #ifndef MENUQC
22 /*
23 * Get "real" origin, in worldspace, even if ent is attached to something else.
24 */
25 vector real_origin(entity ent)
26 {
27         entity e;
28         vector v = ((ent.absmin + ent.absmax) * 0.5);
29
30         e = ent.tag_entity;
31         while(e)
32         {
33                 v = v + ((e.absmin + e.absmax) * 0.5);
34                 e = e.tag_entity;
35         }
36
37         return v;
38 }
39 #endif
40
41 string wordwrap_buffer;
42
43 void wordwrap_buffer_put(string s)
44 {
45         wordwrap_buffer = strcat(wordwrap_buffer, s);
46 }
47
48 string wordwrap(string s, float l)
49 {
50         string r;
51         wordwrap_buffer = "";
52         wordwrap_cb(s, l, wordwrap_buffer_put);
53         r = wordwrap_buffer;
54         wordwrap_buffer = "";
55         return r;
56 }
57
58 #ifndef MENUQC
59 #ifndef CSQC
60 void wordwrap_buffer_sprint(string s)
61 {SELFPARAM();
62         wordwrap_buffer = strcat(wordwrap_buffer, s);
63         if(s == "\n")
64         {
65                 sprint(self, wordwrap_buffer);
66                 wordwrap_buffer = "";
67         }
68 }
69
70 void wordwrap_sprint(string s, float l)
71 {SELFPARAM();
72         wordwrap_buffer = "";
73         wordwrap_cb(s, l, wordwrap_buffer_sprint);
74         if(wordwrap_buffer != "")
75                 sprint(self, strcat(wordwrap_buffer, "\n"));
76         wordwrap_buffer = "";
77         return;
78 }
79 #endif
80 #endif
81
82 #ifndef SVQC
83 string draw_UseSkinFor(string pic)
84 {
85         if(substring(pic, 0, 1) == "/")
86                 return substring(pic, 1, strlen(pic)-1);
87         else
88                 return strcat(draw_currentSkin, "/", pic);
89 }
90 #endif
91
92 void wordwrap_cb(string s, float l, void(string) callback)
93 {
94         string c;
95         float lleft, i, j, wlen;
96
97         s = strzone(s);
98         lleft = l;
99         for (i = 0;i < strlen(s);++i)
100         {
101                 if (substring(s, i, 2) == "\\n")
102                 {
103                         callback("\n");
104                         lleft = l;
105                         ++i;
106                 }
107                 else if (substring(s, i, 1) == "\n")
108                 {
109                         callback("\n");
110                         lleft = l;
111                 }
112                 else if (substring(s, i, 1) == " ")
113                 {
114                         if (lleft > 0)
115                         {
116                                 callback(" ");
117                                 lleft = lleft - 1;
118                         }
119                 }
120                 else
121                 {
122                         for (j = i+1;j < strlen(s);++j)
123                                 //    ^^ this skips over the first character of a word, which
124                                 //       is ALWAYS part of the word
125                                 //       this is safe since if i+1 == strlen(s), i will become
126                                 //       strlen(s)-1 at the end of this block and the function
127                                 //       will terminate. A space can't be the first character we
128                                 //       read here, and neither can a \n be the start, since these
129                                 //       two cases have been handled above.
130                         {
131                                 c = substring(s, j, 1);
132                                 if (c == " ")
133                                         break;
134                                 if (c == "\\")
135                                         break;
136                                 if (c == "\n")
137                                         break;
138                                 // we need to keep this tempstring alive even if substring is
139                                 // called repeatedly, so call strcat even though we're not
140                                 // doing anything
141                                 callback("");
142                         }
143                         wlen = j - i;
144                         if (lleft < wlen)
145                         {
146                                 callback("\n");
147                                 lleft = l;
148                         }
149                         callback(substring(s, i, wlen));
150                         lleft = lleft - wlen;
151                         i = j - 1;
152                 }
153         }
154         strunzone(s);
155 }
156
157 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
158 {
159         entity e;
160         e = start;
161         funcPre(pass, e);
162         while (e.(downleft))
163         {
164                 e = e.(downleft);
165                 funcPre(pass, e);
166         }
167         funcPost(pass, e);
168         while(e != start)
169         {
170                 if (e.(right))
171                 {
172                         e = e.(right);
173                         funcPre(pass, e);
174                         while (e.(downleft))
175                         {
176                                 e = e.(downleft);
177                                 funcPre(pass, e);
178                         }
179                 }
180                 else
181                         e = e.(up);
182                 funcPost(pass, e);
183         }
184 }
185
186 string ScoreString(int pFlags, float pValue)
187 {
188         string valstr;
189         float l;
190
191         pValue = floor(pValue + 0.5); // round
192
193         if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
194                 valstr = "";
195         else if(pFlags & SFL_RANK)
196         {
197                 valstr = ftos(pValue);
198                 l = strlen(valstr);
199                 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
200                         valstr = strcat(valstr, "th");
201                 else if(substring(valstr, l - 1, 1) == "1")
202                         valstr = strcat(valstr, "st");
203                 else if(substring(valstr, l - 1, 1) == "2")
204                         valstr = strcat(valstr, "nd");
205                 else if(substring(valstr, l - 1, 1) == "3")
206                         valstr = strcat(valstr, "rd");
207                 else
208                         valstr = strcat(valstr, "th");
209         }
210         else if(pFlags & SFL_TIME)
211                 valstr = TIME_ENCODED_TOSTRING(pValue);
212         else
213                 valstr = ftos(pValue);
214
215         return valstr;
216 }
217
218 // compressed vector format:
219 // like MD3, just even shorter
220 //   4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
221 //   5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
222 //   7 bit length (logarithmic encoding), 1/8 .. about 7844
223 //     length = 2^(length_encoded/8) / 8
224 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
225 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
226 // the special value 0 indicates the zero vector
227
228 float lengthLogTable[128];
229
230 float invertLengthLog(float x)
231 {
232         int l, r, m;
233
234         if(x >= lengthLogTable[127])
235                 return 127;
236         if(x <= lengthLogTable[0])
237                 return 0;
238
239         l = 0;
240         r = 127;
241
242         while(r - l > 1)
243         {
244                 m = floor((l + r) / 2);
245                 if(lengthLogTable[m] < x)
246                         l = m;
247                 else
248                         r = m;
249         }
250
251         // now: r is >=, l is <
252         float lerr = (x - lengthLogTable[l]);
253         float rerr = (lengthLogTable[r] - x);
254         if(lerr < rerr)
255                 return l;
256         return r;
257 }
258
259 vector decompressShortVector(int data)
260 {
261         vector out;
262         if(data == 0)
263                 return '0 0 0';
264         float p = (data & 0xF000) / 0x1000;
265         float y = (data & 0x0F80) / 0x80;
266         int len = (data & 0x007F);
267
268         //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
269
270         if(p == 0)
271         {
272                 out.x = 0;
273                 out.y = 0;
274                 if(y == 31)
275                         out.z = -1;
276                 else
277                         out.z = +1;
278         }
279         else
280         {
281                 y   = .19634954084936207740 * y;
282                 p = .19634954084936207740 * p - 1.57079632679489661922;
283                 out.x = cos(y) *  cos(p);
284                 out.y = sin(y) *  cos(p);
285                 out.z =          -sin(p);
286         }
287
288         //print("decompressed: ", vtos(out), "\n");
289
290         return out * lengthLogTable[len];
291 }
292
293 float compressShortVector(vector vec)
294 {
295         vector ang;
296         float p, y, len;
297         if(vlen(vec) == 0)
298                 return 0;
299         //print("compress: ", vtos(vec), "\n");
300         ang = vectoangles(vec);
301         ang.x = -ang.x;
302         if(ang.x < -90)
303                 ang.x += 360;
304         if(ang.x < -90 && ang.x > +90)
305                 error("BOGUS vectoangles");
306         //print("angles: ", vtos(ang), "\n");
307
308         p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
309         if(p == 0)
310         {
311                 if(vec.z < 0)
312                         y = 31;
313                 else
314                         y = 30;
315         }
316         else
317                 y = floor(0.5 + ang.y * 32 / 360)          & 31; // 0..360 to 0..32
318         len = invertLengthLog(vlen(vec));
319
320         //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
321
322         return (p * 0x1000) + (y * 0x80) + len;
323 }
324
325 void compressShortVector_init()
326 {
327         float l = 1;
328         float f = pow(2, 1/8);
329         int i;
330         for(i = 0; i < 128; ++i)
331         {
332                 lengthLogTable[i] = l;
333                 l *= f;
334         }
335
336         if(cvar("developer"))
337         {
338                 LOG_INFO("Verifying vector compression table...\n");
339                 for(i = 0x0F00; i < 0xFFFF; ++i)
340                         if(i != compressShortVector(decompressShortVector(i)))
341                         {
342                                 LOG_INFO("BROKEN vector compression: ", ftos(i));
343                                 LOG_INFO(" -> ", vtos(decompressShortVector(i)));
344                                 LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
345                                 LOG_INFO("\n");
346                                 error("b0rk");
347                         }
348                 LOG_INFO("Done.\n");
349         }
350 }
351
352 #ifndef MENUQC
353 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
354 {
355         traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
356         traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
357         traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
358         traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
359         traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
360         traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
361         traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
362         traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
363         traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
364         traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
365         traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
366         traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
367         return 1;
368 }
369 #endif
370
371 string fixPriorityList(string order, float from, float to, float subtract, float complete)
372 {
373         string neworder;
374         float i, n, w;
375
376         n = tokenize_console(order);
377         neworder = "";
378         for(i = 0; i < n; ++i)
379         {
380                 w = stof(argv(i));
381                 if(w == floor(w))
382                 {
383                         if(w >= from && w <= to)
384                                 neworder = strcat(neworder, ftos(w), " ");
385                         else
386                         {
387                                 w -= subtract;
388                                 if(w >= from && w <= to)
389                                         neworder = strcat(neworder, ftos(w), " ");
390                         }
391                 }
392         }
393
394         if(complete)
395         {
396                 n = tokenize_console(neworder);
397                 for(w = to; w >= from; --w)
398                 {
399                         for(i = 0; i < n; ++i)
400                                 if(stof(argv(i)) == w)
401                                         break;
402                         if(i == n) // not found
403                                 neworder = strcat(neworder, ftos(w), " ");
404                 }
405         }
406
407         return substring(neworder, 0, strlen(neworder) - 1);
408 }
409
410 string mapPriorityList(string order, string(string) mapfunc)
411 {
412         string neworder;
413         float i, n;
414
415         n = tokenize_console(order);
416         neworder = "";
417         for(i = 0; i < n; ++i)
418                 neworder = strcat(neworder, mapfunc(argv(i)), " ");
419
420         return substring(neworder, 0, strlen(neworder) - 1);
421 }
422
423 string swapInPriorityList(string order, float i, float j)
424 {
425         string s;
426         float w, n;
427
428         n = tokenize_console(order);
429
430         if(i >= 0 && i < n && j >= 0 && j < n && i != j)
431         {
432                 s = "";
433                 for(w = 0; w < n; ++w)
434                 {
435                         if(w == i)
436                                 s = strcat(s, argv(j), " ");
437                         else if(w == j)
438                                 s = strcat(s, argv(i), " ");
439                         else
440                                 s = strcat(s, argv(w), " ");
441                 }
442                 return substring(s, 0, strlen(s) - 1);
443         }
444
445         return order;
446 }
447
448 #ifndef MENUQC
449 void get_mi_min_max(float mode)
450 {
451         vector mi, ma;
452
453         if(mi_shortname)
454                 strunzone(mi_shortname);
455         mi_shortname = mapname;
456         if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
457                 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
458         if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
459                 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
460         mi_shortname = strzone(mi_shortname);
461
462 #ifdef CSQC
463         mi = world.mins;
464         ma = world.maxs;
465 #else
466         mi = world.absmin;
467         ma = world.absmax;
468 #endif
469
470         mi_min = mi;
471         mi_max = ma;
472         MapInfo_Get_ByName(mi_shortname, 0, 0);
473         if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
474         {
475                 mi_min = MapInfo_Map_mins;
476                 mi_max = MapInfo_Map_maxs;
477         }
478         else
479         {
480                 // not specified
481                 if(mode)
482                 {
483                         // be clever
484                         tracebox('1 0 0' * mi.x,
485                                          '0 1 0' * mi.y + '0 0 1' * mi.z,
486                                          '0 1 0' * ma.y + '0 0 1' * ma.z,
487                                          '1 0 0' * ma.x,
488                                          MOVE_WORLDONLY,
489                                          world);
490                         if(!trace_startsolid)
491                                 mi_min.x = trace_endpos.x;
492
493                         tracebox('0 1 0' * mi.y,
494                                          '1 0 0' * mi.x + '0 0 1' * mi.z,
495                                          '1 0 0' * ma.x + '0 0 1' * ma.z,
496                                          '0 1 0' * ma.y,
497                                          MOVE_WORLDONLY,
498                                          world);
499                         if(!trace_startsolid)
500                                 mi_min.y = trace_endpos.y;
501
502                         tracebox('0 0 1' * mi.z,
503                                          '1 0 0' * mi.x + '0 1 0' * mi.y,
504                                          '1 0 0' * ma.x + '0 1 0' * ma.y,
505                                          '0 0 1' * ma.z,
506                                          MOVE_WORLDONLY,
507                                          world);
508                         if(!trace_startsolid)
509                                 mi_min.z = trace_endpos.z;
510
511                         tracebox('1 0 0' * ma.x,
512                                          '0 1 0' * mi.y + '0 0 1' * mi.z,
513                                          '0 1 0' * ma.y + '0 0 1' * ma.z,
514                                          '1 0 0' * mi.x,
515                                          MOVE_WORLDONLY,
516                                          world);
517                         if(!trace_startsolid)
518                                 mi_max.x = trace_endpos.x;
519
520                         tracebox('0 1 0' * ma.y,
521                                          '1 0 0' * mi.x + '0 0 1' * mi.z,
522                                          '1 0 0' * ma.x + '0 0 1' * ma.z,
523                                          '0 1 0' * mi.y,
524                                          MOVE_WORLDONLY,
525                                          world);
526                         if(!trace_startsolid)
527                                 mi_max.y = trace_endpos.y;
528
529                         tracebox('0 0 1' * ma.z,
530                                          '1 0 0' * mi.x + '0 1 0' * mi.y,
531                                          '1 0 0' * ma.x + '0 1 0' * ma.y,
532                                          '0 0 1' * mi.z,
533                                          MOVE_WORLDONLY,
534                                          world);
535                         if(!trace_startsolid)
536                                 mi_max.z = trace_endpos.z;
537                 }
538         }
539 }
540
541 void get_mi_min_max_texcoords(float mode)
542 {
543         vector extend;
544
545         get_mi_min_max(mode);
546
547         mi_picmin = mi_min;
548         mi_picmax = mi_max;
549
550         // extend mi_picmax to get a square aspect ratio
551         // center the map in that area
552         extend = mi_picmax - mi_picmin;
553         if(extend.y > extend.x)
554         {
555                 mi_picmin.x -= (extend.y - extend.x) * 0.5;
556                 mi_picmax.x += (extend.y - extend.x) * 0.5;
557         }
558         else
559         {
560                 mi_picmin.y -= (extend.x - extend.y) * 0.5;
561                 mi_picmax.y += (extend.x - extend.y) * 0.5;
562         }
563
564         // add another some percent
565         extend = (mi_picmax - mi_picmin) * (1 / 64.0);
566         mi_picmin -= extend;
567         mi_picmax += extend;
568
569         // calculate the texcoords
570         mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
571         // first the two corners of the origin
572         mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
573         mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
574         mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
575         mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
576         // then the other corners
577         mi_pictexcoord1_x = mi_pictexcoord0_x;
578         mi_pictexcoord1_y = mi_pictexcoord2_y;
579         mi_pictexcoord3_x = mi_pictexcoord2_x;
580         mi_pictexcoord3_y = mi_pictexcoord0_y;
581 }
582 #endif
583
584 float cvar_settemp(string tmp_cvar, string tmp_value)
585 {
586         float created_saved_value;
587
588         created_saved_value = 0;
589
590         if (!(tmp_cvar || tmp_value))
591         {
592                 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !\n");
593                 return 0;
594         }
595
596         if(!cvar_type(tmp_cvar))
597         {
598                 LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
599                 return 0;
600         }
601
602         FOREACH_ENTITY_CLASS("saved_cvar_value", it.netname == tmp_cvar,
603         {
604                 created_saved_value = -1; // skip creation
605                 break; // no need to continue
606         });
607
608         if(created_saved_value != -1)
609         {
610                 // creating a new entity to keep track of this cvar
611                 entity e = new_pure(saved_cvar_value);
612                 e.netname = strzone(tmp_cvar);
613                 e.message = strzone(cvar_string(tmp_cvar));
614                 created_saved_value = 1;
615         }
616
617         // update the cvar to the value given
618         cvar_set(tmp_cvar, tmp_value);
619
620         return created_saved_value;
621 }
622
623 int cvar_settemp_restore()
624 {
625         int j = 0;
626         // FIXME this new-style loop fails!
627 #if 0
628         FOREACH_ENTITY_CLASS("saved_cvar_value", true,
629         {
630                 if(cvar_type(it.netname))
631                 {
632                         cvar_set(it.netname, it.message);
633                         strunzone(it.netname);
634                         strunzone(it.message);
635                         remove(it);
636                         ++j;
637                 }
638                 else
639                         LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", it.netname);
640         });
641
642 #else
643         entity e = world;
644         while((e = find(e, classname, "saved_cvar_value")))
645         {
646                 if(cvar_type(e.netname))
647                 {
648                         cvar_set(e.netname, e.message);
649                         remove(e);
650                         ++j;
651                 }
652                 else
653                         print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname));
654         }
655 #endif
656
657         return j;
658 }
659
660 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
661 {
662         // STOP.
663         // The following function is SLOW.
664         // For your safety and for the protection of those around you...
665         // DO NOT CALL THIS AT HOME.
666         // No really, don't.
667         if(w(theText, theSize) <= maxWidth)
668                 return strlen(theText); // yeah!
669
670         // binary search for right place to cut string
671         float ch;
672         float left, right, middle; // this always works
673         left = 0;
674         right = strlen(theText); // this always fails
675         do
676         {
677                 middle = floor((left + right) / 2);
678                 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
679                         left = middle;
680                 else
681                         right = middle;
682         }
683         while(left < right - 1);
684
685         if(w("^7", theSize) == 0) // detect color codes support in the width function
686         {
687                 // NOTE: when color codes are involved, this binary search is,
688                 // mathematically, BROKEN. However, it is obviously guaranteed to
689                 // terminate, as the range still halves each time - but nevertheless, it is
690                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
691                 // range, and "right" is outside).
692
693                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
694                 // and decrease left on the basis of the chars detected of the truncated tag
695                 // Even if the ^xrgb tag is not complete/correct, left is decreased
696                 // (sometimes too much but with a correct result)
697                 // it fixes also ^[0-9]
698                 while(left >= 1 && substring(theText, left-1, 1) == "^")
699                         left-=1;
700
701                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
702                         left-=2;
703                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
704                         {
705                                 ch = str2chr(theText, left-1);
706                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
707                                         left-=3;
708                         }
709                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
710                         {
711                                 ch = str2chr(theText, left-2);
712                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
713                                 {
714                                         ch = str2chr(theText, left-1);
715                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
716                                                 left-=4;
717                                 }
718                         }
719         }
720
721         return left;
722 }
723
724 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
725 {
726         // STOP.
727         // The following function is SLOW.
728         // For your safety and for the protection of those around you...
729         // DO NOT CALL THIS AT HOME.
730         // No really, don't.
731         if(w(theText) <= maxWidth)
732                 return strlen(theText); // yeah!
733
734         // binary search for right place to cut string
735         float ch;
736         float left, right, middle; // this always works
737         left = 0;
738         right = strlen(theText); // this always fails
739         do
740         {
741                 middle = floor((left + right) / 2);
742                 if(w(substring(theText, 0, middle)) <= maxWidth)
743                         left = middle;
744                 else
745                         right = middle;
746         }
747         while(left < right - 1);
748
749         if(w("^7") == 0) // detect color codes support in the width function
750         {
751                 // NOTE: when color codes are involved, this binary search is,
752                 // mathematically, BROKEN. However, it is obviously guaranteed to
753                 // terminate, as the range still halves each time - but nevertheless, it is
754                 // guaranteed that it finds ONE valid cutoff place (where "left" is in
755                 // range, and "right" is outside).
756
757                 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
758                 // and decrease left on the basis of the chars detected of the truncated tag
759                 // Even if the ^xrgb tag is not complete/correct, left is decreased
760                 // (sometimes too much but with a correct result)
761                 // it fixes also ^[0-9]
762                 while(left >= 1 && substring(theText, left-1, 1) == "^")
763                         left-=1;
764
765                 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
766                         left-=2;
767                 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
768                         {
769                                 ch = str2chr(theText, left-1);
770                                 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
771                                         left-=3;
772                         }
773                 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
774                         {
775                                 ch = str2chr(theText, left-2);
776                                 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
777                                 {
778                                         ch = str2chr(theText, left-1);
779                                         if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
780                                                 left-=4;
781                                 }
782                         }
783         }
784
785         return left;
786 }
787
788 string find_last_color_code(string s)
789 {
790         int start = strstrofs(s, "^", 0);
791         if (start == -1) // no caret found
792                 return "";
793         int len = strlen(s)-1;
794         int i;
795         for(i = len; i >= start; --i)
796         {
797                 if(substring(s, i, 1) != "^")
798                         continue;
799
800                 int carets = 1;
801                 while (i-carets >= start && substring(s, i-carets, 1) == "^")
802                         ++carets;
803
804                 // check if carets aren't all escaped
805                 if (carets & 1)
806                 {
807                         if(i+1 <= len)
808                         if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
809                                 return substring(s, i, 2);
810
811                         if(i+4 <= len)
812                         if(substring(s, i+1, 1) == "x")
813                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
814                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
815                         if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
816                                 return substring(s, i, 5);
817                 }
818                 i -= carets; // this also skips one char before the carets
819         }
820
821         return "";
822 }
823
824 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
825 {
826         float cantake;
827         float take;
828         string s;
829
830         s = getWrappedLine_remaining;
831
832         if(w <= 0)
833         {
834                 getWrappedLine_remaining = string_null;
835                 return s; // the line has no size ANYWAY, nothing would be displayed.
836         }
837
838         cantake = textLengthUpToWidth(s, w, theFontSize, tw);
839         if(cantake > 0 && cantake < strlen(s))
840         {
841                 take = cantake - 1;
842                 while(take > 0 && substring(s, take, 1) != " ")
843                         --take;
844                 if(take == 0)
845                 {
846                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
847                         if(getWrappedLine_remaining == "")
848                                 getWrappedLine_remaining = string_null;
849                         else if (tw("^7", theFontSize) == 0)
850                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
851                         return substring(s, 0, cantake);
852                 }
853                 else
854                 {
855                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
856                         if(getWrappedLine_remaining == "")
857                                 getWrappedLine_remaining = string_null;
858                         else if (tw("^7", theFontSize) == 0)
859                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
860                         return substring(s, 0, take);
861                 }
862         }
863         else
864         {
865                 getWrappedLine_remaining = string_null;
866                 return s;
867         }
868 }
869
870 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
871 {
872         float cantake;
873         float take;
874         string s;
875
876         s = getWrappedLine_remaining;
877
878         if(w <= 0)
879         {
880                 getWrappedLine_remaining = string_null;
881                 return s; // the line has no size ANYWAY, nothing would be displayed.
882         }
883
884         cantake = textLengthUpToLength(s, w, tw);
885         if(cantake > 0 && cantake < strlen(s))
886         {
887                 take = cantake - 1;
888                 while(take > 0 && substring(s, take, 1) != " ")
889                         --take;
890                 if(take == 0)
891                 {
892                         getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
893                         if(getWrappedLine_remaining == "")
894                                 getWrappedLine_remaining = string_null;
895                         else if (tw("^7") == 0)
896                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
897                         return substring(s, 0, cantake);
898                 }
899                 else
900                 {
901                         getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
902                         if(getWrappedLine_remaining == "")
903                                 getWrappedLine_remaining = string_null;
904                         else if (tw("^7") == 0)
905                                 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
906                         return substring(s, 0, take);
907                 }
908         }
909         else
910         {
911                 getWrappedLine_remaining = string_null;
912                 return s;
913         }
914 }
915
916 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
917 {
918         if(tw(theText, theFontSize) <= maxWidth)
919                 return theText;
920         else
921                 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
922 }
923
924 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
925 {
926         if(tw(theText) <= maxWidth)
927                 return theText;
928         else
929                 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
930 }
931
932 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
933 {
934         string subpattern, subpattern2, subpattern3, subpattern4;
935         subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
936         if(tp)
937                 subpattern2 = ",teams,";
938         else
939                 subpattern2 = ",noteams,";
940         if(ts)
941                 subpattern3 = ",teamspawns,";
942         else
943                 subpattern3 = ",noteamspawns,";
944         if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
945                 subpattern4 = ",race,";
946         else
947                 subpattern4 = string_null;
948
949         if(substring(pattern, 0, 1) == "-")
950         {
951                 pattern = substring(pattern, 1, strlen(pattern) - 1);
952                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
953                         return 0;
954                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
955                         return 0;
956                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
957                         return 0;
958                 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
959                         return 0;
960         }
961         else
962         {
963                 if(substring(pattern, 0, 1) == "+")
964                         pattern = substring(pattern, 1, strlen(pattern) - 1);
965                 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
966                 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
967                 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
968                 {
969                         if (!subpattern4)
970                                 return 0;
971                         if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
972                                 return 0;
973                 }
974         }
975         return 1;
976 }
977
978 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
979 {
980         vector ret;
981
982         // make origin and speed relative
983         eorg -= myorg;
984         if(newton_style)
985                 evel -= myvel;
986
987         // now solve for ret, ret normalized:
988         //   eorg + t * evel == t * ret * spd
989         // or, rather, solve for t:
990         //   |eorg + t * evel| == t * spd
991         //   eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
992         //   t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
993         vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
994         // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
995         // q = (eorg * eorg) / (evel * evel - spd * spd)
996         if(!solution.z) // no real solution
997         {
998                 // happens if D < 0
999                 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1000                 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1001                 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1002                 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1003                 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1004                 // spd < |evel| * sin angle(evel, eorg)
1005                 return '0 0 0';
1006         }
1007         else if(solution.x > 0)
1008         {
1009                 // both solutions > 0: take the smaller one
1010                 // happens if p < 0 and q > 0
1011                 ret = normalize(eorg + solution.x * evel);
1012         }
1013         else if(solution.y > 0)
1014         {
1015                 // one solution > 0: take the larger one
1016                 // happens if q < 0 or q == 0 and p < 0
1017                 ret = normalize(eorg + solution.y * evel);
1018         }
1019         else
1020         {
1021                 // no solution > 0: reject
1022                 // happens if p > 0 and q >= 0
1023                 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1024                 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1025                 //
1026                 // |evel| >= spd
1027                 // eorg * evel > 0
1028                 //
1029                 // "Enemy is moving away from me at more than spd"
1030                 return '0 0 0';
1031         }
1032
1033         // NOTE: we always got a solution if spd > |evel|
1034
1035         if(newton_style == 2)
1036                 ret = normalize(ret * spd + myvel);
1037
1038         return ret;
1039 }
1040
1041 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1042 {
1043         if(!newton_style)
1044                 return spd * mydir;
1045
1046         if(newton_style == 2)
1047         {
1048                 // true Newtonian projectiles with automatic aim adjustment
1049                 //
1050                 // solve: |outspeed * mydir - myvel| = spd
1051                 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1052                 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1053                 // PLUS SIGN!
1054                 // not defined?
1055                 // then...
1056                 // myvel^2 - (mydir * myvel)^2 > spd^2
1057                 // velocity without mydir component > spd
1058                 // fire at smallest possible spd that works?
1059                 // |(mydir * myvel) * myvel - myvel| = spd
1060
1061                 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1062
1063                 float outspeed;
1064                 if(solution.z)
1065                         outspeed = solution.y; // the larger one
1066                 else
1067                 {
1068                         //outspeed = 0; // slowest possible shot
1069                         outspeed = solution.x; // the real part (that is, the average!)
1070                         //dprint("impossible shot, adjusting\n");
1071                 }
1072
1073                 outspeed = bound(spd * mi, outspeed, spd * ma);
1074                 return mydir * outspeed;
1075         }
1076
1077         // real Newtonian
1078         return myvel + spd * mydir;
1079 }
1080
1081 float compressShotOrigin(vector v)
1082 {
1083         float x, y, z;
1084         x = rint(v.x * 2);
1085         y = rint(v.y * 4) + 128;
1086         z = rint(v.z * 4) + 128;
1087         if(x > 255 || x < 0)
1088         {
1089                 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1090                 x = bound(0, x, 255);
1091         }
1092         if(y > 255 || y < 0)
1093         {
1094                 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1095                 y = bound(0, y, 255);
1096         }
1097         if(z > 255 || z < 0)
1098         {
1099                 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1100                 z = bound(0, z, 255);
1101         }
1102         return x * 0x10000 + y * 0x100 + z;
1103 }
1104 vector decompressShotOrigin(int f)
1105 {
1106         vector v;
1107         v.x = ((f & 0xFF0000) / 0x10000) / 2;
1108         v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1109         v.z = ((f & 0xFF) - 128) / 4;
1110         return v;
1111 }
1112
1113 #ifndef MENUQC
1114 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1115 {
1116         // NOTE: we'll always choose the SMALLER value...
1117         float healthdamage, armordamage, armorideal;
1118         if (DEATH_IS(deathtype, DEATH_DROWN))  // Why should armor help here...
1119                 armorblock = 0;
1120         vector v;
1121         healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1122         armordamage = a + (h - 1); // damage we can take if we could use more armor
1123         armorideal = healthdamage * armorblock;
1124         v.y = armorideal;
1125         if(armordamage < healthdamage)
1126         {
1127                 v.x = armordamage;
1128                 v.z = 1;
1129         }
1130         else
1131         {
1132                 v.x = healthdamage;
1133                 v.z = 0;
1134         }
1135         return v;
1136 }
1137
1138 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1139 {
1140         vector v;
1141         if (DEATH_IS(deathtype, DEATH_DROWN))  // Why should armor help here...
1142                 armorblock = 0;
1143         v.y = bound(0, damage * armorblock, a); // save
1144         v.x = bound(0, damage - v.y, damage); // take
1145         v.z = 0;
1146         return v;
1147 }
1148 #endif
1149
1150 string getcurrentmod()
1151 {
1152         float n;
1153         string m;
1154         m = cvar_string("fs_gamedir");
1155         n = tokenize_console(m);
1156         if(n == 0)
1157                 return "data";
1158         else
1159                 return argv(n - 1);
1160 }
1161
1162 float matchacl(string acl, string str)
1163 {
1164         string t, s;
1165         float r, d;
1166         r = 0;
1167         while(acl)
1168         {
1169                 t = car(acl); acl = cdr(acl);
1170
1171                 d = 1;
1172                 if(substring(t, 0, 1) == "-")
1173                 {
1174                         d = -1;
1175                         t = substring(t, 1, strlen(t) - 1);
1176                 }
1177                 else if(substring(t, 0, 1) == "+")
1178                         t = substring(t, 1, strlen(t) - 1);
1179
1180                 if(substring(t, -1, 1) == "*")
1181                 {
1182                         t = substring(t, 0, strlen(t) - 1);
1183                         s = substring(str, 0, strlen(t));
1184                 }
1185                 else
1186                         s = str;
1187
1188                 if(s == t)
1189                 {
1190                         r = d;
1191                 }
1192         }
1193         return r;
1194 }
1195
1196 string get_model_datafilename(string m, float sk, string fil)
1197 {
1198         if(m)
1199                 m = strcat(m, "_");
1200         else
1201                 m = "models/player/*_";
1202         if(sk >= 0)
1203                 m = strcat(m, ftos(sk));
1204         else
1205                 m = strcat(m, "*");
1206         return strcat(m, ".", fil);
1207 }
1208
1209 float get_model_parameters(string m, float sk)
1210 {
1211         get_model_parameters_modelname = string_null;
1212         get_model_parameters_modelskin = -1;
1213         get_model_parameters_name = string_null;
1214         get_model_parameters_species = -1;
1215         get_model_parameters_sex = string_null;
1216         get_model_parameters_weight = -1;
1217         get_model_parameters_age = -1;
1218         get_model_parameters_desc = string_null;
1219         get_model_parameters_bone_upperbody = string_null;
1220         get_model_parameters_bone_weapon = string_null;
1221         for(int i = 0; i < MAX_AIM_BONES; ++i)
1222         {
1223                 get_model_parameters_bone_aim[i] = string_null;
1224                 get_model_parameters_bone_aimweight[i] = 0;
1225         }
1226         get_model_parameters_fixbone = 0;
1227
1228 #ifndef MENUQC
1229         MUTATOR_CALLHOOK(ClearModelParams);
1230 #endif
1231
1232         if (!m)
1233                 return 1;
1234
1235         if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1236                 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1237
1238         if(sk < 0)
1239         {
1240                 if(substring(m, -4, -1) != ".txt")
1241                         return 0;
1242                 if(substring(m, -6, 1) != "_")
1243                         return 0;
1244                 sk = stof(substring(m, -5, 1));
1245                 m = substring(m, 0, -7);
1246         }
1247
1248         string fn = get_model_datafilename(m, sk, "txt");
1249         int fh = fopen(fn, FILE_READ);
1250         if(fh < 0)
1251         {
1252                 sk = 0;
1253                 fn = get_model_datafilename(m, sk, "txt");
1254                 fh = fopen(fn, FILE_READ);
1255                 if(fh < 0)
1256                         return 0;
1257         }
1258
1259         get_model_parameters_modelname = m;
1260         get_model_parameters_modelskin = sk;
1261         string s, c;
1262         while((s = fgets(fh)))
1263         {
1264                 if(s == "")
1265                         break; // next lines will be description
1266                 c = car(s);
1267                 s = cdr(s);
1268                 if(c == "name")
1269                         get_model_parameters_name = s;
1270                 if(c == "species")
1271                         switch(s)
1272                         {
1273                                 case "human":       get_model_parameters_species = SPECIES_HUMAN;       break;
1274                                 case "alien":       get_model_parameters_species = SPECIES_ALIEN;       break;
1275                                 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1276                                 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1277                                 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1278                                 case "animal":      get_model_parameters_species = SPECIES_ANIMAL;      break;
1279                                 case "reserved":    get_model_parameters_species = SPECIES_RESERVED;    break;
1280                         }
1281                 if(c == "sex")
1282                         get_model_parameters_sex = s;
1283                 if(c == "weight")
1284                         get_model_parameters_weight = stof(s);
1285                 if(c == "age")
1286                         get_model_parameters_age = stof(s);
1287                 if(c == "description")
1288                         get_model_parameters_description = s;
1289                 if(c == "bone_upperbody")
1290                         get_model_parameters_bone_upperbody = s;
1291                 if(c == "bone_weapon")
1292                         get_model_parameters_bone_weapon = s;
1293         #ifndef MENUQC
1294                 MUTATOR_CALLHOOK(GetModelParams, c, s);
1295         #endif
1296                 for(int i = 0; i < MAX_AIM_BONES; ++i)
1297                         if(c == strcat("bone_aim", ftos(i)))
1298                         {
1299                                 get_model_parameters_bone_aimweight[i] = stof(car(s));
1300                                 get_model_parameters_bone_aim[i] = cdr(s);
1301                         }
1302                 if(c == "fixbone")
1303                         get_model_parameters_fixbone = stof(s);
1304         }
1305
1306         while((s = fgets(fh)))
1307         {
1308                 if(get_model_parameters_desc)
1309                         get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1310                 if(s != "")
1311                         get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1312         }
1313
1314         fclose(fh);
1315
1316         return 1;
1317 }
1318
1319 // x-encoding (encoding as zero length invisible string)
1320 const string XENCODE_2  = "xX";
1321 const string XENCODE_22 = "0123456789abcdefABCDEF";
1322 string xencode(int f)
1323 {
1324         float a, b, c, d;
1325         d = f % 22; f = floor(f / 22);
1326         c = f % 22; f = floor(f / 22);
1327         b = f % 22; f = floor(f / 22);
1328         a = f %  2; // f = floor(f /  2);
1329         return strcat(
1330                 "^",
1331                 substring(XENCODE_2,  a, 1),
1332                 substring(XENCODE_22, b, 1),
1333                 substring(XENCODE_22, c, 1),
1334                 substring(XENCODE_22, d, 1)
1335         );
1336 }
1337 float xdecode(string s)
1338 {
1339         float a, b, c, d;
1340         if(substring(s, 0, 1) != "^")
1341                 return -1;
1342         if(strlen(s) < 5)
1343                 return -1;
1344         a = strstrofs(XENCODE_2,  substring(s, 1, 1), 0);
1345         b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1346         c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1347         d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1348         if(a < 0 || b < 0 || c < 0 || d < 0)
1349                 return -1;
1350         return ((a * 22 + b) * 22 + c) * 22 + d;
1351 }
1352
1353 /*
1354 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1355 {
1356         if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1357                 return input;
1358         else
1359                 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1360 }*/
1361
1362 float shutdown_running;
1363 #ifdef SVQC
1364 void SV_Shutdown()
1365 #endif
1366 #ifdef CSQC
1367 void CSQC_Shutdown()
1368 #endif
1369 #ifdef MENUQC
1370 void m_shutdown()
1371 #endif
1372 {
1373         if(shutdown_running)
1374         {
1375                 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
1376         }
1377         else
1378         {
1379                 shutdown_running = 1;
1380                 Shutdown();
1381                 shutdownhooks();
1382         }
1383         cvar_settemp_restore(); // this must be done LAST, but in any case
1384 }
1385
1386 #ifndef MENUQC
1387 .float skeleton_bones_index;
1388 void Skeleton_SetBones(entity e)
1389 {
1390         // set skeleton_bones to the total number of bones on the model
1391         if(e.skeleton_bones_index == e.modelindex)
1392                 return; // same model, nothing to update
1393
1394         float skelindex;
1395         skelindex = skel_create(e.modelindex);
1396         e.skeleton_bones = skel_get_numbones(skelindex);
1397         skel_delete(skelindex);
1398         e.skeleton_bones_index = e.modelindex;
1399 }
1400 #endif
1401
1402 string to_execute_next_frame;
1403 void execute_next_frame()
1404 {
1405         if(to_execute_next_frame)
1406         {
1407                 localcmd("\n", to_execute_next_frame, "\n");
1408                 strunzone(to_execute_next_frame);
1409                 to_execute_next_frame = string_null;
1410         }
1411 }
1412 void queue_to_execute_next_frame(string s)
1413 {
1414         if(to_execute_next_frame)
1415         {
1416                 s = strcat(s, "\n", to_execute_next_frame);
1417                 strunzone(to_execute_next_frame);
1418         }
1419         to_execute_next_frame = strzone(s);
1420 }
1421
1422 .float FindConnectedComponent_processing;
1423 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1424 {
1425         entity queue_start, queue_end;
1426
1427         // we build a queue of to-be-processed entities.
1428         // queue_start is the next entity to be checked for neighbors
1429         // queue_end is the last entity added
1430
1431         if(e.FindConnectedComponent_processing)
1432                 error("recursion or broken cleanup");
1433
1434         // start with a 1-element queue
1435         queue_start = queue_end = e;
1436         queue_end.(fld) = world;
1437         queue_end.FindConnectedComponent_processing = 1;
1438
1439         // for each queued item:
1440         for (; queue_start; queue_start = queue_start.(fld))
1441         {
1442                 // find all neighbors of queue_start
1443                 entity t;
1444                 for(t = world; (t = nxt(t, queue_start, pass)); )
1445                 {
1446                         if(t.FindConnectedComponent_processing)
1447                                 continue;
1448                         if(iscon(t, queue_start, pass))
1449                         {
1450                                 // it is connected? ADD IT. It will look for neighbors soon too.
1451                                 queue_end.(fld) = t;
1452                                 queue_end = t;
1453                                 queue_end.(fld) = world;
1454                                 queue_end.FindConnectedComponent_processing = 1;
1455                         }
1456                 }
1457         }
1458
1459         // unmark
1460         for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1461                 queue_start.FindConnectedComponent_processing = 0;
1462 }
1463
1464 #ifndef MENUQC
1465 vector animfixfps(entity e, vector a, vector b)
1466 {
1467         // multi-frame anim: keep as-is
1468         if(a.y == 1)
1469         {
1470                 float dur = frameduration(e.modelindex, a.x);
1471                 if (dur <= 0 && b.y)
1472                 {
1473                         a = b;
1474                         dur = frameduration(e.modelindex, a.x);
1475                 }
1476                 if (dur > 0)
1477                         a.z = 1.0 / dur;
1478         }
1479         return a;
1480 }
1481 #endif
1482
1483 #ifndef MENUQC
1484 Notification Announcer_PickNumber(int type, int num)
1485 {
1486     return = NULL;
1487         switch (type)
1488         {
1489                 case CNT_GAMESTART:
1490                 {
1491                         switch(num)
1492                         {
1493                                 case 10: return ANNCE_NUM_GAMESTART_10;
1494                                 case 9:  return ANNCE_NUM_GAMESTART_9;
1495                                 case 8:  return ANNCE_NUM_GAMESTART_8;
1496                                 case 7:  return ANNCE_NUM_GAMESTART_7;
1497                                 case 6:  return ANNCE_NUM_GAMESTART_6;
1498                                 case 5:  return ANNCE_NUM_GAMESTART_5;
1499                                 case 4:  return ANNCE_NUM_GAMESTART_4;
1500                                 case 3:  return ANNCE_NUM_GAMESTART_3;
1501                                 case 2:  return ANNCE_NUM_GAMESTART_2;
1502                                 case 1:  return ANNCE_NUM_GAMESTART_1;
1503                         }
1504                         break;
1505                 }
1506                 case CNT_IDLE:
1507                 {
1508                         switch(num)
1509                         {
1510                                 case 10: return ANNCE_NUM_IDLE_10;
1511                                 case 9:  return ANNCE_NUM_IDLE_9;
1512                                 case 8:  return ANNCE_NUM_IDLE_8;
1513                                 case 7:  return ANNCE_NUM_IDLE_7;
1514                                 case 6:  return ANNCE_NUM_IDLE_6;
1515                                 case 5:  return ANNCE_NUM_IDLE_5;
1516                                 case 4:  return ANNCE_NUM_IDLE_4;
1517                                 case 3:  return ANNCE_NUM_IDLE_3;
1518                                 case 2:  return ANNCE_NUM_IDLE_2;
1519                                 case 1:  return ANNCE_NUM_IDLE_1;
1520                         }
1521                         break;
1522                 }
1523                 case CNT_KILL:
1524                 {
1525                         switch(num)
1526                         {
1527                                 case 10: return ANNCE_NUM_KILL_10;
1528                                 case 9:  return ANNCE_NUM_KILL_9;
1529                                 case 8:  return ANNCE_NUM_KILL_8;
1530                                 case 7:  return ANNCE_NUM_KILL_7;
1531                                 case 6:  return ANNCE_NUM_KILL_6;
1532                                 case 5:  return ANNCE_NUM_KILL_5;
1533                                 case 4:  return ANNCE_NUM_KILL_4;
1534                                 case 3:  return ANNCE_NUM_KILL_3;
1535                                 case 2:  return ANNCE_NUM_KILL_2;
1536                                 case 1:  return ANNCE_NUM_KILL_1;
1537                         }
1538                         break;
1539                 }
1540                 case CNT_RESPAWN:
1541                 {
1542                         switch(num)
1543                         {
1544                                 case 10: return ANNCE_NUM_RESPAWN_10;
1545                                 case 9:  return ANNCE_NUM_RESPAWN_9;
1546                                 case 8:  return ANNCE_NUM_RESPAWN_8;
1547                                 case 7:  return ANNCE_NUM_RESPAWN_7;
1548                                 case 6:  return ANNCE_NUM_RESPAWN_6;
1549                                 case 5:  return ANNCE_NUM_RESPAWN_5;
1550                                 case 4:  return ANNCE_NUM_RESPAWN_4;
1551                                 case 3:  return ANNCE_NUM_RESPAWN_3;
1552                                 case 2:  return ANNCE_NUM_RESPAWN_2;
1553                                 case 1:  return ANNCE_NUM_RESPAWN_1;
1554                         }
1555                         break;
1556                 }
1557                 case CNT_ROUNDSTART:
1558                 {
1559                         switch(num)
1560                         {
1561                                 case 10: return ANNCE_NUM_ROUNDSTART_10;
1562                                 case 9:  return ANNCE_NUM_ROUNDSTART_9;
1563                                 case 8:  return ANNCE_NUM_ROUNDSTART_8;
1564                                 case 7:  return ANNCE_NUM_ROUNDSTART_7;
1565                                 case 6:  return ANNCE_NUM_ROUNDSTART_6;
1566                                 case 5:  return ANNCE_NUM_ROUNDSTART_5;
1567                                 case 4:  return ANNCE_NUM_ROUNDSTART_4;
1568                                 case 3:  return ANNCE_NUM_ROUNDSTART_3;
1569                                 case 2:  return ANNCE_NUM_ROUNDSTART_2;
1570                                 case 1:  return ANNCE_NUM_ROUNDSTART_1;
1571                         }
1572                         break;
1573                 }
1574                 default:
1575                 {
1576                         switch(num)
1577                         {
1578                                 case 10: return ANNCE_NUM_10;
1579                                 case 9:  return ANNCE_NUM_9;
1580                                 case 8:  return ANNCE_NUM_8;
1581                                 case 7:  return ANNCE_NUM_7;
1582                                 case 6:  return ANNCE_NUM_6;
1583                                 case 5:  return ANNCE_NUM_5;
1584                                 case 4:  return ANNCE_NUM_4;
1585                                 case 3:  return ANNCE_NUM_3;
1586                                 case 2:  return ANNCE_NUM_2;
1587                                 case 1:  return ANNCE_NUM_1;
1588                         }
1589                         break;
1590                 }
1591         }
1592 }
1593 #endif
1594
1595 #ifndef MENUQC
1596 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1597 {
1598         switch(nativecontents)
1599         {
1600                 case CONTENT_EMPTY:
1601                         return 0;
1602                 case CONTENT_SOLID:
1603                         return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1604                 case CONTENT_WATER:
1605                         return DPCONTENTS_WATER;
1606                 case CONTENT_SLIME:
1607                         return DPCONTENTS_SLIME;
1608                 case CONTENT_LAVA:
1609                         return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1610                 case CONTENT_SKY:
1611                         return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1612         }
1613         return 0;
1614 }
1615
1616 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1617 {
1618         if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1619                 return CONTENT_SOLID;
1620         if(supercontents & DPCONTENTS_SKY)
1621                 return CONTENT_SKY;
1622         if(supercontents & DPCONTENTS_LAVA)
1623                 return CONTENT_LAVA;
1624         if(supercontents & DPCONTENTS_SLIME)
1625                 return CONTENT_SLIME;
1626         if(supercontents & DPCONTENTS_WATER)
1627                 return CONTENT_WATER;
1628         return CONTENT_EMPTY;
1629 }
1630 #endif