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 local 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 // Databases (hash tables)
240 #define DB_BUCKETS 8192
241 void db_save(float db, string pFilename)
244 fh = fopen(pFilename, FILE_WRITE);
247 print(strcat("^1Can't write DB to ", pFilename));
251 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
252 for(i = 0; i < n; ++i)
253 fputs(fh, strcat(bufstr_get(db, i), "\n"));
262 float db_load(string pFilename)
264 float db, fh, i, j, n;
269 fh = fopen(pFilename, FILE_READ);
273 if(stof(l) == DB_BUCKETS)
276 while((l = fgets(fh)))
279 bufstr_set(db, i, l);
285 // different count of buckets, or a dump?
286 // need to reorganize the database then (SLOW)
288 // note: we also parse the first line (l) in case the DB file is
289 // missing the bucket count
292 n = tokenizebyseparator(l, "\\");
293 for(j = 2; j < n; j += 2)
294 db_put(db, argv(j-1), uri_unescape(argv(j)));
296 while((l = fgets(fh)));
302 void db_dump(float db, string pFilename)
304 float fh, i, j, n, m;
305 fh = fopen(pFilename, FILE_WRITE);
307 error(strcat("Can't dump DB to ", pFilename));
310 for(i = 0; i < n; ++i)
312 m = tokenizebyseparator(bufstr_get(db, i), "\\");
313 for(j = 2; j < m; j += 2)
314 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
319 void db_close(float db)
324 string db_get(float db, string pKey)
327 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
328 return uri_unescape(infoget(bufstr_get(db, h), pKey));
331 void db_put(float db, string pKey, string pValue)
334 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
335 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
342 db = db_load("foo.db");
343 print("LOADED. FILL...\n");
344 for(i = 0; i < DB_BUCKETS; ++i)
345 db_put(db, ftos(random()), "X");
346 print("FILLED. SAVE...\n");
347 db_save(db, "foo.db");
348 print("SAVED. CLOSE...\n");
353 // Multiline text file buffers
354 float buf_load(string pFilename)
361 fh = fopen(pFilename, FILE_READ);
365 while((l = fgets(fh)))
367 bufstr_set(buf, i, l);
374 void buf_save(float buf, string pFilename)
377 fh = fopen(pFilename, FILE_WRITE);
379 error(strcat("Can't write buf to ", pFilename));
380 n = buf_getsize(buf);
381 for(i = 0; i < n; ++i)
382 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
386 string GametypeNameFromType(float g)
388 if (g == GAME_DEATHMATCH) return "dm";
389 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
390 else if (g == GAME_DOMINATION) return "dom";
391 else if (g == GAME_CTF) return "ctf";
392 else if (g == GAME_RUNEMATCH) return "rune";
393 else if (g == GAME_LMS) return "lms";
394 else if (g == GAME_ARENA) return "arena";
395 else if (g == GAME_CA) return "ca";
396 else if (g == GAME_KEYHUNT) return "kh";
397 else if (g == GAME_ONSLAUGHT) return "ons";
398 else if (g == GAME_ASSAULT) return "as";
399 else if (g == GAME_RACE) return "rc";
400 else if (g == GAME_NEXBALL) return "nexball";
401 else if (g == GAME_CTS) return "cts";
405 string mmsss(float tenths)
409 tenths = floor(tenths + 0.5);
410 minutes = floor(tenths / 600);
411 tenths -= minutes * 600;
412 s = ftos(1000 + tenths);
413 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
416 string mmssss(float hundredths)
420 hundredths = floor(hundredths + 0.5);
421 minutes = floor(hundredths / 6000);
422 hundredths -= minutes * 6000;
423 s = ftos(10000 + hundredths);
424 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
427 string ScoreString(float pFlags, float pValue)
432 pValue = floor(pValue + 0.5); // round
434 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
436 else if(pFlags & SFL_RANK)
438 valstr = ftos(pValue);
440 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
441 valstr = strcat(valstr, "th");
442 else if(substring(valstr, l - 1, 1) == "1")
443 valstr = strcat(valstr, "st");
444 else if(substring(valstr, l - 1, 1) == "2")
445 valstr = strcat(valstr, "nd");
446 else if(substring(valstr, l - 1, 1) == "3")
447 valstr = strcat(valstr, "rd");
449 valstr = strcat(valstr, "th");
451 else if(pFlags & SFL_TIME)
452 valstr = TIME_ENCODED_TOSTRING(pValue);
454 valstr = ftos(pValue);
459 vector cross(vector a, vector b)
462 '1 0 0' * (a_y * b_z - a_z * b_y)
463 + '0 1 0' * (a_z * b_x - a_x * b_z)
464 + '0 0 1' * (a_x * b_y - a_y * b_x);
467 // compressed vector format:
468 // like MD3, just even shorter
469 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
470 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
471 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
472 // length = 2^(length_encoded/8) / 8
473 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
474 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
475 // the special value 0 indicates the zero vector
477 float lengthLogTable[128];
479 float invertLengthLog(float x)
481 float l, r, m, lerr, rerr;
483 if(x >= lengthLogTable[127])
485 if(x <= lengthLogTable[0])
493 m = floor((l + r) / 2);
494 if(lengthLogTable[m] < x)
500 // now: r is >=, l is <
501 lerr = (x - lengthLogTable[l]);
502 rerr = (lengthLogTable[r] - x);
508 vector decompressShortVector(float data)
511 float pitch, yaw, len;
514 pitch = (data & 0xF000) / 0x1000;
515 yaw = (data & 0x0F80) / 0x80;
516 len = (data & 0x007F);
518 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
531 yaw = .19634954084936207740 * yaw;
532 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
533 out_x = cos(yaw) * cos(pitch);
534 out_y = sin(yaw) * cos(pitch);
538 //print("decompressed: ", vtos(out), "\n");
540 return out * lengthLogTable[len];
543 float compressShortVector(vector vec)
546 float pitch, yaw, len;
549 //print("compress: ", vtos(vec), "\n");
550 ang = vectoangles(vec);
554 if(ang_x < -90 && ang_x > +90)
555 error("BOGUS vectoangles");
556 //print("angles: ", vtos(ang), "\n");
558 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
567 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
568 len = invertLengthLog(vlen(vec));
570 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
572 return (pitch * 0x1000) + (yaw * 0x80) + len;
575 void compressShortVector_init()
580 for(i = 0; i < 128; ++i)
582 lengthLogTable[i] = l;
586 if(cvar("developer"))
588 print("Verifying vector compression table...\n");
589 for(i = 0x0F00; i < 0xFFFF; ++i)
590 if(i != compressShortVector(decompressShortVector(i)))
592 print("BROKEN vector compression: ", ftos(i));
593 print(" -> ", vtos(decompressShortVector(i)));
594 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
603 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
605 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
606 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
607 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
608 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
609 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
610 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
611 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
612 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
621 string fixPriorityList(string order, float from, float to, float subtract, float complete)
626 n = tokenize_console(order);
628 for(i = 0; i < n; ++i)
633 if(w >= from && w <= to)
634 neworder = strcat(neworder, ftos(w), " ");
638 if(w >= from && w <= to)
639 neworder = strcat(neworder, ftos(w), " ");
646 n = tokenize_console(neworder);
647 for(w = to; w >= from; --w)
649 for(i = 0; i < n; ++i)
650 if(stof(argv(i)) == w)
652 if(i == n) // not found
653 neworder = strcat(neworder, ftos(w), " ");
657 return substring(neworder, 0, strlen(neworder) - 1);
660 string mapPriorityList(string order, string(string) mapfunc)
665 n = tokenize_console(order);
667 for(i = 0; i < n; ++i)
668 neworder = strcat(neworder, mapfunc(argv(i)), " ");
670 return substring(neworder, 0, strlen(neworder) - 1);
673 string swapInPriorityList(string order, float i, float j)
678 n = tokenize_console(order);
680 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
683 for(w = 0; w < n; ++w)
686 s = strcat(s, argv(j), " ");
688 s = strcat(s, argv(i), " ");
690 s = strcat(s, argv(w), " ");
692 return substring(s, 0, strlen(s) - 1);
698 float cvar_value_issafe(string s)
700 if(strstrofs(s, "\"", 0) >= 0)
702 if(strstrofs(s, "\\", 0) >= 0)
704 if(strstrofs(s, ";", 0) >= 0)
706 if(strstrofs(s, "$", 0) >= 0)
708 if(strstrofs(s, "\r", 0) >= 0)
710 if(strstrofs(s, "\n", 0) >= 0)
716 void get_mi_min_max(float mode)
721 strunzone(mi_shortname);
722 mi_shortname = mapname;
723 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
724 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
725 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
726 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
727 mi_shortname = strzone(mi_shortname);
739 MapInfo_Get_ByName(mi_shortname, 0, 0);
740 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
742 mi_min = MapInfo_Map_mins;
743 mi_max = MapInfo_Map_maxs;
751 tracebox('1 0 0' * mi_x,
752 '0 1 0' * mi_y + '0 0 1' * mi_z,
753 '0 1 0' * ma_y + '0 0 1' * ma_z,
757 if(!trace_startsolid)
758 mi_min_x = trace_endpos_x;
760 tracebox('0 1 0' * mi_y,
761 '1 0 0' * mi_x + '0 0 1' * mi_z,
762 '1 0 0' * ma_x + '0 0 1' * ma_z,
766 if(!trace_startsolid)
767 mi_min_y = trace_endpos_y;
769 tracebox('0 0 1' * mi_z,
770 '1 0 0' * mi_x + '0 1 0' * mi_y,
771 '1 0 0' * ma_x + '0 1 0' * ma_y,
775 if(!trace_startsolid)
776 mi_min_z = trace_endpos_z;
778 tracebox('1 0 0' * ma_x,
779 '0 1 0' * mi_y + '0 0 1' * mi_z,
780 '0 1 0' * ma_y + '0 0 1' * ma_z,
784 if(!trace_startsolid)
785 mi_max_x = trace_endpos_x;
787 tracebox('0 1 0' * ma_y,
788 '1 0 0' * mi_x + '0 0 1' * mi_z,
789 '1 0 0' * ma_x + '0 0 1' * ma_z,
793 if(!trace_startsolid)
794 mi_max_y = trace_endpos_y;
796 tracebox('0 0 1' * ma_z,
797 '1 0 0' * mi_x + '0 1 0' * mi_y,
798 '1 0 0' * ma_x + '0 1 0' * ma_y,
802 if(!trace_startsolid)
803 mi_max_z = trace_endpos_z;
808 void get_mi_min_max_texcoords(float mode)
812 get_mi_min_max(mode);
817 // extend mi_picmax to get a square aspect ratio
818 // center the map in that area
819 extend = mi_picmax - mi_picmin;
820 if(extend_y > extend_x)
822 mi_picmin_x -= (extend_y - extend_x) * 0.5;
823 mi_picmax_x += (extend_y - extend_x) * 0.5;
827 mi_picmin_y -= (extend_x - extend_y) * 0.5;
828 mi_picmax_y += (extend_x - extend_y) * 0.5;
831 // add another some percent
832 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
836 // calculate the texcoords
837 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
838 // first the two corners of the origin
839 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
840 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
841 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
842 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
843 // then the other corners
844 mi_pictexcoord1_x = mi_pictexcoord0_x;
845 mi_pictexcoord1_y = mi_pictexcoord2_y;
846 mi_pictexcoord3_x = mi_pictexcoord2_x;
847 mi_pictexcoord3_y = mi_pictexcoord0_y;
852 void cvar_settemp(string pKey, string pValue)
854 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
856 void cvar_settemp_restore()
858 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
861 void cvar_settemp(string pKey, string pValue)
865 if(cvar_string(pKey) == pValue)
867 i = cvar("settemp_idx");
868 cvar_set("settemp_idx", ftos(i+1));
869 settemp_var = strcat("_settemp_x", ftos(i));
871 registercvar(settemp_var, "", 0);
873 registercvar(settemp_var, "");
875 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
876 cvar_set(settemp_var, cvar_string(pKey));
877 cvar_set(pKey, pValue);
880 void cvar_settemp_restore()
882 // undo what cvar_settemp did
884 n = tokenize_console(cvar_string("settemp_list"));
885 for(i = 0; i < n - 3; i += 3)
886 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
887 cvar_set("settemp_list", "0");
891 float almost_equals(float a, float b)
894 eps = (max(a, -a) + max(b, -b)) * 0.001;
895 if(a - b < eps && b - a < eps)
900 float almost_in_bounds(float a, float b, float c)
903 eps = (max(a, -a) + max(c, -c)) * 0.001;
904 return b == median(a - eps, b, c + eps);
907 float power2of(float e)
911 float log2of(float x)
913 // NOTE: generated code
986 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
993 return (rgb_y - rgb_z) / (ma - mi);
995 return (rgb_y - rgb_z) / (ma - mi) + 6;
998 return (rgb_z - rgb_x) / (ma - mi) + 2;
999 else // if(ma == rgb_z)
1000 return (rgb_x - rgb_y) / (ma - mi) + 4;
1003 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1007 hue -= 6 * floor(hue / 6);
1009 //else if(ma == rgb_x)
1010 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1014 rgb_y = hue * (ma - mi) + mi;
1017 //else if(ma == rgb_y)
1018 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1021 rgb_x = (2 - hue) * (ma - mi) + mi;
1029 rgb_z = (hue - 2) * (ma - mi) + mi;
1031 //else // if(ma == rgb_z)
1032 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1036 rgb_y = (4 - hue) * (ma - mi) + mi;
1041 rgb_x = (hue - 4) * (ma - mi) + mi;
1045 //else if(ma == rgb_x)
1046 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1047 else // if(hue <= 6)
1051 rgb_z = (6 - hue) * (ma - mi) + mi;
1057 vector rgb_to_hsv(vector rgb)
1062 mi = min3(rgb_x, rgb_y, rgb_z);
1063 ma = max3(rgb_x, rgb_y, rgb_z);
1065 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1076 vector hsv_to_rgb(vector hsv)
1078 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1081 vector rgb_to_hsl(vector rgb)
1086 mi = min3(rgb_x, rgb_y, rgb_z);
1087 ma = max3(rgb_x, rgb_y, rgb_z);
1089 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1091 hsl_z = 0.5 * (mi + ma);
1094 else if(hsl_z <= 0.5)
1095 hsl_y = (ma - mi) / (2*hsl_z);
1096 else // if(hsl_z > 0.5)
1097 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1102 vector hsl_to_rgb(vector hsl)
1104 float mi, ma, maminusmi;
1107 maminusmi = hsl_y * 2 * hsl_z;
1109 maminusmi = hsl_y * (2 - 2 * hsl_z);
1111 // hsl_z = 0.5 * mi + 0.5 * ma
1112 // maminusmi = - mi + ma
1113 mi = hsl_z - 0.5 * maminusmi;
1114 ma = hsl_z + 0.5 * maminusmi;
1116 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1119 string rgb_to_hexcolor(vector rgb)
1124 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1125 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1126 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1130 // requires that m2>m1 in all coordinates, and that m4>m3
1131 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;};
1133 // requires the same, but is a stronger condition
1134 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;};
1139 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1141 float ICanHasKallerz;
1143 // detect color codes support in the width function
1144 ICanHasKallerz = (w("^7", theSize) == 0);
1147 // The following function is SLOW.
1148 // For your safety and for the protection of those around you...
1149 // DO NOT CALL THIS AT HOME.
1150 // No really, don't.
1151 if(w(theText, theSize) <= maxWidth)
1152 return strlen(theText); // yeah!
1154 // binary search for right place to cut string
1156 float left, right, middle; // this always works
1158 right = strlen(theText); // this always fails
1161 middle = floor((left + right) / 2);
1162 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1167 while(left < right - 1);
1171 // NOTE: when color codes are involved, this binary search is,
1172 // mathematically, BROKEN. However, it is obviously guaranteed to
1173 // terminate, as the range still halves each time - but nevertheless, it is
1174 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1175 // range, and "right" is outside).
1177 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1178 // and decrease left on the basis of the chars detected of the truncated tag
1179 // Even if the ^xrgb tag is not complete/correct, left is decreased
1180 // (sometimes too much but with a correct result)
1181 // it fixes also ^[0-9]
1182 while(left >= 1 && substring(theText, left-1, 1) == "^")
1185 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1187 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1189 ch = str2chr(theText, left-1);
1190 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1193 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1195 ch = str2chr(theText, left-2);
1196 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1198 ch = str2chr(theText, left-1);
1199 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1208 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1210 float ICanHasKallerz;
1212 // detect color codes support in the width function
1213 ICanHasKallerz = (w("^7") == 0);
1216 // The following function is SLOW.
1217 // For your safety and for the protection of those around you...
1218 // DO NOT CALL THIS AT HOME.
1219 // No really, don't.
1220 if(w(theText) <= maxWidth)
1221 return strlen(theText); // yeah!
1223 // binary search for right place to cut string
1225 float left, right, middle; // this always works
1227 right = strlen(theText); // this always fails
1230 middle = floor((left + right) / 2);
1231 if(w(substring(theText, 0, middle)) <= maxWidth)
1236 while(left < right - 1);
1240 // NOTE: when color codes are involved, this binary search is,
1241 // mathematically, BROKEN. However, it is obviously guaranteed to
1242 // terminate, as the range still halves each time - but nevertheless, it is
1243 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1244 // range, and "right" is outside).
1246 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1247 // and decrease left on the basis of the chars detected of the truncated tag
1248 // Even if the ^xrgb tag is not complete/correct, left is decreased
1249 // (sometimes too much but with a correct result)
1250 // it fixes also ^[0-9]
1251 while(left >= 1 && substring(theText, left-1, 1) == "^")
1254 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1256 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1258 ch = str2chr(theText, left-1);
1259 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1262 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1264 ch = str2chr(theText, left-2);
1265 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1267 ch = str2chr(theText, left-1);
1268 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1277 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1283 s = getWrappedLine_remaining;
1285 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1286 if(cantake > 0 && cantake < strlen(s))
1289 while(take > 0 && substring(s, take, 1) != " ")
1293 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1294 if(getWrappedLine_remaining == "")
1295 getWrappedLine_remaining = string_null;
1296 return substring(s, 0, cantake);
1300 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1301 if(getWrappedLine_remaining == "")
1302 getWrappedLine_remaining = string_null;
1303 return substring(s, 0, take);
1308 getWrappedLine_remaining = string_null;
1313 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1319 s = getWrappedLine_remaining;
1321 cantake = textLengthUpToLength(s, w, tw);
1322 if(cantake > 0 && cantake < strlen(s))
1325 while(take > 0 && substring(s, take, 1) != " ")
1329 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1330 if(getWrappedLine_remaining == "")
1331 getWrappedLine_remaining = string_null;
1332 return substring(s, 0, cantake);
1336 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1337 if(getWrappedLine_remaining == "")
1338 getWrappedLine_remaining = string_null;
1339 return substring(s, 0, take);
1344 getWrappedLine_remaining = string_null;
1349 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1351 if(tw(theText, theFontSize) <= maxWidth)
1354 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1357 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1359 if(tw(theText) <= maxWidth)
1362 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1365 float isGametypeInFilter(float gt, float tp, string pattern)
1367 string subpattern, subpattern2, subpattern3;
1368 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1370 subpattern2 = ",teams,";
1372 subpattern2 = ",noteams,";
1373 if(gt == GAME_RACE || gt == GAME_CTS)
1374 subpattern3 = ",race,";
1376 subpattern3 = string_null;
1378 if(substring(pattern, 0, 1) == "-")
1380 pattern = substring(pattern, 1, strlen(pattern) - 1);
1381 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1383 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1385 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1390 if(substring(pattern, 0, 1) == "+")
1391 pattern = substring(pattern, 1, strlen(pattern) - 1);
1392 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1393 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1394 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1400 void shuffle(float n, swapfunc_t swap, entity pass)
1403 for(i = 1; i < n; ++i)
1405 // swap i-th item at a random position from 0 to i
1406 // proof for even distribution:
1409 // item n+1 gets at any position with chance 1/(n+1)
1410 // all others will get their 1/n chance reduced by factor n/(n+1)
1411 // to be on place n+1, their chance will be 1/(n+1)
1412 // 1/n * n/(n+1) = 1/(n+1)
1414 j = floor(random() * (i + 1));
1420 string substring_range(string s, float b, float e)
1422 return substring(s, b, e - b);
1425 string swapwords(string str, float i, float j)
1428 string s1, s2, s3, s4, s5;
1429 float si, ei, sj, ej, s0, en;
1430 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1431 si = argv_start_index(i);
1432 sj = argv_start_index(j);
1433 ei = argv_end_index(i);
1434 ej = argv_end_index(j);
1435 s0 = argv_start_index(0);
1436 en = argv_end_index(n-1);
1437 s1 = substring_range(str, s0, si);
1438 s2 = substring_range(str, si, ei);
1439 s3 = substring_range(str, ei, sj);
1440 s4 = substring_range(str, sj, ej);
1441 s5 = substring_range(str, ej, en);
1442 return strcat(s1, s4, s3, s2, s5);
1445 string _shufflewords_str;
1446 void _shufflewords_swapfunc(float i, float j, entity pass)
1448 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1450 string shufflewords(string str)
1453 _shufflewords_str = str;
1454 n = tokenizebyseparator(str, " ");
1455 shuffle(n, _shufflewords_swapfunc, world);
1456 str = _shufflewords_str;
1457 _shufflewords_str = string_null;
1461 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1477 // actually, every number solves the equation!
1488 if(a > 0) // put the smaller solution first
1490 v_x = ((-b)-D) / (2*a);
1491 v_y = ((-b)+D) / (2*a);
1495 v_x = (-b+D) / (2*a);
1496 v_y = (-b-D) / (2*a);
1502 // complex solutions!
1516 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1517 float _unacceptable_compiler_bug_1_b() { return 1; }
1518 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1519 float _unacceptable_compiler_bug_1_d() { return 1; }
1521 void check_unacceptable_compiler_bugs()
1523 if(cvar("_allow_unacceptable_compiler_bugs"))
1525 tokenize_console("foo bar");
1526 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1527 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.");
1530 float compressShotOrigin(vector v)
1534 y = rint(v_y * 4) + 128;
1535 z = rint(v_z * 4) + 128;
1536 if(x > 255 || x < 0)
1538 print("shot origin ", vtos(v), " x out of bounds\n");
1539 x = bound(0, x, 255);
1541 if(y > 255 || y < 0)
1543 print("shot origin ", vtos(v), " y out of bounds\n");
1544 y = bound(0, y, 255);
1546 if(z > 255 || z < 0)
1548 print("shot origin ", vtos(v), " z out of bounds\n");
1549 z = bound(0, z, 255);
1551 return x * 0x10000 + y * 0x100 + z;
1553 vector decompressShotOrigin(float f)
1556 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1557 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1558 v_z = ((f & 0xFF) - 128) / 4;
1562 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1564 float start, end, root, child;
1567 start = floor((n - 2) / 2);
1570 // siftdown(start, count-1);
1572 while(root * 2 + 1 <= n-1)
1574 child = root * 2 + 1;
1576 if(cmp(child, child+1, pass) < 0)
1578 if(cmp(root, child, pass) < 0)
1580 swap(root, child, pass);
1596 // siftdown(0, end);
1598 while(root * 2 + 1 <= end)
1600 child = root * 2 + 1;
1601 if(child < end && cmp(child, child+1, pass) < 0)
1603 if(cmp(root, child, pass) < 0)
1605 swap(root, child, pass);
1615 void RandomSelection_Init()
1617 RandomSelection_totalweight = 0;
1618 RandomSelection_chosen_ent = world;
1619 RandomSelection_chosen_float = 0;
1620 RandomSelection_chosen_string = string_null;
1621 RandomSelection_best_priority = -1;
1623 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1625 if(priority > RandomSelection_best_priority)
1627 RandomSelection_best_priority = priority;
1628 RandomSelection_chosen_ent = e;
1629 RandomSelection_chosen_float = f;
1630 RandomSelection_chosen_string = s;
1631 RandomSelection_totalweight = weight;
1633 else if(priority == RandomSelection_best_priority)
1635 RandomSelection_totalweight += weight;
1636 if(random() * RandomSelection_totalweight <= weight)
1638 RandomSelection_chosen_ent = e;
1639 RandomSelection_chosen_float = f;
1640 RandomSelection_chosen_string = s;
1645 vector healtharmor_maxdamage(float h, float a, float armorblock)
1647 // NOTE: we'll always choose the SMALLER value...
1648 float healthdamage, armordamage, armorideal;
1650 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1651 armordamage = a + (h - 1); // damage we can take if we could use more armor
1652 armorideal = healthdamage * armorblock;
1654 if(armordamage < healthdamage)
1667 vector healtharmor_applydamage(float a, float armorblock, float damage)
1670 v_y = bound(0, damage * armorblock, a); // save
1671 v_x = bound(0, damage - v_y, damage); // take
1676 string getcurrentmod()
1680 m = cvar_string("fs_gamedir");
1681 n = tokenize_console(m);
1693 v = ReadShort() * 256; // note: this is signed
1694 v += ReadByte(); // note: this is unsigned
1698 void WriteInt24_t(float dest, float val)
1701 WriteShort(dest, (v = floor(val / 256)));
1702 WriteByte(dest, val - v * 256); // 0..255
1707 float float2range11(float f)
1709 // continuous function mapping all reals into -1..1
1710 return f / (fabs(f) + 1);
1713 float float2range01(float f)
1715 // continuous function mapping all reals into 0..1
1716 return 0.5 + 0.5 * float2range11(f);
1719 // from the GNU Scientific Library
1720 float gsl_ran_gaussian_lastvalue;
1721 float gsl_ran_gaussian_lastvalue_set;
1722 float gsl_ran_gaussian(float sigma)
1725 if(gsl_ran_gaussian_lastvalue_set)
1727 gsl_ran_gaussian_lastvalue_set = 0;
1728 return sigma * gsl_ran_gaussian_lastvalue;
1732 a = random() * 2 * M_PI;
1733 b = sqrt(-2 * log(random()));
1734 gsl_ran_gaussian_lastvalue = cos(a) * b;
1735 gsl_ran_gaussian_lastvalue_set = 1;
1736 return sigma * sin(a) * b;
1740 string car(string s)
1743 o = strstrofs(s, " ", 0);
1746 return substring(s, 0, o);
1748 string cdr(string s)
1751 o = strstrofs(s, " ", 0);
1754 return substring(s, o + 1, strlen(s) - (o + 1));
1756 float matchacl(string acl, string str)
1763 t = car(acl); acl = cdr(acl);
1765 if(substring(t, 0, 1) == "-")
1768 t = substring(t, 1, strlen(t) - 1);
1770 else if(substring(t, 0, 1) == "+")
1771 t = substring(t, 1, strlen(t) - 1);
1772 if(substring(t, -1, 1) == "*")
1774 t = substring(t, 0, strlen(t) - 1);
1775 s = substring(s, 0, strlen(t));
1787 float startsWith(string haystack, string needle)
1789 return substring(haystack, 0, strlen(needle)) == needle;
1791 float startsWithNocase(string haystack, string needle)
1793 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1796 string get_model_datafilename(string m, float sk, string fil)
1801 m = "models/player/*_";
1803 m = strcat(m, ftos(sk));
1806 return strcat(m, ".", fil);
1809 float get_model_parameters(string m, float sk)
1814 get_model_parameters_modelname = string_null;
1815 get_model_parameters_modelskin = -1;
1816 get_model_parameters_name = string_null;
1817 get_model_parameters_species = -1;
1818 get_model_parameters_sex = string_null;
1819 get_model_parameters_weight = -1;
1820 get_model_parameters_age = -1;
1821 get_model_parameters_desc = string_null;
1827 if(substring(m, -4, -1) != ".txt")
1829 if(substring(m, -6, 1) != "_")
1831 sk = stof(substring(m, -5, 1));
1832 m = substring(m, 0, -7);
1835 fn = get_model_datafilename(m, sk, "txt");
1836 fh = fopen(fn, FILE_READ);
1840 get_model_parameters_modelname = m;
1841 get_model_parameters_modelskin = sk;
1842 while((s = fgets(fh)))
1845 break; // next lines will be description
1849 get_model_parameters_name = s;
1853 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1854 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1855 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1856 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1857 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1858 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1859 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1862 get_model_parameters_sex = s;
1864 get_model_parameters_weight = stof(s);
1866 get_model_parameters_age = stof(s);
1869 while((s = fgets(fh)))
1871 if(get_model_parameters_desc)
1872 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1874 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1882 vector vec2(vector v)
1889 vector NearestPointOnBox(entity box, vector org)
1891 vector m1, m2, nearest;
1893 m1 = box.mins + box.origin;
1894 m2 = box.maxs + box.origin;
1896 nearest_x = bound(m1_x, org_x, m2_x);
1897 nearest_y = bound(m1_y, org_y, m2_y);
1898 nearest_z = bound(m1_z, org_z, m2_z);
1904 float vercmp_recursive(string v1, string v2)
1910 dot1 = strstrofs(v1, ".", 0);
1911 dot2 = strstrofs(v2, ".", 0);
1915 s1 = substring(v1, 0, dot1);
1919 s2 = substring(v2, 0, dot2);
1921 r = stof(s1) - stof(s2);
1925 r = strcasecmp(s1, s2);
1938 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1941 float vercmp(string v1, string v2)
1943 if(strcasecmp(v1, v2) == 0) // early out check
1952 return vercmp_recursive(v1, v2);