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