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