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