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