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