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);
368 while((l = fgets(fh)))
370 bufstr_set(buf, i, l);
377 void buf_save(float buf, string pFilename)
380 fh = fopen(pFilename, FILE_WRITE);
382 error(strcat("Can't write buf to ", pFilename));
383 n = buf_getsize(buf);
384 for(i = 0; i < n; ++i)
385 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
389 string GametypeNameFromType(float g)
391 if (g == GAME_DEATHMATCH) return "dm";
392 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
393 else if (g == GAME_DOMINATION) return "dom";
394 else if (g == GAME_CTF) return "ctf";
395 else if (g == GAME_RUNEMATCH) return "rune";
396 else if (g == GAME_LMS) return "lms";
397 else if (g == GAME_ARENA) return "arena";
398 else if (g == GAME_CA) return "ca";
399 else if (g == GAME_KEYHUNT) return "kh";
400 else if (g == GAME_ONSLAUGHT) return "ons";
401 else if (g == GAME_ASSAULT) return "as";
402 else if (g == GAME_RACE) return "rc";
403 else if (g == GAME_NEXBALL) return "nexball";
404 else if (g == GAME_CTS) return "cts";
405 else if (g == GAME_FREEZETAG) return "freezetag";
406 else if (g == GAME_KEEPAWAY) return "ka";
410 string mmsss(float tenths)
414 tenths = floor(tenths + 0.5);
415 minutes = floor(tenths / 600);
416 tenths -= minutes * 600;
417 s = ftos(1000 + tenths);
418 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
421 string mmssss(float hundredths)
425 hundredths = floor(hundredths + 0.5);
426 minutes = floor(hundredths / 6000);
427 hundredths -= minutes * 6000;
428 s = ftos(10000 + hundredths);
429 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
432 string ScoreString(float pFlags, float pValue)
437 pValue = floor(pValue + 0.5); // round
439 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
441 else if(pFlags & SFL_RANK)
443 valstr = ftos(pValue);
445 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
446 valstr = strcat(valstr, "th");
447 else if(substring(valstr, l - 1, 1) == "1")
448 valstr = strcat(valstr, "st");
449 else if(substring(valstr, l - 1, 1) == "2")
450 valstr = strcat(valstr, "nd");
451 else if(substring(valstr, l - 1, 1) == "3")
452 valstr = strcat(valstr, "rd");
454 valstr = strcat(valstr, "th");
456 else if(pFlags & SFL_TIME)
457 valstr = TIME_ENCODED_TOSTRING(pValue);
459 valstr = ftos(pValue);
464 vector cross(vector a, vector b)
467 '1 0 0' * (a_y * b_z - a_z * b_y)
468 + '0 1 0' * (a_z * b_x - a_x * b_z)
469 + '0 0 1' * (a_x * b_y - a_y * b_x);
472 // compressed vector format:
473 // like MD3, just even shorter
474 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
475 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
476 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
477 // length = 2^(length_encoded/8) / 8
478 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
479 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
480 // the special value 0 indicates the zero vector
482 float lengthLogTable[128];
484 float invertLengthLog(float x)
486 float l, r, m, lerr, rerr;
488 if(x >= lengthLogTable[127])
490 if(x <= lengthLogTable[0])
498 m = floor((l + r) / 2);
499 if(lengthLogTable[m] < x)
505 // now: r is >=, l is <
506 lerr = (x - lengthLogTable[l]);
507 rerr = (lengthLogTable[r] - x);
513 vector decompressShortVector(float data)
516 float pitch, yaw, len;
519 pitch = (data & 0xF000) / 0x1000;
520 yaw = (data & 0x0F80) / 0x80;
521 len = (data & 0x007F);
523 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
536 yaw = .19634954084936207740 * yaw;
537 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
538 out_x = cos(yaw) * cos(pitch);
539 out_y = sin(yaw) * cos(pitch);
543 //print("decompressed: ", vtos(out), "\n");
545 return out * lengthLogTable[len];
548 float compressShortVector(vector vec)
551 float pitch, yaw, len;
554 //print("compress: ", vtos(vec), "\n");
555 ang = vectoangles(vec);
559 if(ang_x < -90 && ang_x > +90)
560 error("BOGUS vectoangles");
561 //print("angles: ", vtos(ang), "\n");
563 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
572 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
573 len = invertLengthLog(vlen(vec));
575 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
577 return (pitch * 0x1000) + (yaw * 0x80) + len;
580 void compressShortVector_init()
585 for(i = 0; i < 128; ++i)
587 lengthLogTable[i] = l;
591 if(cvar("developer"))
593 print("Verifying vector compression table...\n");
594 for(i = 0x0F00; i < 0xFFFF; ++i)
595 if(i != compressShortVector(decompressShortVector(i)))
597 print("BROKEN vector compression: ", ftos(i));
598 print(" -> ", vtos(decompressShortVector(i)));
599 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
608 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
610 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
611 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
612 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
619 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
620 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
626 string fixPriorityList(string order, float from, float to, float subtract, float complete)
631 n = tokenize_console(order);
633 for(i = 0; i < n; ++i)
638 if(w >= from && w <= to)
639 neworder = strcat(neworder, ftos(w), " ");
643 if(w >= from && w <= to)
644 neworder = strcat(neworder, ftos(w), " ");
651 n = tokenize_console(neworder);
652 for(w = to; w >= from; --w)
654 for(i = 0; i < n; ++i)
655 if(stof(argv(i)) == w)
657 if(i == n) // not found
658 neworder = strcat(neworder, ftos(w), " ");
662 return substring(neworder, 0, strlen(neworder) - 1);
665 string mapPriorityList(string order, string(string) mapfunc)
670 n = tokenize_console(order);
672 for(i = 0; i < n; ++i)
673 neworder = strcat(neworder, mapfunc(argv(i)), " ");
675 return substring(neworder, 0, strlen(neworder) - 1);
678 string swapInPriorityList(string order, float i, float j)
683 n = tokenize_console(order);
685 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
688 for(w = 0; w < n; ++w)
691 s = strcat(s, argv(j), " ");
693 s = strcat(s, argv(i), " ");
695 s = strcat(s, argv(w), " ");
697 return substring(s, 0, strlen(s) - 1);
703 float cvar_value_issafe(string s)
705 if(strstrofs(s, "\"", 0) >= 0)
707 if(strstrofs(s, "\\", 0) >= 0)
709 if(strstrofs(s, ";", 0) >= 0)
711 if(strstrofs(s, "$", 0) >= 0)
713 if(strstrofs(s, "\r", 0) >= 0)
715 if(strstrofs(s, "\n", 0) >= 0)
721 void get_mi_min_max(float mode)
726 strunzone(mi_shortname);
727 mi_shortname = mapname;
728 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
729 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
730 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
731 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
732 mi_shortname = strzone(mi_shortname);
744 MapInfo_Get_ByName(mi_shortname, 0, 0);
745 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
747 mi_min = MapInfo_Map_mins;
748 mi_max = MapInfo_Map_maxs;
756 tracebox('1 0 0' * mi_x,
757 '0 1 0' * mi_y + '0 0 1' * mi_z,
758 '0 1 0' * ma_y + '0 0 1' * ma_z,
762 if(!trace_startsolid)
763 mi_min_x = trace_endpos_x;
765 tracebox('0 1 0' * mi_y,
766 '1 0 0' * mi_x + '0 0 1' * mi_z,
767 '1 0 0' * ma_x + '0 0 1' * ma_z,
771 if(!trace_startsolid)
772 mi_min_y = trace_endpos_y;
774 tracebox('0 0 1' * mi_z,
775 '1 0 0' * mi_x + '0 1 0' * mi_y,
776 '1 0 0' * ma_x + '0 1 0' * ma_y,
780 if(!trace_startsolid)
781 mi_min_z = trace_endpos_z;
783 tracebox('1 0 0' * ma_x,
784 '0 1 0' * mi_y + '0 0 1' * mi_z,
785 '0 1 0' * ma_y + '0 0 1' * ma_z,
789 if(!trace_startsolid)
790 mi_max_x = trace_endpos_x;
792 tracebox('0 1 0' * ma_y,
793 '1 0 0' * mi_x + '0 0 1' * mi_z,
794 '1 0 0' * ma_x + '0 0 1' * ma_z,
798 if(!trace_startsolid)
799 mi_max_y = trace_endpos_y;
801 tracebox('0 0 1' * ma_z,
802 '1 0 0' * mi_x + '0 1 0' * mi_y,
803 '1 0 0' * ma_x + '0 1 0' * ma_y,
807 if(!trace_startsolid)
808 mi_max_z = trace_endpos_z;
813 void get_mi_min_max_texcoords(float mode)
817 get_mi_min_max(mode);
822 // extend mi_picmax to get a square aspect ratio
823 // center the map in that area
824 extend = mi_picmax - mi_picmin;
825 if(extend_y > extend_x)
827 mi_picmin_x -= (extend_y - extend_x) * 0.5;
828 mi_picmax_x += (extend_y - extend_x) * 0.5;
832 mi_picmin_y -= (extend_x - extend_y) * 0.5;
833 mi_picmax_y += (extend_x - extend_y) * 0.5;
836 // add another some percent
837 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
841 // calculate the texcoords
842 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
843 // first the two corners of the origin
844 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
845 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
846 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
847 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
848 // then the other corners
849 mi_pictexcoord1_x = mi_pictexcoord0_x;
850 mi_pictexcoord1_y = mi_pictexcoord2_y;
851 mi_pictexcoord3_x = mi_pictexcoord2_x;
852 mi_pictexcoord3_y = mi_pictexcoord0_y;
857 void cvar_settemp(string pKey, string pValue)
859 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
861 void cvar_settemp_restore()
863 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
866 void cvar_settemp(string pKey, string pValue)
870 if(cvar_string(pKey) == pValue)
872 i = cvar("settemp_idx");
873 cvar_set("settemp_idx", ftos(i+1));
874 settemp_var = strcat("_settemp_x", ftos(i));
876 registercvar(settemp_var, "", 0);
878 registercvar(settemp_var, "");
880 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
881 cvar_set(settemp_var, cvar_string(pKey));
882 cvar_set(pKey, pValue);
885 void cvar_settemp_restore()
887 // undo what cvar_settemp did
889 n = tokenize_console(cvar_string("settemp_list"));
890 for(i = 0; i < n - 3; i += 3)
891 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
892 cvar_set("settemp_list", "0");
896 float almost_equals(float a, float b)
899 eps = (max(a, -a) + max(b, -b)) * 0.001;
900 if(a - b < eps && b - a < eps)
905 float almost_in_bounds(float a, float b, float c)
908 eps = (max(a, -a) + max(c, -c)) * 0.001;
909 return b == median(a - eps, b, c + eps);
912 float power2of(float e)
916 float log2of(float x)
918 // NOTE: generated code
991 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
998 return (rgb_y - rgb_z) / (ma - mi);
1000 return (rgb_y - rgb_z) / (ma - mi) + 6;
1002 else if(ma == rgb_y)
1003 return (rgb_z - rgb_x) / (ma - mi) + 2;
1004 else // if(ma == rgb_z)
1005 return (rgb_x - rgb_y) / (ma - mi) + 4;
1008 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1012 hue -= 6 * floor(hue / 6);
1014 //else if(ma == rgb_x)
1015 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1019 rgb_y = hue * (ma - mi) + mi;
1022 //else if(ma == rgb_y)
1023 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1026 rgb_x = (2 - hue) * (ma - mi) + mi;
1034 rgb_z = (hue - 2) * (ma - mi) + mi;
1036 //else // if(ma == rgb_z)
1037 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1041 rgb_y = (4 - hue) * (ma - mi) + mi;
1046 rgb_x = (hue - 4) * (ma - mi) + mi;
1050 //else if(ma == rgb_x)
1051 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1052 else // if(hue <= 6)
1056 rgb_z = (6 - hue) * (ma - mi) + mi;
1062 vector rgb_to_hsv(vector rgb)
1067 mi = min3(rgb_x, rgb_y, rgb_z);
1068 ma = max3(rgb_x, rgb_y, rgb_z);
1070 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1081 vector hsv_to_rgb(vector hsv)
1083 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1086 vector rgb_to_hsl(vector rgb)
1091 mi = min3(rgb_x, rgb_y, rgb_z);
1092 ma = max3(rgb_x, rgb_y, rgb_z);
1094 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1096 hsl_z = 0.5 * (mi + ma);
1099 else if(hsl_z <= 0.5)
1100 hsl_y = (ma - mi) / (2*hsl_z);
1101 else // if(hsl_z > 0.5)
1102 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1107 vector hsl_to_rgb(vector hsl)
1109 float mi, ma, maminusmi;
1112 maminusmi = hsl_y * 2 * hsl_z;
1114 maminusmi = hsl_y * (2 - 2 * hsl_z);
1116 // hsl_z = 0.5 * mi + 0.5 * ma
1117 // maminusmi = - mi + ma
1118 mi = hsl_z - 0.5 * maminusmi;
1119 ma = hsl_z + 0.5 * maminusmi;
1121 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1124 string rgb_to_hexcolor(vector rgb)
1129 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1130 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1131 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1135 // requires that m2>m1 in all coordinates, and that m4>m3
1136 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;};
1138 // requires the same, but is a stronger condition
1139 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;};
1144 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1146 float ICanHasKallerz;
1148 // detect color codes support in the width function
1149 ICanHasKallerz = (w("^7", theSize) == 0);
1152 // The following function is SLOW.
1153 // For your safety and for the protection of those around you...
1154 // DO NOT CALL THIS AT HOME.
1155 // No really, don't.
1156 if(w(theText, theSize) <= maxWidth)
1157 return strlen(theText); // yeah!
1159 // binary search for right place to cut string
1161 float left, right, middle; // this always works
1163 right = strlen(theText); // this always fails
1166 middle = floor((left + right) / 2);
1167 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1172 while(left < right - 1);
1176 // NOTE: when color codes are involved, this binary search is,
1177 // mathematically, BROKEN. However, it is obviously guaranteed to
1178 // terminate, as the range still halves each time - but nevertheless, it is
1179 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1180 // range, and "right" is outside).
1182 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1183 // and decrease left on the basis of the chars detected of the truncated tag
1184 // Even if the ^xrgb tag is not complete/correct, left is decreased
1185 // (sometimes too much but with a correct result)
1186 // it fixes also ^[0-9]
1187 while(left >= 1 && substring(theText, left-1, 1) == "^")
1190 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1192 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1194 ch = str2chr(theText, left-1);
1195 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1198 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1200 ch = str2chr(theText, left-2);
1201 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1203 ch = str2chr(theText, left-1);
1204 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1213 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1215 float ICanHasKallerz;
1217 // detect color codes support in the width function
1218 ICanHasKallerz = (w("^7") == 0);
1221 // The following function is SLOW.
1222 // For your safety and for the protection of those around you...
1223 // DO NOT CALL THIS AT HOME.
1224 // No really, don't.
1225 if(w(theText) <= maxWidth)
1226 return strlen(theText); // yeah!
1228 // binary search for right place to cut string
1230 float left, right, middle; // this always works
1232 right = strlen(theText); // this always fails
1235 middle = floor((left + right) / 2);
1236 if(w(substring(theText, 0, middle)) <= maxWidth)
1241 while(left < right - 1);
1245 // NOTE: when color codes are involved, this binary search is,
1246 // mathematically, BROKEN. However, it is obviously guaranteed to
1247 // terminate, as the range still halves each time - but nevertheless, it is
1248 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1249 // range, and "right" is outside).
1251 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1252 // and decrease left on the basis of the chars detected of the truncated tag
1253 // Even if the ^xrgb tag is not complete/correct, left is decreased
1254 // (sometimes too much but with a correct result)
1255 // it fixes also ^[0-9]
1256 while(left >= 1 && substring(theText, left-1, 1) == "^")
1259 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1261 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1263 ch = str2chr(theText, left-1);
1264 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1267 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1269 ch = str2chr(theText, left-2);
1270 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1272 ch = str2chr(theText, left-1);
1273 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1282 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1288 s = getWrappedLine_remaining;
1290 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1291 if(cantake > 0 && cantake < strlen(s))
1294 while(take > 0 && substring(s, take, 1) != " ")
1298 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1299 if(getWrappedLine_remaining == "")
1300 getWrappedLine_remaining = string_null;
1301 return substring(s, 0, cantake);
1305 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1306 if(getWrappedLine_remaining == "")
1307 getWrappedLine_remaining = string_null;
1308 return substring(s, 0, take);
1313 getWrappedLine_remaining = string_null;
1318 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1324 s = getWrappedLine_remaining;
1326 cantake = textLengthUpToLength(s, w, tw);
1327 if(cantake > 0 && cantake < strlen(s))
1330 while(take > 0 && substring(s, take, 1) != " ")
1334 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1335 if(getWrappedLine_remaining == "")
1336 getWrappedLine_remaining = string_null;
1337 return substring(s, 0, cantake);
1341 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1342 if(getWrappedLine_remaining == "")
1343 getWrappedLine_remaining = string_null;
1344 return substring(s, 0, take);
1349 getWrappedLine_remaining = string_null;
1354 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1356 if(tw(theText, theFontSize) <= maxWidth)
1359 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1362 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1364 if(tw(theText) <= maxWidth)
1367 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1370 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1372 string subpattern, subpattern2, subpattern3, subpattern4;
1373 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1375 subpattern2 = ",teams,";
1377 subpattern2 = ",noteams,";
1379 subpattern3 = ",teamspawns,";
1381 subpattern3 = ",noteamspawns,";
1382 if(gt == GAME_RACE || gt == GAME_CTS)
1383 subpattern4 = ",race,";
1385 subpattern4 = string_null;
1387 if(substring(pattern, 0, 1) == "-")
1389 pattern = substring(pattern, 1, strlen(pattern) - 1);
1390 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1392 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1394 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1396 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1401 if(substring(pattern, 0, 1) == "+")
1402 pattern = substring(pattern, 1, strlen(pattern) - 1);
1403 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1404 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1405 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1406 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1412 void shuffle(float n, swapfunc_t swap, entity pass)
1415 for(i = 1; i < n; ++i)
1417 // swap i-th item at a random position from 0 to i
1418 // proof for even distribution:
1421 // item n+1 gets at any position with chance 1/(n+1)
1422 // all others will get their 1/n chance reduced by factor n/(n+1)
1423 // to be on place n+1, their chance will be 1/(n+1)
1424 // 1/n * n/(n+1) = 1/(n+1)
1426 j = floor(random() * (i + 1));
1432 string substring_range(string s, float b, float e)
1434 return substring(s, b, e - b);
1437 string swapwords(string str, float i, float j)
1440 string s1, s2, s3, s4, s5;
1441 float si, ei, sj, ej, s0, en;
1442 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1443 si = argv_start_index(i);
1444 sj = argv_start_index(j);
1445 ei = argv_end_index(i);
1446 ej = argv_end_index(j);
1447 s0 = argv_start_index(0);
1448 en = argv_end_index(n-1);
1449 s1 = substring_range(str, s0, si);
1450 s2 = substring_range(str, si, ei);
1451 s3 = substring_range(str, ei, sj);
1452 s4 = substring_range(str, sj, ej);
1453 s5 = substring_range(str, ej, en);
1454 return strcat(s1, s4, s3, s2, s5);
1457 string _shufflewords_str;
1458 void _shufflewords_swapfunc(float i, float j, entity pass)
1460 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1462 string shufflewords(string str)
1465 _shufflewords_str = str;
1466 n = tokenizebyseparator(str, " ");
1467 shuffle(n, _shufflewords_swapfunc, world);
1468 str = _shufflewords_str;
1469 _shufflewords_str = string_null;
1473 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1489 // actually, every number solves the equation!
1500 if(a > 0) // put the smaller solution first
1502 v_x = ((-b)-D) / (2*a);
1503 v_y = ((-b)+D) / (2*a);
1507 v_x = (-b+D) / (2*a);
1508 v_y = (-b-D) / (2*a);
1514 // complex solutions!
1528 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1529 float _unacceptable_compiler_bug_1_b() { return 1; }
1530 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1531 float _unacceptable_compiler_bug_1_d() { return 1; }
1533 void check_unacceptable_compiler_bugs()
1535 if(cvar("_allow_unacceptable_compiler_bugs"))
1537 tokenize_console("foo bar");
1538 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1539 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.");
1542 float compressShotOrigin(vector v)
1546 y = rint(v_y * 4) + 128;
1547 z = rint(v_z * 4) + 128;
1548 if(x > 255 || x < 0)
1550 print("shot origin ", vtos(v), " x out of bounds\n");
1551 x = bound(0, x, 255);
1553 if(y > 255 || y < 0)
1555 print("shot origin ", vtos(v), " y out of bounds\n");
1556 y = bound(0, y, 255);
1558 if(z > 255 || z < 0)
1560 print("shot origin ", vtos(v), " z out of bounds\n");
1561 z = bound(0, z, 255);
1563 return x * 0x10000 + y * 0x100 + z;
1565 vector decompressShotOrigin(float f)
1568 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1569 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1570 v_z = ((f & 0xFF) - 128) / 4;
1574 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1576 float start, end, root, child;
1579 start = floor((n - 2) / 2);
1582 // siftdown(start, count-1);
1584 while(root * 2 + 1 <= n-1)
1586 child = root * 2 + 1;
1588 if(cmp(child, child+1, pass) < 0)
1590 if(cmp(root, child, pass) < 0)
1592 swap(root, child, pass);
1608 // siftdown(0, end);
1610 while(root * 2 + 1 <= end)
1612 child = root * 2 + 1;
1613 if(child < end && cmp(child, child+1, pass) < 0)
1615 if(cmp(root, child, pass) < 0)
1617 swap(root, child, pass);
1627 void RandomSelection_Init()
1629 RandomSelection_totalweight = 0;
1630 RandomSelection_chosen_ent = world;
1631 RandomSelection_chosen_float = 0;
1632 RandomSelection_chosen_string = string_null;
1633 RandomSelection_best_priority = -1;
1635 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1637 if(priority > RandomSelection_best_priority)
1639 RandomSelection_best_priority = priority;
1640 RandomSelection_chosen_ent = e;
1641 RandomSelection_chosen_float = f;
1642 RandomSelection_chosen_string = s;
1643 RandomSelection_totalweight = weight;
1645 else if(priority == RandomSelection_best_priority)
1647 RandomSelection_totalweight += weight;
1648 if(random() * RandomSelection_totalweight <= weight)
1650 RandomSelection_chosen_ent = e;
1651 RandomSelection_chosen_float = f;
1652 RandomSelection_chosen_string = s;
1657 vector healtharmor_maxdamage(float h, float a, float armorblock)
1659 // NOTE: we'll always choose the SMALLER value...
1660 float healthdamage, armordamage, armorideal;
1662 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1663 armordamage = a + (h - 1); // damage we can take if we could use more armor
1664 armorideal = healthdamage * armorblock;
1666 if(armordamage < healthdamage)
1679 vector healtharmor_applydamage(float a, float armorblock, float damage)
1682 v_y = bound(0, damage * armorblock, a); // save
1683 v_x = bound(0, damage - v_y, damage); // take
1688 string getcurrentmod()
1692 m = cvar_string("fs_gamedir");
1693 n = tokenize_console(m);
1705 v = ReadShort() * 256; // note: this is signed
1706 v += ReadByte(); // note: this is unsigned
1710 void WriteInt24_t(float dest, float val)
1713 WriteShort(dest, (v = floor(val / 256)));
1714 WriteByte(dest, val - v * 256); // 0..255
1719 float float2range11(float f)
1721 // continuous function mapping all reals into -1..1
1722 return f / (fabs(f) + 1);
1725 float float2range01(float f)
1727 // continuous function mapping all reals into 0..1
1728 return 0.5 + 0.5 * float2range11(f);
1731 // from the GNU Scientific Library
1732 float gsl_ran_gaussian_lastvalue;
1733 float gsl_ran_gaussian_lastvalue_set;
1734 float gsl_ran_gaussian(float sigma)
1737 if(gsl_ran_gaussian_lastvalue_set)
1739 gsl_ran_gaussian_lastvalue_set = 0;
1740 return sigma * gsl_ran_gaussian_lastvalue;
1744 a = random() * 2 * M_PI;
1745 b = sqrt(-2 * log(random()));
1746 gsl_ran_gaussian_lastvalue = cos(a) * b;
1747 gsl_ran_gaussian_lastvalue_set = 1;
1748 return sigma * sin(a) * b;
1752 string car(string s)
1755 o = strstrofs(s, " ", 0);
1758 return substring(s, 0, o);
1760 string cdr(string s)
1763 o = strstrofs(s, " ", 0);
1766 return substring(s, o + 1, strlen(s) - (o + 1));
1768 float matchacl(string acl, string str)
1775 t = car(acl); acl = cdr(acl);
1777 if(substring(t, 0, 1) == "-")
1780 t = substring(t, 1, strlen(t) - 1);
1782 else if(substring(t, 0, 1) == "+")
1783 t = substring(t, 1, strlen(t) - 1);
1784 if(substring(t, -1, 1) == "*")
1786 t = substring(t, 0, strlen(t) - 1);
1787 s = substring(s, 0, strlen(t));
1799 float startsWith(string haystack, string needle)
1801 return substring(haystack, 0, strlen(needle)) == needle;
1803 float startsWithNocase(string haystack, string needle)
1805 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1808 string get_model_datafilename(string m, float sk, string fil)
1813 m = "models/player/*_";
1815 m = strcat(m, ftos(sk));
1818 return strcat(m, ".", fil);
1821 float get_model_parameters(string m, float sk)
1826 get_model_parameters_modelname = string_null;
1827 get_model_parameters_modelskin = -1;
1828 get_model_parameters_name = string_null;
1829 get_model_parameters_species = -1;
1830 get_model_parameters_sex = string_null;
1831 get_model_parameters_weight = -1;
1832 get_model_parameters_age = -1;
1833 get_model_parameters_desc = string_null;
1839 if(substring(m, -4, -1) != ".txt")
1841 if(substring(m, -6, 1) != "_")
1843 sk = stof(substring(m, -5, 1));
1844 m = substring(m, 0, -7);
1847 fn = get_model_datafilename(m, sk, "txt");
1848 fh = fopen(fn, FILE_READ);
1852 fn = get_model_datafilename(m, sk, "txt");
1853 fh = fopen(fn, FILE_READ);
1858 get_model_parameters_modelname = m;
1859 get_model_parameters_modelskin = sk;
1860 while((s = fgets(fh)))
1863 break; // next lines will be description
1867 get_model_parameters_name = s;
1871 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1872 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1873 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1874 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1875 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1876 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1877 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1880 get_model_parameters_sex = s;
1882 get_model_parameters_weight = stof(s);
1884 get_model_parameters_age = stof(s);
1887 while((s = fgets(fh)))
1889 if(get_model_parameters_desc)
1890 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1892 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1900 vector vec2(vector v)
1907 vector NearestPointOnBox(entity box, vector org)
1909 vector m1, m2, nearest;
1911 m1 = box.mins + box.origin;
1912 m2 = box.maxs + box.origin;
1914 nearest_x = bound(m1_x, org_x, m2_x);
1915 nearest_y = bound(m1_y, org_y, m2_y);
1916 nearest_z = bound(m1_z, org_z, m2_z);
1922 float vercmp_recursive(string v1, string v2)
1928 dot1 = strstrofs(v1, ".", 0);
1929 dot2 = strstrofs(v2, ".", 0);
1933 s1 = substring(v1, 0, dot1);
1937 s2 = substring(v2, 0, dot2);
1939 r = stof(s1) - stof(s2);
1943 r = strcasecmp(s1, s2);
1956 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1959 float vercmp(string v1, string v2)
1961 if(strcasecmp(v1, v2) == 0) // early out check
1970 return vercmp_recursive(v1, v2);
1973 float u8_strsize(string s)
1993 // translation helpers
1994 string language_filename(string s)
1999 if(fn == "" || fn == "dump")
2001 fn = strcat(s, ".", fn);
2002 if((fh = fopen(fn, FILE_READ)) >= 0)
2009 string CTX(string s)
2011 float p = strstrofs(s, "^", 0);
2014 return substring(s, p+1, -1);