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