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