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