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