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