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