1 string wordwrap_buffer;
3 void wordwrap_buffer_put(string s)
5 wordwrap_buffer = strcat(wordwrap_buffer, s);
8 string wordwrap(string s, float l)
12 wordwrap_cb(s, l, wordwrap_buffer_put);
20 void wordwrap_buffer_sprint(string s)
22 wordwrap_buffer = strcat(wordwrap_buffer, s);
25 sprint(self, wordwrap_buffer);
30 void wordwrap_sprint(string s, float l)
33 wordwrap_cb(s, l, wordwrap_buffer_sprint);
34 if(wordwrap_buffer != "")
35 sprint(self, strcat(wordwrap_buffer, "\n"));
42 string unescape(string in)
47 // but it doesn't seem to be necessary in my tests at least
52 for(i = 0; i < len; ++i)
54 s = substring(in, i, 1);
57 s = substring(in, i+1, 1);
59 str = strcat(str, "\n");
61 str = strcat(str, "\\");
63 str = strcat(str, substring(in, i, 2));
73 void wordwrap_cb(string s, float l, void(string) callback)
76 float lleft, i, j, wlen;
80 for (i = 0;i < strlen(s);++i)
82 if (substring(s, i, 2) == "\\n")
88 else if (substring(s, i, 1) == "\n")
93 else if (substring(s, i, 1) == " ")
103 for (j = i+1;j < strlen(s);++j)
104 // ^^ this skips over the first character of a word, which
105 // is ALWAYS part of the word
106 // this is safe since if i+1 == strlen(s), i will become
107 // strlen(s)-1 at the end of this block and the function
108 // will terminate. A space can't be the first character we
109 // read here, and neither can a \n be the start, since these
110 // two cases have been handled above.
112 c = substring(s, j, 1);
119 // we need to keep this tempstring alive even if substring is
120 // called repeatedly, so call strcat even though we're not
130 callback(substring(s, i, wlen));
131 lleft = lleft - wlen;
138 float dist_point_line(vector p, vector l0, vector ldir)
140 ldir = normalize(ldir);
142 // remove the component in line direction
143 p = p - (p * ldir) * ldir;
145 // vlen of the remaining vector
149 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
178 float median(float a, float b, float c)
181 return bound(a, b, c);
182 return bound(c, b, a);
185 // converts a number to a string with the indicated number of decimals
186 // works for up to 10 decimals!
187 string ftos_decimals(float number, float decimals)
189 // we have sprintf...
190 return sprintf("%.*f", decimals, number);
194 vector colormapPaletteColor(float c, float isPants)
198 case 0: return '0.800000 0.800000 0.800000';
199 case 1: return '0.600000 0.400000 0.000000';
200 case 2: return '0.000000 1.000000 0.501961';
201 case 3: return '0.000000 1.000000 0.000000';
202 case 4: return '1.000000 0.000000 0.000000';
203 case 5: return '0.000000 0.658824 1.000000';
204 case 6: return '0.000000 1.000000 1.000000';
205 case 7: return '0.501961 1.000000 0.000000';
206 case 8: return '0.501961 0.000000 1.000000';
207 case 9: return '1.000000 0.000000 1.000000';
208 case 10: return '1.000000 0.000000 0.501961';
209 case 11: return '0.600000 0.600000 0.600000';
210 case 12: return '1.000000 1.000000 0.000000';
211 case 13: return '0.000000 0.313725 1.000000';
212 case 14: return '1.000000 0.501961 0.000000';
216 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
217 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
218 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
221 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
222 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
223 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
224 default: return '0.000 0.000 0.000';
228 // unzone the string, and return it as tempstring. Safe to be called on string_null
229 string fstrunzone(string s)
239 float fexists(string f)
242 fh = fopen(f, FILE_READ);
249 // Databases (hash tables)
250 #define DB_BUCKETS 8192
251 void db_save(float db, string pFilename)
254 fh = fopen(pFilename, FILE_WRITE);
257 print(strcat("^1Can't write DB to ", pFilename));
261 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
262 for(i = 0; i < n; ++i)
263 fputs(fh, strcat(bufstr_get(db, i), "\n"));
272 float db_load(string pFilename)
274 float db, fh, i, j, n;
279 fh = fopen(pFilename, FILE_READ);
283 if(stof(l) == DB_BUCKETS)
286 while((l = fgets(fh)))
289 bufstr_set(db, i, l);
295 // different count of buckets, or a dump?
296 // need to reorganize the database then (SLOW)
298 // note: we also parse the first line (l) in case the DB file is
299 // missing the bucket count
302 n = tokenizebyseparator(l, "\\");
303 for(j = 2; j < n; j += 2)
304 db_put(db, argv(j-1), uri_unescape(argv(j)));
306 while((l = fgets(fh)));
312 void db_dump(float db, string pFilename)
314 float fh, i, j, n, m;
315 fh = fopen(pFilename, FILE_WRITE);
317 error(strcat("Can't dump DB to ", pFilename));
320 for(i = 0; i < n; ++i)
322 m = tokenizebyseparator(bufstr_get(db, i), "\\");
323 for(j = 2; j < m; j += 2)
324 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
329 void db_close(float db)
334 string db_get(float db, string pKey)
337 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
338 return uri_unescape(infoget(bufstr_get(db, h), pKey));
341 void db_put(float db, string pKey, string pValue)
344 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
345 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
352 db = db_load("foo.db");
353 print("LOADED. FILL...\n");
354 for(i = 0; i < DB_BUCKETS; ++i)
355 db_put(db, ftos(random()), "X");
356 print("FILLED. SAVE...\n");
357 db_save(db, "foo.db");
358 print("SAVED. CLOSE...\n");
363 // Multiline text file buffers
364 float buf_load(string pFilename)
371 fh = fopen(pFilename, FILE_READ);
378 while((l = fgets(fh)))
380 bufstr_set(buf, i, l);
387 void buf_save(float buf, string pFilename)
390 fh = fopen(pFilename, FILE_WRITE);
392 error(strcat("Can't write buf to ", pFilename));
393 n = buf_getsize(buf);
394 for(i = 0; i < n; ++i)
395 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
399 string GametypeNameFromType(float g)
401 if (g == GAME_DEATHMATCH) return "dm";
402 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
403 else if (g == GAME_DOMINATION) return "dom";
404 else if (g == GAME_CTF) return "ctf";
405 else if (g == GAME_RUNEMATCH) return "rune";
406 else if (g == GAME_LMS) return "lms";
407 else if (g == GAME_ARENA) return "arena";
408 else if (g == GAME_CA) return "ca";
409 else if (g == GAME_KEYHUNT) return "kh";
410 else if (g == GAME_ONSLAUGHT) return "ons";
411 else if (g == GAME_ASSAULT) return "as";
412 else if (g == GAME_RACE) return "rc";
413 else if (g == GAME_NEXBALL) return "nexball";
414 else if (g == GAME_CTS) return "cts";
415 else if (g == GAME_FREEZETAG) return "freezetag";
416 else if (g == GAME_KEEPAWAY) return "ka";
420 string mmsss(float tenths)
424 tenths = floor(tenths + 0.5);
425 minutes = floor(tenths / 600);
426 tenths -= minutes * 600;
427 s = ftos(1000 + tenths);
428 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
431 string mmssss(float hundredths)
435 hundredths = floor(hundredths + 0.5);
436 minutes = floor(hundredths / 6000);
437 hundredths -= minutes * 6000;
438 s = ftos(10000 + hundredths);
439 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
442 string ScoreString(float pFlags, float pValue)
447 pValue = floor(pValue + 0.5); // round
449 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
451 else if(pFlags & SFL_RANK)
453 valstr = ftos(pValue);
455 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
456 valstr = strcat(valstr, "th");
457 else if(substring(valstr, l - 1, 1) == "1")
458 valstr = strcat(valstr, "st");
459 else if(substring(valstr, l - 1, 1) == "2")
460 valstr = strcat(valstr, "nd");
461 else if(substring(valstr, l - 1, 1) == "3")
462 valstr = strcat(valstr, "rd");
464 valstr = strcat(valstr, "th");
466 else if(pFlags & SFL_TIME)
467 valstr = TIME_ENCODED_TOSTRING(pValue);
469 valstr = ftos(pValue);
474 vector cross(vector a, vector b)
477 '1 0 0' * (a_y * b_z - a_z * b_y)
478 + '0 1 0' * (a_z * b_x - a_x * b_z)
479 + '0 0 1' * (a_x * b_y - a_y * b_x);
482 // compressed vector format:
483 // like MD3, just even shorter
484 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
485 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
486 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
487 // length = 2^(length_encoded/8) / 8
488 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
489 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
490 // the special value 0 indicates the zero vector
492 float lengthLogTable[128];
494 float invertLengthLog(float x)
496 float l, r, m, lerr, rerr;
498 if(x >= lengthLogTable[127])
500 if(x <= lengthLogTable[0])
508 m = floor((l + r) / 2);
509 if(lengthLogTable[m] < x)
515 // now: r is >=, l is <
516 lerr = (x - lengthLogTable[l]);
517 rerr = (lengthLogTable[r] - x);
523 vector decompressShortVector(float data)
529 p = (data & 0xF000) / 0x1000;
530 y = (data & 0x0F80) / 0x80;
531 len = (data & 0x007F);
533 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
546 y = .19634954084936207740 * y;
547 p = .19634954084936207740 * p - 1.57079632679489661922;
548 out_x = cos(y) * cos(p);
549 out_y = sin(y) * cos(p);
553 //print("decompressed: ", vtos(out), "\n");
555 return out * lengthLogTable[len];
558 float compressShortVector(vector vec)
564 //print("compress: ", vtos(vec), "\n");
565 ang = vectoangles(vec);
569 if(ang_x < -90 && ang_x > +90)
570 error("BOGUS vectoangles");
571 //print("angles: ", vtos(ang), "\n");
573 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
582 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
583 len = invertLengthLog(vlen(vec));
585 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
587 return (p * 0x1000) + (y * 0x80) + len;
590 void compressShortVector_init()
595 for(i = 0; i < 128; ++i)
597 lengthLogTable[i] = l;
601 if(cvar("developer"))
603 print("Verifying vector compression table...\n");
604 for(i = 0x0F00; i < 0xFFFF; ++i)
605 if(i != compressShortVector(decompressShortVector(i)))
607 print("BROKEN vector compression: ", ftos(i));
608 print(" -> ", vtos(decompressShortVector(i)));
609 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
618 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
620 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
628 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
629 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
630 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
631 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
636 string fixPriorityList(string order, float from, float to, float subtract, float complete)
641 n = tokenize_console(order);
643 for(i = 0; i < n; ++i)
648 if(w >= from && w <= to)
649 neworder = strcat(neworder, ftos(w), " ");
653 if(w >= from && w <= to)
654 neworder = strcat(neworder, ftos(w), " ");
661 n = tokenize_console(neworder);
662 for(w = to; w >= from; --w)
664 for(i = 0; i < n; ++i)
665 if(stof(argv(i)) == w)
667 if(i == n) // not found
668 neworder = strcat(neworder, ftos(w), " ");
672 return substring(neworder, 0, strlen(neworder) - 1);
675 string mapPriorityList(string order, string(string) mapfunc)
680 n = tokenize_console(order);
682 for(i = 0; i < n; ++i)
683 neworder = strcat(neworder, mapfunc(argv(i)), " ");
685 return substring(neworder, 0, strlen(neworder) - 1);
688 string swapInPriorityList(string order, float i, float j)
693 n = tokenize_console(order);
695 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
698 for(w = 0; w < n; ++w)
701 s = strcat(s, argv(j), " ");
703 s = strcat(s, argv(i), " ");
705 s = strcat(s, argv(w), " ");
707 return substring(s, 0, strlen(s) - 1);
713 float cvar_value_issafe(string s)
715 if(strstrofs(s, "\"", 0) >= 0)
717 if(strstrofs(s, "\\", 0) >= 0)
719 if(strstrofs(s, ";", 0) >= 0)
721 if(strstrofs(s, "$", 0) >= 0)
723 if(strstrofs(s, "\r", 0) >= 0)
725 if(strstrofs(s, "\n", 0) >= 0)
731 void get_mi_min_max(float mode)
736 strunzone(mi_shortname);
737 mi_shortname = mapname;
738 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
739 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
740 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
741 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
742 mi_shortname = strzone(mi_shortname);
754 MapInfo_Get_ByName(mi_shortname, 0, 0);
755 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
757 mi_min = MapInfo_Map_mins;
758 mi_max = MapInfo_Map_maxs;
766 tracebox('1 0 0' * mi_x,
767 '0 1 0' * mi_y + '0 0 1' * mi_z,
768 '0 1 0' * ma_y + '0 0 1' * ma_z,
772 if(!trace_startsolid)
773 mi_min_x = trace_endpos_x;
775 tracebox('0 1 0' * mi_y,
776 '1 0 0' * mi_x + '0 0 1' * mi_z,
777 '1 0 0' * ma_x + '0 0 1' * ma_z,
781 if(!trace_startsolid)
782 mi_min_y = trace_endpos_y;
784 tracebox('0 0 1' * mi_z,
785 '1 0 0' * mi_x + '0 1 0' * mi_y,
786 '1 0 0' * ma_x + '0 1 0' * ma_y,
790 if(!trace_startsolid)
791 mi_min_z = trace_endpos_z;
793 tracebox('1 0 0' * ma_x,
794 '0 1 0' * mi_y + '0 0 1' * mi_z,
795 '0 1 0' * ma_y + '0 0 1' * ma_z,
799 if(!trace_startsolid)
800 mi_max_x = trace_endpos_x;
802 tracebox('0 1 0' * ma_y,
803 '1 0 0' * mi_x + '0 0 1' * mi_z,
804 '1 0 0' * ma_x + '0 0 1' * ma_z,
808 if(!trace_startsolid)
809 mi_max_y = trace_endpos_y;
811 tracebox('0 0 1' * ma_z,
812 '1 0 0' * mi_x + '0 1 0' * mi_y,
813 '1 0 0' * ma_x + '0 1 0' * ma_y,
817 if(!trace_startsolid)
818 mi_max_z = trace_endpos_z;
823 void get_mi_min_max_texcoords(float mode)
827 get_mi_min_max(mode);
832 // extend mi_picmax to get a square aspect ratio
833 // center the map in that area
834 extend = mi_picmax - mi_picmin;
835 if(extend_y > extend_x)
837 mi_picmin_x -= (extend_y - extend_x) * 0.5;
838 mi_picmax_x += (extend_y - extend_x) * 0.5;
842 mi_picmin_y -= (extend_x - extend_y) * 0.5;
843 mi_picmax_y += (extend_x - extend_y) * 0.5;
846 // add another some percent
847 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
851 // calculate the texcoords
852 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
853 // first the two corners of the origin
854 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
855 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
856 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
857 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
858 // then the other corners
859 mi_pictexcoord1_x = mi_pictexcoord0_x;
860 mi_pictexcoord1_y = mi_pictexcoord2_y;
861 mi_pictexcoord3_x = mi_pictexcoord2_x;
862 mi_pictexcoord3_y = mi_pictexcoord0_y;
866 float cvar_settemp(string tmp_cvar, string tmp_value)
868 float created_saved_value;
871 if not(tmp_cvar || tmp_value)
873 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
877 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
878 if(e.netname == tmp_cvar)
879 goto saved; // skip creation
881 // creating a new entity to keep track of this cvar
883 e.classname = "saved_cvar_value";
884 e.netname = strzone(tmp_cvar);
885 e.message = strzone(cvar_string(tmp_cvar));
886 created_saved_value = TRUE;
888 // an entity for this cvar already exists
891 // update the cvar to the value given
892 cvar_set(tmp_cvar, tmp_value);
894 return created_saved_value;
897 float cvar_settemp_restore()
901 while((e = find(world, classname, "saved_cvar_value")))
903 cvar_set(e.netname, e.message);
910 float almost_equals(float a, float b)
913 eps = (max(a, -a) + max(b, -b)) * 0.001;
914 if(a - b < eps && b - a < eps)
919 float almost_in_bounds(float a, float b, float c)
922 eps = (max(a, -a) + max(c, -c)) * 0.001;
923 return b == median(a - eps, b, c + eps);
926 float power2of(float e)
930 float log2of(float x)
932 // NOTE: generated code
1005 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1009 else if(ma == rgb_x)
1012 return (rgb_y - rgb_z) / (ma - mi);
1014 return (rgb_y - rgb_z) / (ma - mi) + 6;
1016 else if(ma == rgb_y)
1017 return (rgb_z - rgb_x) / (ma - mi) + 2;
1018 else // if(ma == rgb_z)
1019 return (rgb_x - rgb_y) / (ma - mi) + 4;
1022 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1026 hue -= 6 * floor(hue / 6);
1028 //else if(ma == rgb_x)
1029 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1033 rgb_y = hue * (ma - mi) + mi;
1036 //else if(ma == rgb_y)
1037 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1040 rgb_x = (2 - hue) * (ma - mi) + mi;
1048 rgb_z = (hue - 2) * (ma - mi) + mi;
1050 //else // if(ma == rgb_z)
1051 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1055 rgb_y = (4 - hue) * (ma - mi) + mi;
1060 rgb_x = (hue - 4) * (ma - mi) + mi;
1064 //else if(ma == rgb_x)
1065 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1066 else // if(hue <= 6)
1070 rgb_z = (6 - hue) * (ma - mi) + mi;
1076 vector rgb_to_hsv(vector rgb)
1081 mi = min(rgb_x, rgb_y, rgb_z);
1082 ma = max(rgb_x, rgb_y, rgb_z);
1084 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1095 vector hsv_to_rgb(vector hsv)
1097 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1100 vector rgb_to_hsl(vector rgb)
1105 mi = min(rgb_x, rgb_y, rgb_z);
1106 ma = max(rgb_x, rgb_y, rgb_z);
1108 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1110 hsl_z = 0.5 * (mi + ma);
1113 else if(hsl_z <= 0.5)
1114 hsl_y = (ma - mi) / (2*hsl_z);
1115 else // if(hsl_z > 0.5)
1116 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1121 vector hsl_to_rgb(vector hsl)
1123 float mi, ma, maminusmi;
1126 maminusmi = hsl_y * 2 * hsl_z;
1128 maminusmi = hsl_y * (2 - 2 * hsl_z);
1130 // hsl_z = 0.5 * mi + 0.5 * ma
1131 // maminusmi = - mi + ma
1132 mi = hsl_z - 0.5 * maminusmi;
1133 ma = hsl_z + 0.5 * maminusmi;
1135 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1138 string rgb_to_hexcolor(vector rgb)
1143 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1144 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1145 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1149 // requires that m2>m1 in all coordinates, and that m4>m3
1150 float boxesoverlap(vector m1, vector m2, vector m3, vector m4) {return m2_x >= m3_x && m1_x <= m4_x && m2_y >= m3_y && m1_y <= m4_y && m2_z >= m3_z && m1_z <= m4_z;}
1152 // requires the same, but is a stronger condition
1153 float boxinsidebox(vector smins, vector smaxs, vector bmins, vector bmaxs) {return smins_x >= bmins_x && smaxs_x <= bmaxs_x && smins_y >= bmins_y && smaxs_y <= bmaxs_y && smins_z >= bmins_z && smaxs_z <= bmaxs_z;}
1158 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1161 // The following function is SLOW.
1162 // For your safety and for the protection of those around you...
1163 // DO NOT CALL THIS AT HOME.
1164 // No really, don't.
1165 if(w(theText, theSize) <= maxWidth)
1166 return strlen(theText); // yeah!
1168 // binary search for right place to cut string
1170 float left, right, middle; // this always works
1172 right = strlen(theText); // this always fails
1175 middle = floor((left + right) / 2);
1176 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1181 while(left < right - 1);
1183 if(w("^7", theSize) == 0) // detect color codes support in the width function
1185 // NOTE: when color codes are involved, this binary search is,
1186 // mathematically, BROKEN. However, it is obviously guaranteed to
1187 // terminate, as the range still halves each time - but nevertheless, it is
1188 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1189 // range, and "right" is outside).
1191 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1192 // and decrease left on the basis of the chars detected of the truncated tag
1193 // Even if the ^xrgb tag is not complete/correct, left is decreased
1194 // (sometimes too much but with a correct result)
1195 // it fixes also ^[0-9]
1196 while(left >= 1 && substring(theText, left-1, 1) == "^")
1199 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1201 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1203 ch = str2chr(theText, left-1);
1204 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1207 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1209 ch = str2chr(theText, left-2);
1210 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1212 ch = str2chr(theText, left-1);
1213 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1222 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1225 // The following function is SLOW.
1226 // For your safety and for the protection of those around you...
1227 // DO NOT CALL THIS AT HOME.
1228 // No really, don't.
1229 if(w(theText) <= maxWidth)
1230 return strlen(theText); // yeah!
1232 // binary search for right place to cut string
1234 float left, right, middle; // this always works
1236 right = strlen(theText); // this always fails
1239 middle = floor((left + right) / 2);
1240 if(w(substring(theText, 0, middle)) <= maxWidth)
1245 while(left < right - 1);
1247 if(w("^7") == 0) // detect color codes support in the width function
1249 // NOTE: when color codes are involved, this binary search is,
1250 // mathematically, BROKEN. However, it is obviously guaranteed to
1251 // terminate, as the range still halves each time - but nevertheless, it is
1252 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1253 // range, and "right" is outside).
1255 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1256 // and decrease left on the basis of the chars detected of the truncated tag
1257 // Even if the ^xrgb tag is not complete/correct, left is decreased
1258 // (sometimes too much but with a correct result)
1259 // it fixes also ^[0-9]
1260 while(left >= 1 && substring(theText, left-1, 1) == "^")
1263 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1265 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1267 ch = str2chr(theText, left-1);
1268 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1271 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1273 ch = str2chr(theText, left-2);
1274 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1276 ch = str2chr(theText, left-1);
1277 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1286 string find_last_color_code(string s)
1288 float start, len, i, carets;
1289 start = strstrofs(s, "^", 0);
1290 if (start == -1) // no caret found
1293 for(i = len; i >= start; --i)
1295 if(substring(s, i, 1) != "^")
1299 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1302 // check if carets aren't all escaped
1303 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1306 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1307 return substring(s, i, 2);
1310 if(substring(s, i+1, 1) == "x")
1311 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1312 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1313 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1314 return substring(s, i, 5);
1316 i -= carets; // this also skips one char before the carets
1322 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1328 s = getWrappedLine_remaining;
1332 getWrappedLine_remaining = string_null;
1333 return s; // the line has no size ANYWAY, nothing would be displayed.
1336 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1337 if(cantake > 0 && cantake < strlen(s))
1340 while(take > 0 && substring(s, take, 1) != " ")
1344 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1345 if(getWrappedLine_remaining == "")
1346 getWrappedLine_remaining = string_null;
1347 else if (tw("^7", theFontSize) == 0)
1348 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1349 return substring(s, 0, cantake);
1353 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1354 if(getWrappedLine_remaining == "")
1355 getWrappedLine_remaining = string_null;
1356 else if (tw("^7", theFontSize) == 0)
1357 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1358 return substring(s, 0, take);
1363 getWrappedLine_remaining = string_null;
1368 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1374 s = getWrappedLine_remaining;
1378 getWrappedLine_remaining = string_null;
1379 return s; // the line has no size ANYWAY, nothing would be displayed.
1382 cantake = textLengthUpToLength(s, w, tw);
1383 if(cantake > 0 && cantake < strlen(s))
1386 while(take > 0 && substring(s, take, 1) != " ")
1390 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1391 if(getWrappedLine_remaining == "")
1392 getWrappedLine_remaining = string_null;
1393 else if (tw("^7") == 0)
1394 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1395 return substring(s, 0, cantake);
1399 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1400 if(getWrappedLine_remaining == "")
1401 getWrappedLine_remaining = string_null;
1402 else if (tw("^7") == 0)
1403 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1404 return substring(s, 0, take);
1409 getWrappedLine_remaining = string_null;
1414 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1416 if(tw(theText, theFontSize) <= maxWidth)
1419 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1422 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1424 if(tw(theText) <= maxWidth)
1427 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1430 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1432 string subpattern, subpattern2, subpattern3, subpattern4;
1433 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1435 subpattern2 = ",teams,";
1437 subpattern2 = ",noteams,";
1439 subpattern3 = ",teamspawns,";
1441 subpattern3 = ",noteamspawns,";
1442 if(gt == GAME_RACE || gt == GAME_CTS)
1443 subpattern4 = ",race,";
1445 subpattern4 = string_null;
1447 if(substring(pattern, 0, 1) == "-")
1449 pattern = substring(pattern, 1, strlen(pattern) - 1);
1450 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1452 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1454 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1456 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1461 if(substring(pattern, 0, 1) == "+")
1462 pattern = substring(pattern, 1, strlen(pattern) - 1);
1463 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1464 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1465 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1466 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1472 void shuffle(float n, swapfunc_t swap, entity pass)
1475 for(i = 1; i < n; ++i)
1477 // swap i-th item at a random position from 0 to i
1478 // proof for even distribution:
1481 // item n+1 gets at any position with chance 1/(n+1)
1482 // all others will get their 1/n chance reduced by factor n/(n+1)
1483 // to be on place n+1, their chance will be 1/(n+1)
1484 // 1/n * n/(n+1) = 1/(n+1)
1486 j = floor(random() * (i + 1));
1492 string substring_range(string s, float b, float e)
1494 return substring(s, b, e - b);
1497 string swapwords(string str, float i, float j)
1500 string s1, s2, s3, s4, s5;
1501 float si, ei, sj, ej, s0, en;
1502 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1503 si = argv_start_index(i);
1504 sj = argv_start_index(j);
1505 ei = argv_end_index(i);
1506 ej = argv_end_index(j);
1507 s0 = argv_start_index(0);
1508 en = argv_end_index(n-1);
1509 s1 = substring_range(str, s0, si);
1510 s2 = substring_range(str, si, ei);
1511 s3 = substring_range(str, ei, sj);
1512 s4 = substring_range(str, sj, ej);
1513 s5 = substring_range(str, ej, en);
1514 return strcat(s1, s4, s3, s2, s5);
1517 string _shufflewords_str;
1518 void _shufflewords_swapfunc(float i, float j, entity pass)
1520 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1522 string shufflewords(string str)
1525 _shufflewords_str = str;
1526 n = tokenizebyseparator(str, " ");
1527 shuffle(n, _shufflewords_swapfunc, world);
1528 str = _shufflewords_str;
1529 _shufflewords_str = string_null;
1533 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1549 // actually, every number solves the equation!
1560 if(a > 0) // put the smaller solution first
1562 v_x = ((-b)-D) / (2*a);
1563 v_y = ((-b)+D) / (2*a);
1567 v_x = (-b+D) / (2*a);
1568 v_y = (-b-D) / (2*a);
1574 // complex solutions!
1587 void check_unacceptable_compiler_bugs()
1589 if(cvar("_allow_unacceptable_compiler_bugs"))
1591 tokenize_console("foo bar");
1592 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1593 error("fteqcc bug introduced with revision 3178 detected. Please upgrade fteqcc to a later revision, downgrade fteqcc to revision 3177, or pester Spike until he fixes it. You can set _allow_unacceptable_compiler_bugs 1 to skip this check, but expect stuff to be horribly broken then.");
1596 float compressShotOrigin(vector v)
1600 y = rint(v_y * 4) + 128;
1601 z = rint(v_z * 4) + 128;
1602 if(x > 255 || x < 0)
1604 print("shot origin ", vtos(v), " x out of bounds\n");
1605 x = bound(0, x, 255);
1607 if(y > 255 || y < 0)
1609 print("shot origin ", vtos(v), " y out of bounds\n");
1610 y = bound(0, y, 255);
1612 if(z > 255 || z < 0)
1614 print("shot origin ", vtos(v), " z out of bounds\n");
1615 z = bound(0, z, 255);
1617 return x * 0x10000 + y * 0x100 + z;
1619 vector decompressShotOrigin(float f)
1622 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1623 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1624 v_z = ((f & 0xFF) - 128) / 4;
1628 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1630 float start, end, root, child;
1633 start = floor((n - 2) / 2);
1636 // siftdown(start, count-1);
1638 while(root * 2 + 1 <= n-1)
1640 child = root * 2 + 1;
1642 if(cmp(child, child+1, pass) < 0)
1644 if(cmp(root, child, pass) < 0)
1646 swap(root, child, pass);
1662 // siftdown(0, end);
1664 while(root * 2 + 1 <= end)
1666 child = root * 2 + 1;
1667 if(child < end && cmp(child, child+1, pass) < 0)
1669 if(cmp(root, child, pass) < 0)
1671 swap(root, child, pass);
1681 void RandomSelection_Init()
1683 RandomSelection_totalweight = 0;
1684 RandomSelection_chosen_ent = world;
1685 RandomSelection_chosen_float = 0;
1686 RandomSelection_chosen_string = string_null;
1687 RandomSelection_best_priority = -1;
1689 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1691 if(priority > RandomSelection_best_priority)
1693 RandomSelection_best_priority = priority;
1694 RandomSelection_chosen_ent = e;
1695 RandomSelection_chosen_float = f;
1696 RandomSelection_chosen_string = s;
1697 RandomSelection_totalweight = weight;
1699 else if(priority == RandomSelection_best_priority)
1701 RandomSelection_totalweight += weight;
1702 if(random() * RandomSelection_totalweight <= weight)
1704 RandomSelection_chosen_ent = e;
1705 RandomSelection_chosen_float = f;
1706 RandomSelection_chosen_string = s;
1711 vector healtharmor_maxdamage(float h, float a, float armorblock)
1713 // NOTE: we'll always choose the SMALLER value...
1714 float healthdamage, armordamage, armorideal;
1716 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1717 armordamage = a + (h - 1); // damage we can take if we could use more armor
1718 armorideal = healthdamage * armorblock;
1720 if(armordamage < healthdamage)
1733 vector healtharmor_applydamage(float a, float armorblock, float damage)
1736 v_y = bound(0, damage * armorblock, a); // save
1737 v_x = bound(0, damage - v_y, damage); // take
1742 string getcurrentmod()
1746 m = cvar_string("fs_gamedir");
1747 n = tokenize_console(m);
1759 v = ReadShort() * 256; // note: this is signed
1760 v += ReadByte(); // note: this is unsigned
1764 void WriteInt24_t(float dst, float val)
1767 WriteShort(dst, (v = floor(val / 256)));
1768 WriteByte(dst, val - v * 256); // 0..255
1773 float float2range11(float f)
1775 // continuous function mapping all reals into -1..1
1776 return f / (fabs(f) + 1);
1779 float float2range01(float f)
1781 // continuous function mapping all reals into 0..1
1782 return 0.5 + 0.5 * float2range11(f);
1785 // from the GNU Scientific Library
1786 float gsl_ran_gaussian_lastvalue;
1787 float gsl_ran_gaussian_lastvalue_set;
1788 float gsl_ran_gaussian(float sigma)
1791 if(gsl_ran_gaussian_lastvalue_set)
1793 gsl_ran_gaussian_lastvalue_set = 0;
1794 return sigma * gsl_ran_gaussian_lastvalue;
1798 a = random() * 2 * M_PI;
1799 b = sqrt(-2 * log(random()));
1800 gsl_ran_gaussian_lastvalue = cos(a) * b;
1801 gsl_ran_gaussian_lastvalue_set = 1;
1802 return sigma * sin(a) * b;
1806 string car(string s)
1809 o = strstrofs(s, " ", 0);
1812 return substring(s, 0, o);
1814 string cdr(string s)
1817 o = strstrofs(s, " ", 0);
1820 return substring(s, o + 1, strlen(s) - (o + 1));
1822 float matchacl(string acl, string str)
1829 t = car(acl); acl = cdr(acl);
1831 if(substring(t, 0, 1) == "-")
1834 t = substring(t, 1, strlen(t) - 1);
1836 else if(substring(t, 0, 1) == "+")
1837 t = substring(t, 1, strlen(t) - 1);
1838 if(substring(t, -1, 1) == "*")
1840 t = substring(t, 0, strlen(t) - 1);
1841 s = substring(s, 0, strlen(t));
1853 float startsWith(string haystack, string needle)
1855 return substring(haystack, 0, strlen(needle)) == needle;
1857 float startsWithNocase(string haystack, string needle)
1859 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1862 string get_model_datafilename(string m, float sk, string fil)
1867 m = "models/player/*_";
1869 m = strcat(m, ftos(sk));
1872 return strcat(m, ".", fil);
1875 float get_model_parameters(string m, float sk)
1880 get_model_parameters_modelname = string_null;
1881 get_model_parameters_modelskin = -1;
1882 get_model_parameters_name = string_null;
1883 get_model_parameters_species = -1;
1884 get_model_parameters_sex = string_null;
1885 get_model_parameters_weight = -1;
1886 get_model_parameters_age = -1;
1887 get_model_parameters_desc = string_null;
1893 if(substring(m, -4, -1) != ".txt")
1895 if(substring(m, -6, 1) != "_")
1897 sk = stof(substring(m, -5, 1));
1898 m = substring(m, 0, -7);
1901 fn = get_model_datafilename(m, sk, "txt");
1902 fh = fopen(fn, FILE_READ);
1906 fn = get_model_datafilename(m, sk, "txt");
1907 fh = fopen(fn, FILE_READ);
1912 get_model_parameters_modelname = m;
1913 get_model_parameters_modelskin = sk;
1914 while((s = fgets(fh)))
1917 break; // next lines will be description
1921 get_model_parameters_name = s;
1925 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1926 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1927 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1928 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1929 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1930 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1931 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1934 get_model_parameters_sex = s;
1936 get_model_parameters_weight = stof(s);
1938 get_model_parameters_age = stof(s);
1941 while((s = fgets(fh)))
1943 if(get_model_parameters_desc)
1944 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1946 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1954 vector vec2(vector v)
1961 vector NearestPointOnBox(entity box, vector org)
1963 vector m1, m2, nearest;
1965 m1 = box.mins + box.origin;
1966 m2 = box.maxs + box.origin;
1968 nearest_x = bound(m1_x, org_x, m2_x);
1969 nearest_y = bound(m1_y, org_y, m2_y);
1970 nearest_z = bound(m1_z, org_z, m2_z);
1976 float vercmp_recursive(string v1, string v2)
1982 dot1 = strstrofs(v1, ".", 0);
1983 dot2 = strstrofs(v2, ".", 0);
1987 s1 = substring(v1, 0, dot1);
1991 s2 = substring(v2, 0, dot2);
1993 r = stof(s1) - stof(s2);
1997 r = strcasecmp(s1, s2);
2010 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2013 float vercmp(string v1, string v2)
2015 if(strcasecmp(v1, v2) == 0) // early out check
2024 return vercmp_recursive(v1, v2);
2027 float u8_strsize(string s)
2047 // translation helpers
2048 string language_filename(string s)
2053 if(fn == "" || fn == "dump")
2055 fn = strcat(s, ".", fn);
2056 if((fh = fopen(fn, FILE_READ)) >= 0)
2063 string CTX(string s)
2065 float p = strstrofs(s, "^", 0);
2068 return substring(s, p+1, -1);
2071 // x-encoding (encoding as zero length invisible string)
2072 const string XENCODE_2 = "xX";
2073 const string XENCODE_22 = "0123456789abcdefABCDEF";
2074 string xencode(float f)
2077 d = mod(f, 22); f = floor(f / 22);
2078 c = mod(f, 22); f = floor(f / 22);
2079 b = mod(f, 22); f = floor(f / 22);
2080 a = mod(f, 2); // f = floor(f / 2);
2083 substring(XENCODE_2, a, 1),
2084 substring(XENCODE_22, b, 1),
2085 substring(XENCODE_22, c, 1),
2086 substring(XENCODE_22, d, 1)
2089 float xdecode(string s)
2092 if(substring(s, 0, 1) != "^")
2096 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2097 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2098 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2099 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2100 if(a < 0 || b < 0 || c < 0 || d < 0)
2102 return ((a * 22 + b) * 22 + c) * 22 + d;
2105 float lowestbit(float f)
2116 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2118 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2121 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2124 // escape the string to make it safe for consoles
2125 string MakeConsoleSafe(string input)
2127 input = strreplace("\n", "", input);
2128 input = strreplace("\\", "\\\\", input);
2129 input = strreplace("$", "$$", input);
2130 input = strreplace("\"", "\\\"", input);
2135 // get true/false value of a string with multiple different inputs
2136 float InterpretBoolean(string input)
2138 switch(strtolower(input))
2150 default: return stof(input);
2156 entity ReadCSQCEntity()
2162 return findfloat(world, entnum, f);
2165 float shutdown_running;
2170 void CSQC_Shutdown()
2176 if(shutdown_running)
2178 print("Recursive shutdown detected! Only restoring cvars...\n");
2182 shutdown_running = 1;
2185 cvar_settemp_restore(); // this must be done LAST, but in any case