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