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";
402 else if (g == GAME_FREEZETAG) return "freezetag";
403 else if (g == GAME_KEEPAWAY) return "ka";
407 string mmsss(float tenths)
411 tenths = floor(tenths + 0.5);
412 minutes = floor(tenths / 600);
413 tenths -= minutes * 600;
414 s = ftos(1000 + tenths);
415 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
418 string mmssss(float hundredths)
422 hundredths = floor(hundredths + 0.5);
423 minutes = floor(hundredths / 6000);
424 hundredths -= minutes * 6000;
425 s = ftos(10000 + hundredths);
426 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
429 string ScoreString(float pFlags, float pValue)
434 pValue = floor(pValue + 0.5); // round
436 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
438 else if(pFlags & SFL_RANK)
440 valstr = ftos(pValue);
442 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
443 valstr = strcat(valstr, "th");
444 else if(substring(valstr, l - 1, 1) == "1")
445 valstr = strcat(valstr, "st");
446 else if(substring(valstr, l - 1, 1) == "2")
447 valstr = strcat(valstr, "nd");
448 else if(substring(valstr, l - 1, 1) == "3")
449 valstr = strcat(valstr, "rd");
451 valstr = strcat(valstr, "th");
453 else if(pFlags & SFL_TIME)
454 valstr = TIME_ENCODED_TOSTRING(pValue);
456 valstr = ftos(pValue);
461 vector cross(vector a, vector b)
464 '1 0 0' * (a_y * b_z - a_z * b_y)
465 + '0 1 0' * (a_z * b_x - a_x * b_z)
466 + '0 0 1' * (a_x * b_y - a_y * b_x);
469 // compressed vector format:
470 // like MD3, just even shorter
471 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
472 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
473 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
474 // length = 2^(length_encoded/8) / 8
475 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
476 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
477 // the special value 0 indicates the zero vector
479 float lengthLogTable[128];
481 float invertLengthLog(float x)
483 float l, r, m, lerr, rerr;
485 if(x >= lengthLogTable[127])
487 if(x <= lengthLogTable[0])
495 m = floor((l + r) / 2);
496 if(lengthLogTable[m] < x)
502 // now: r is >=, l is <
503 lerr = (x - lengthLogTable[l]);
504 rerr = (lengthLogTable[r] - x);
510 vector decompressShortVector(float data)
513 float pitch, yaw, len;
516 pitch = (data & 0xF000) / 0x1000;
517 yaw = (data & 0x0F80) / 0x80;
518 len = (data & 0x007F);
520 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
533 yaw = .19634954084936207740 * yaw;
534 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
535 out_x = cos(yaw) * cos(pitch);
536 out_y = sin(yaw) * cos(pitch);
540 //print("decompressed: ", vtos(out), "\n");
542 return out * lengthLogTable[len];
545 float compressShortVector(vector vec)
548 float pitch, yaw, len;
551 //print("compress: ", vtos(vec), "\n");
552 ang = vectoangles(vec);
556 if(ang_x < -90 && ang_x > +90)
557 error("BOGUS vectoangles");
558 //print("angles: ", vtos(ang), "\n");
560 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
569 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
570 len = invertLengthLog(vlen(vec));
572 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
574 return (pitch * 0x1000) + (yaw * 0x80) + len;
577 void compressShortVector_init()
582 for(i = 0; i < 128; ++i)
584 lengthLogTable[i] = l;
588 if(cvar("developer"))
590 print("Verifying vector compression table...\n");
591 for(i = 0x0F00; i < 0xFFFF; ++i)
592 if(i != compressShortVector(decompressShortVector(i)))
594 print("BROKEN vector compression: ", ftos(i));
595 print(" -> ", vtos(decompressShortVector(i)));
596 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
605 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
607 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
608 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
609 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
610 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
611 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
612 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623 string fixPriorityList(string order, float from, float to, float subtract, float complete)
628 n = tokenize_console(order);
630 for(i = 0; i < n; ++i)
635 if(w >= from && w <= to)
636 neworder = strcat(neworder, ftos(w), " ");
640 if(w >= from && w <= to)
641 neworder = strcat(neworder, ftos(w), " ");
648 n = tokenize_console(neworder);
649 for(w = to; w >= from; --w)
651 for(i = 0; i < n; ++i)
652 if(stof(argv(i)) == w)
654 if(i == n) // not found
655 neworder = strcat(neworder, ftos(w), " ");
659 return substring(neworder, 0, strlen(neworder) - 1);
662 string mapPriorityList(string order, string(string) mapfunc)
667 n = tokenize_console(order);
669 for(i = 0; i < n; ++i)
670 neworder = strcat(neworder, mapfunc(argv(i)), " ");
672 return substring(neworder, 0, strlen(neworder) - 1);
675 string swapInPriorityList(string order, float i, float j)
680 n = tokenize_console(order);
682 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
685 for(w = 0; w < n; ++w)
688 s = strcat(s, argv(j), " ");
690 s = strcat(s, argv(i), " ");
692 s = strcat(s, argv(w), " ");
694 return substring(s, 0, strlen(s) - 1);
700 float cvar_value_issafe(string s)
702 if(strstrofs(s, "\"", 0) >= 0)
704 if(strstrofs(s, "\\", 0) >= 0)
706 if(strstrofs(s, ";", 0) >= 0)
708 if(strstrofs(s, "$", 0) >= 0)
710 if(strstrofs(s, "\r", 0) >= 0)
712 if(strstrofs(s, "\n", 0) >= 0)
718 void get_mi_min_max(float mode)
723 strunzone(mi_shortname);
724 mi_shortname = mapname;
725 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
726 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
727 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
728 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
729 mi_shortname = strzone(mi_shortname);
741 MapInfo_Get_ByName(mi_shortname, 0, 0);
742 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
744 mi_min = MapInfo_Map_mins;
745 mi_max = MapInfo_Map_maxs;
753 tracebox('1 0 0' * mi_x,
754 '0 1 0' * mi_y + '0 0 1' * mi_z,
755 '0 1 0' * ma_y + '0 0 1' * ma_z,
759 if(!trace_startsolid)
760 mi_min_x = trace_endpos_x;
762 tracebox('0 1 0' * mi_y,
763 '1 0 0' * mi_x + '0 0 1' * mi_z,
764 '1 0 0' * ma_x + '0 0 1' * ma_z,
768 if(!trace_startsolid)
769 mi_min_y = trace_endpos_y;
771 tracebox('0 0 1' * mi_z,
772 '1 0 0' * mi_x + '0 1 0' * mi_y,
773 '1 0 0' * ma_x + '0 1 0' * ma_y,
777 if(!trace_startsolid)
778 mi_min_z = trace_endpos_z;
780 tracebox('1 0 0' * ma_x,
781 '0 1 0' * mi_y + '0 0 1' * mi_z,
782 '0 1 0' * ma_y + '0 0 1' * ma_z,
786 if(!trace_startsolid)
787 mi_max_x = trace_endpos_x;
789 tracebox('0 1 0' * ma_y,
790 '1 0 0' * mi_x + '0 0 1' * mi_z,
791 '1 0 0' * ma_x + '0 0 1' * ma_z,
795 if(!trace_startsolid)
796 mi_max_y = trace_endpos_y;
798 tracebox('0 0 1' * ma_z,
799 '1 0 0' * mi_x + '0 1 0' * mi_y,
800 '1 0 0' * ma_x + '0 1 0' * ma_y,
804 if(!trace_startsolid)
805 mi_max_z = trace_endpos_z;
810 void get_mi_min_max_texcoords(float mode)
814 get_mi_min_max(mode);
819 // extend mi_picmax to get a square aspect ratio
820 // center the map in that area
821 extend = mi_picmax - mi_picmin;
822 if(extend_y > extend_x)
824 mi_picmin_x -= (extend_y - extend_x) * 0.5;
825 mi_picmax_x += (extend_y - extend_x) * 0.5;
829 mi_picmin_y -= (extend_x - extend_y) * 0.5;
830 mi_picmax_y += (extend_x - extend_y) * 0.5;
833 // add another some percent
834 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
838 // calculate the texcoords
839 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
840 // first the two corners of the origin
841 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
842 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
843 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
844 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
845 // then the other corners
846 mi_pictexcoord1_x = mi_pictexcoord0_x;
847 mi_pictexcoord1_y = mi_pictexcoord2_y;
848 mi_pictexcoord3_x = mi_pictexcoord2_x;
849 mi_pictexcoord3_y = mi_pictexcoord0_y;
854 void cvar_settemp(string pKey, string pValue)
856 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
858 void cvar_settemp_restore()
860 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
863 void cvar_settemp(string pKey, string pValue)
867 if(cvar_string(pKey) == pValue)
869 i = cvar("settemp_idx");
870 cvar_set("settemp_idx", ftos(i+1));
871 settemp_var = strcat("_settemp_x", ftos(i));
873 registercvar(settemp_var, "", 0);
875 registercvar(settemp_var, "");
877 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
878 cvar_set(settemp_var, cvar_string(pKey));
879 cvar_set(pKey, pValue);
882 void cvar_settemp_restore()
884 // undo what cvar_settemp did
886 n = tokenize_console(cvar_string("settemp_list"));
887 for(i = 0; i < n - 3; i += 3)
888 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
889 cvar_set("settemp_list", "0");
893 float almost_equals(float a, float b)
896 eps = (max(a, -a) + max(b, -b)) * 0.001;
897 if(a - b < eps && b - a < eps)
902 float almost_in_bounds(float a, float b, float c)
905 eps = (max(a, -a) + max(c, -c)) * 0.001;
906 return b == median(a - eps, b, c + eps);
909 float power2of(float e)
913 float log2of(float x)
915 // NOTE: generated code
988 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
995 return (rgb_y - rgb_z) / (ma - mi);
997 return (rgb_y - rgb_z) / (ma - mi) + 6;
1000 return (rgb_z - rgb_x) / (ma - mi) + 2;
1001 else // if(ma == rgb_z)
1002 return (rgb_x - rgb_y) / (ma - mi) + 4;
1005 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1009 hue -= 6 * floor(hue / 6);
1011 //else if(ma == rgb_x)
1012 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1016 rgb_y = hue * (ma - mi) + mi;
1019 //else if(ma == rgb_y)
1020 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1023 rgb_x = (2 - hue) * (ma - mi) + mi;
1031 rgb_z = (hue - 2) * (ma - mi) + mi;
1033 //else // if(ma == rgb_z)
1034 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1038 rgb_y = (4 - hue) * (ma - mi) + mi;
1043 rgb_x = (hue - 4) * (ma - mi) + mi;
1047 //else if(ma == rgb_x)
1048 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1049 else // if(hue <= 6)
1053 rgb_z = (6 - hue) * (ma - mi) + mi;
1059 vector rgb_to_hsv(vector rgb)
1064 mi = min3(rgb_x, rgb_y, rgb_z);
1065 ma = max3(rgb_x, rgb_y, rgb_z);
1067 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1078 vector hsv_to_rgb(vector hsv)
1080 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1083 vector rgb_to_hsl(vector rgb)
1088 mi = min3(rgb_x, rgb_y, rgb_z);
1089 ma = max3(rgb_x, rgb_y, rgb_z);
1091 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1093 hsl_z = 0.5 * (mi + ma);
1096 else if(hsl_z <= 0.5)
1097 hsl_y = (ma - mi) / (2*hsl_z);
1098 else // if(hsl_z > 0.5)
1099 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1104 vector hsl_to_rgb(vector hsl)
1106 float mi, ma, maminusmi;
1109 maminusmi = hsl_y * 2 * hsl_z;
1111 maminusmi = hsl_y * (2 - 2 * hsl_z);
1113 // hsl_z = 0.5 * mi + 0.5 * ma
1114 // maminusmi = - mi + ma
1115 mi = hsl_z - 0.5 * maminusmi;
1116 ma = hsl_z + 0.5 * maminusmi;
1118 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1121 string rgb_to_hexcolor(vector rgb)
1126 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1127 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1128 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1132 // requires that m2>m1 in all coordinates, and that m4>m3
1133 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;};
1135 // requires the same, but is a stronger condition
1136 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;};
1141 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1143 float ICanHasKallerz;
1145 // detect color codes support in the width function
1146 ICanHasKallerz = (w("^7", theSize) == 0);
1149 // The following function is SLOW.
1150 // For your safety and for the protection of those around you...
1151 // DO NOT CALL THIS AT HOME.
1152 // No really, don't.
1153 if(w(theText, theSize) <= maxWidth)
1154 return strlen(theText); // yeah!
1156 // binary search for right place to cut string
1158 float left, right, middle; // this always works
1160 right = strlen(theText); // this always fails
1163 middle = floor((left + right) / 2);
1164 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1169 while(left < right - 1);
1173 // NOTE: when color codes are involved, this binary search is,
1174 // mathematically, BROKEN. However, it is obviously guaranteed to
1175 // terminate, as the range still halves each time - but nevertheless, it is
1176 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1177 // range, and "right" is outside).
1179 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1180 // and decrease left on the basis of the chars detected of the truncated tag
1181 // Even if the ^xrgb tag is not complete/correct, left is decreased
1182 // (sometimes too much but with a correct result)
1183 // it fixes also ^[0-9]
1184 while(left >= 1 && substring(theText, left-1, 1) == "^")
1187 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1189 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1191 ch = str2chr(theText, left-1);
1192 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1195 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1197 ch = str2chr(theText, left-2);
1198 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1200 ch = str2chr(theText, left-1);
1201 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1210 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1212 float ICanHasKallerz;
1214 // detect color codes support in the width function
1215 ICanHasKallerz = (w("^7") == 0);
1218 // The following function is SLOW.
1219 // For your safety and for the protection of those around you...
1220 // DO NOT CALL THIS AT HOME.
1221 // No really, don't.
1222 if(w(theText) <= maxWidth)
1223 return strlen(theText); // yeah!
1225 // binary search for right place to cut string
1227 float left, right, middle; // this always works
1229 right = strlen(theText); // this always fails
1232 middle = floor((left + right) / 2);
1233 if(w(substring(theText, 0, middle)) <= maxWidth)
1238 while(left < right - 1);
1242 // NOTE: when color codes are involved, this binary search is,
1243 // mathematically, BROKEN. However, it is obviously guaranteed to
1244 // terminate, as the range still halves each time - but nevertheless, it is
1245 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1246 // range, and "right" is outside).
1248 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1249 // and decrease left on the basis of the chars detected of the truncated tag
1250 // Even if the ^xrgb tag is not complete/correct, left is decreased
1251 // (sometimes too much but with a correct result)
1252 // it fixes also ^[0-9]
1253 while(left >= 1 && substring(theText, left-1, 1) == "^")
1256 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1258 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1260 ch = str2chr(theText, left-1);
1261 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1264 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1266 ch = str2chr(theText, left-2);
1267 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1269 ch = str2chr(theText, left-1);
1270 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1279 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1285 s = getWrappedLine_remaining;
1287 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1288 if(cantake > 0 && cantake < strlen(s))
1291 while(take > 0 && substring(s, take, 1) != " ")
1295 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1296 if(getWrappedLine_remaining == "")
1297 getWrappedLine_remaining = string_null;
1298 return substring(s, 0, cantake);
1302 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1303 if(getWrappedLine_remaining == "")
1304 getWrappedLine_remaining = string_null;
1305 return substring(s, 0, take);
1310 getWrappedLine_remaining = string_null;
1315 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1321 s = getWrappedLine_remaining;
1323 cantake = textLengthUpToLength(s, w, tw);
1324 if(cantake > 0 && cantake < strlen(s))
1327 while(take > 0 && substring(s, take, 1) != " ")
1331 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1332 if(getWrappedLine_remaining == "")
1333 getWrappedLine_remaining = string_null;
1334 return substring(s, 0, cantake);
1338 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1339 if(getWrappedLine_remaining == "")
1340 getWrappedLine_remaining = string_null;
1341 return substring(s, 0, take);
1346 getWrappedLine_remaining = string_null;
1351 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1353 if(tw(theText, theFontSize) <= maxWidth)
1356 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1359 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1361 if(tw(theText) <= maxWidth)
1364 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1367 float isGametypeInFilter(float gt, float tp, string pattern)
1369 string subpattern, subpattern2, subpattern3;
1370 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1372 subpattern2 = ",teams,";
1374 subpattern2 = ",noteams,";
1375 if(gt == GAME_RACE || gt == GAME_CTS)
1376 subpattern3 = ",race,";
1378 subpattern3 = string_null;
1380 if(substring(pattern, 0, 1) == "-")
1382 pattern = substring(pattern, 1, strlen(pattern) - 1);
1383 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1385 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1387 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1392 if(substring(pattern, 0, 1) == "+")
1393 pattern = substring(pattern, 1, strlen(pattern) - 1);
1394 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1395 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1396 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1402 void shuffle(float n, swapfunc_t swap, entity pass)
1405 for(i = 1; i < n; ++i)
1407 // swap i-th item at a random position from 0 to i
1408 // proof for even distribution:
1411 // item n+1 gets at any position with chance 1/(n+1)
1412 // all others will get their 1/n chance reduced by factor n/(n+1)
1413 // to be on place n+1, their chance will be 1/(n+1)
1414 // 1/n * n/(n+1) = 1/(n+1)
1416 j = floor(random() * (i + 1));
1422 string substring_range(string s, float b, float e)
1424 return substring(s, b, e - b);
1427 string swapwords(string str, float i, float j)
1430 string s1, s2, s3, s4, s5;
1431 float si, ei, sj, ej, s0, en;
1432 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1433 si = argv_start_index(i);
1434 sj = argv_start_index(j);
1435 ei = argv_end_index(i);
1436 ej = argv_end_index(j);
1437 s0 = argv_start_index(0);
1438 en = argv_end_index(n-1);
1439 s1 = substring_range(str, s0, si);
1440 s2 = substring_range(str, si, ei);
1441 s3 = substring_range(str, ei, sj);
1442 s4 = substring_range(str, sj, ej);
1443 s5 = substring_range(str, ej, en);
1444 return strcat(s1, s4, s3, s2, s5);
1447 string _shufflewords_str;
1448 void _shufflewords_swapfunc(float i, float j, entity pass)
1450 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1452 string shufflewords(string str)
1455 _shufflewords_str = str;
1456 n = tokenizebyseparator(str, " ");
1457 shuffle(n, _shufflewords_swapfunc, world);
1458 str = _shufflewords_str;
1459 _shufflewords_str = string_null;
1463 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1479 // actually, every number solves the equation!
1490 if(a > 0) // put the smaller solution first
1492 v_x = ((-b)-D) / (2*a);
1493 v_y = ((-b)+D) / (2*a);
1497 v_x = (-b+D) / (2*a);
1498 v_y = (-b-D) / (2*a);
1504 // complex solutions!
1518 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1519 float _unacceptable_compiler_bug_1_b() { return 1; }
1520 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1521 float _unacceptable_compiler_bug_1_d() { return 1; }
1523 void check_unacceptable_compiler_bugs()
1525 if(cvar("_allow_unacceptable_compiler_bugs"))
1527 tokenize_console("foo bar");
1528 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1529 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.");
1532 float compressShotOrigin(vector v)
1536 y = rint(v_y * 4) + 128;
1537 z = rint(v_z * 4) + 128;
1538 if(x > 255 || x < 0)
1540 print("shot origin ", vtos(v), " x out of bounds\n");
1541 x = bound(0, x, 255);
1543 if(y > 255 || y < 0)
1545 print("shot origin ", vtos(v), " y out of bounds\n");
1546 y = bound(0, y, 255);
1548 if(z > 255 || z < 0)
1550 print("shot origin ", vtos(v), " z out of bounds\n");
1551 z = bound(0, z, 255);
1553 return x * 0x10000 + y * 0x100 + z;
1555 vector decompressShotOrigin(float f)
1558 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1559 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1560 v_z = ((f & 0xFF) - 128) / 4;
1564 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1566 float start, end, root, child;
1569 start = floor((n - 2) / 2);
1572 // siftdown(start, count-1);
1574 while(root * 2 + 1 <= n-1)
1576 child = root * 2 + 1;
1578 if(cmp(child, child+1, pass) < 0)
1580 if(cmp(root, child, pass) < 0)
1582 swap(root, child, pass);
1598 // siftdown(0, end);
1600 while(root * 2 + 1 <= end)
1602 child = root * 2 + 1;
1603 if(child < end && cmp(child, child+1, pass) < 0)
1605 if(cmp(root, child, pass) < 0)
1607 swap(root, child, pass);
1617 void RandomSelection_Init()
1619 RandomSelection_totalweight = 0;
1620 RandomSelection_chosen_ent = world;
1621 RandomSelection_chosen_float = 0;
1622 RandomSelection_chosen_string = string_null;
1623 RandomSelection_best_priority = -1;
1625 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1627 if(priority > RandomSelection_best_priority)
1629 RandomSelection_best_priority = priority;
1630 RandomSelection_chosen_ent = e;
1631 RandomSelection_chosen_float = f;
1632 RandomSelection_chosen_string = s;
1633 RandomSelection_totalweight = weight;
1635 else if(priority == RandomSelection_best_priority)
1637 RandomSelection_totalweight += weight;
1638 if(random() * RandomSelection_totalweight <= weight)
1640 RandomSelection_chosen_ent = e;
1641 RandomSelection_chosen_float = f;
1642 RandomSelection_chosen_string = s;
1647 vector healtharmor_maxdamage(float h, float a, float armorblock)
1649 // NOTE: we'll always choose the SMALLER value...
1650 float healthdamage, armordamage, armorideal;
1652 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1653 armordamage = a + (h - 1); // damage we can take if we could use more armor
1654 armorideal = healthdamage * armorblock;
1656 if(armordamage < healthdamage)
1669 vector healtharmor_applydamage(float a, float armorblock, float damage)
1672 v_y = bound(0, damage * armorblock, a); // save
1673 v_x = bound(0, damage - v_y, damage); // take
1678 string getcurrentmod()
1682 m = cvar_string("fs_gamedir");
1683 n = tokenize_console(m);
1695 v = ReadShort() * 256; // note: this is signed
1696 v += ReadByte(); // note: this is unsigned
1700 void WriteInt24_t(float dest, float val)
1703 WriteShort(dest, (v = floor(val / 256)));
1704 WriteByte(dest, val - v * 256); // 0..255
1709 float float2range11(float f)
1711 // continuous function mapping all reals into -1..1
1712 return f / (fabs(f) + 1);
1715 float float2range01(float f)
1717 // continuous function mapping all reals into 0..1
1718 return 0.5 + 0.5 * float2range11(f);
1721 // from the GNU Scientific Library
1722 float gsl_ran_gaussian_lastvalue;
1723 float gsl_ran_gaussian_lastvalue_set;
1724 float gsl_ran_gaussian(float sigma)
1727 if(gsl_ran_gaussian_lastvalue_set)
1729 gsl_ran_gaussian_lastvalue_set = 0;
1730 return sigma * gsl_ran_gaussian_lastvalue;
1734 a = random() * 2 * M_PI;
1735 b = sqrt(-2 * log(random()));
1736 gsl_ran_gaussian_lastvalue = cos(a) * b;
1737 gsl_ran_gaussian_lastvalue_set = 1;
1738 return sigma * sin(a) * b;
1742 string car(string s)
1745 o = strstrofs(s, " ", 0);
1748 return substring(s, 0, o);
1750 string cdr(string s)
1753 o = strstrofs(s, " ", 0);
1756 return substring(s, o + 1, strlen(s) - (o + 1));
1758 float matchacl(string acl, string str)
1765 t = car(acl); acl = cdr(acl);
1767 if(substring(t, 0, 1) == "-")
1770 t = substring(t, 1, strlen(t) - 1);
1772 else if(substring(t, 0, 1) == "+")
1773 t = substring(t, 1, strlen(t) - 1);
1774 if(substring(t, -1, 1) == "*")
1776 t = substring(t, 0, strlen(t) - 1);
1777 s = substring(s, 0, strlen(t));
1789 float startsWith(string haystack, string needle)
1791 return substring(haystack, 0, strlen(needle)) == needle;
1793 float startsWithNocase(string haystack, string needle)
1795 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1798 string get_model_datafilename(string m, float sk, string fil)
1803 m = "models/player/*_";
1805 m = strcat(m, ftos(sk));
1808 return strcat(m, ".", fil);
1811 float get_model_parameters(string m, float sk)
1816 get_model_parameters_modelname = string_null;
1817 get_model_parameters_modelskin = -1;
1818 get_model_parameters_name = string_null;
1819 get_model_parameters_species = -1;
1820 get_model_parameters_sex = string_null;
1821 get_model_parameters_weight = -1;
1822 get_model_parameters_age = -1;
1823 get_model_parameters_desc = string_null;
1829 if(substring(m, -4, -1) != ".txt")
1831 if(substring(m, -6, 1) != "_")
1833 sk = stof(substring(m, -5, 1));
1834 m = substring(m, 0, -7);
1837 fn = get_model_datafilename(m, sk, "txt");
1838 fh = fopen(fn, FILE_READ);
1842 get_model_parameters_modelname = m;
1843 get_model_parameters_modelskin = sk;
1844 while((s = fgets(fh)))
1847 break; // next lines will be description
1851 get_model_parameters_name = s;
1855 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1856 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1857 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1858 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1859 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1860 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1861 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1864 get_model_parameters_sex = s;
1866 get_model_parameters_weight = stof(s);
1868 get_model_parameters_age = stof(s);
1871 while((s = fgets(fh)))
1873 if(get_model_parameters_desc)
1874 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1876 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1884 vector vec2(vector v)
1891 vector NearestPointOnBox(entity box, vector org)
1893 vector m1, m2, nearest;
1895 m1 = box.mins + box.origin;
1896 m2 = box.maxs + box.origin;
1898 nearest_x = bound(m1_x, org_x, m2_x);
1899 nearest_y = bound(m1_y, org_y, m2_y);
1900 nearest_z = bound(m1_z, org_z, m2_z);
1906 float vercmp_recursive(string v1, string v2)
1912 dot1 = strstrofs(v1, ".", 0);
1913 dot2 = strstrofs(v2, ".", 0);
1917 s1 = substring(v1, 0, dot1);
1921 s2 = substring(v2, 0, dot2);
1923 r = stof(s1) - stof(s2);
1927 r = strcasecmp(s1, s2);
1940 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1943 float vercmp(string v1, string v2)
1945 if(strcasecmp(v1, v2) == 0) // early out check
1954 return vercmp_recursive(v1, v2);