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