1 string wordwrap_buffer;
3 void wordwrap_buffer_put(string s)
5 wordwrap_buffer = strcat(wordwrap_buffer, s);
8 string wordwrap(string s, float l)
12 wordwrap_cb(s, l, wordwrap_buffer_put);
20 void wordwrap_buffer_sprint(string s)
22 wordwrap_buffer = strcat(wordwrap_buffer, s);
25 sprint(self, wordwrap_buffer);
30 void wordwrap_sprint(string s, float l)
33 wordwrap_cb(s, l, wordwrap_buffer_sprint);
34 if(wordwrap_buffer != "")
35 sprint(self, strcat(wordwrap_buffer, "\n"));
42 string unescape(string in)
47 // but it doesn't seem to be necessary in my tests at least
52 for(i = 0; i < len; ++i)
54 s = substring(in, i, 1);
57 s = substring(in, i+1, 1);
59 str = strcat(str, "\n");
61 str = strcat(str, "\\");
63 str = strcat(str, substring(in, i, 2));
73 void wordwrap_cb(string s, float l, void(string) callback)
76 float lleft, i, j, wlen;
80 for (i = 0;i < strlen(s);++i)
82 if (substring(s, i, 2) == "\\n")
88 else if (substring(s, i, 1) == "\n")
93 else if (substring(s, i, 1) == " ")
103 for (j = i+1;j < strlen(s);++j)
104 // ^^ this skips over the first character of a word, which
105 // is ALWAYS part of the word
106 // this is safe since if i+1 == strlen(s), i will become
107 // strlen(s)-1 at the end of this block and the function
108 // will terminate. A space can't be the first character we
109 // read here, and neither can a \n be the start, since these
110 // two cases have been handled above.
112 c = substring(s, j, 1);
119 // we need to keep this tempstring alive even if substring is
120 // called repeatedly, so call strcat even though we're not
130 callback(substring(s, i, wlen));
131 lleft = lleft - wlen;
138 float dist_point_line(vector p, vector l0, vector ldir)
140 ldir = normalize(ldir);
142 // remove the component in line direction
143 p = p - (p * ldir) * ldir;
145 // vlen of the remaining vector
149 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
178 float median(float a, float b, float c)
181 return bound(a, b, c);
182 return bound(c, b, a);
185 // converts a number to a string with the indicated number of decimals
186 // works for up to 10 decimals!
187 string ftos_decimals(float number, float decimals)
189 // we have sprintf...
190 return sprintf("%.*f", decimals, number);
194 vector colormapPaletteColor(float c, float isPants)
198 case 0: return '0.800000 0.800000 0.800000';
199 case 1: return '0.600000 0.400000 0.000000';
200 case 2: return '0.000000 1.000000 0.501961';
201 case 3: return '0.000000 1.000000 0.000000';
202 case 4: return '1.000000 0.000000 0.000000';
203 case 5: return '0.000000 0.658824 1.000000';
204 case 6: return '0.000000 1.000000 1.000000';
205 case 7: return '0.501961 1.000000 0.000000';
206 case 8: return '0.501961 0.000000 1.000000';
207 case 9: return '1.000000 0.000000 1.000000';
208 case 10: return '1.000000 0.000000 0.501961';
209 case 11: return '0.600000 0.600000 0.600000';
210 case 12: return '1.000000 1.000000 0.000000';
211 case 13: return '0.000000 0.313725 1.000000';
212 case 14: return '1.000000 0.501961 0.000000';
216 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
217 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
218 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
221 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
222 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
223 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
224 default: return '0.000 0.000 0.000';
228 // unzone the string, and return it as tempstring. Safe to be called on string_null
229 string fstrunzone(string s)
239 float fexists(string f)
242 fh = fopen(f, FILE_READ);
249 // Databases (hash tables)
250 #define DB_BUCKETS 8192
251 void db_save(float db, string pFilename)
254 fh = fopen(pFilename, FILE_WRITE);
257 print(strcat("^1Can't write DB to ", pFilename));
261 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
262 for(i = 0; i < n; ++i)
263 fputs(fh, strcat(bufstr_get(db, i), "\n"));
272 float db_load(string pFilename)
274 float db, fh, i, j, n;
279 fh = fopen(pFilename, FILE_READ);
283 if(stof(l) == DB_BUCKETS)
286 while((l = fgets(fh)))
289 bufstr_set(db, i, l);
295 // different count of buckets, or a dump?
296 // need to reorganize the database then (SLOW)
298 // note: we also parse the first line (l) in case the DB file is
299 // missing the bucket count
302 n = tokenizebyseparator(l, "\\");
303 for(j = 2; j < n; j += 2)
304 db_put(db, argv(j-1), uri_unescape(argv(j)));
306 while((l = fgets(fh)));
312 void db_dump(float db, string pFilename)
314 float fh, i, j, n, m;
315 fh = fopen(pFilename, FILE_WRITE);
317 error(strcat("Can't dump DB to ", pFilename));
320 for(i = 0; i < n; ++i)
322 m = tokenizebyseparator(bufstr_get(db, i), "\\");
323 for(j = 2; j < m; j += 2)
324 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
329 void db_close(float db)
334 string db_get(float db, string pKey)
337 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
338 return uri_unescape(infoget(bufstr_get(db, h), pKey));
341 void db_put(float db, string pKey, string pValue)
344 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
345 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
352 db = db_load("foo.db");
353 print("LOADED. FILL...\n");
354 for(i = 0; i < DB_BUCKETS; ++i)
355 db_put(db, ftos(random()), "X");
356 print("FILLED. SAVE...\n");
357 db_save(db, "foo.db");
358 print("SAVED. CLOSE...\n");
363 // Multiline text file buffers
364 float buf_load(string pFilename)
371 fh = fopen(pFilename, FILE_READ);
378 while((l = fgets(fh)))
380 bufstr_set(buf, i, l);
387 void buf_save(float buf, string pFilename)
390 fh = fopen(pFilename, FILE_WRITE);
392 error(strcat("Can't write buf to ", pFilename));
393 n = buf_getsize(buf);
394 for(i = 0; i < n; ++i)
395 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
399 string GametypeNameFromType(float g)
401 if (g == GAME_DEATHMATCH) return "dm";
402 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
403 else if (g == GAME_DOMINATION) return "dom";
404 else if (g == GAME_CTF) return "ctf";
405 else if (g == GAME_RUNEMATCH) return "rune";
406 else if (g == GAME_LMS) return "lms";
407 else if (g == GAME_ARENA) return "arena";
408 else if (g == GAME_CA) return "ca";
409 else if (g == GAME_KEYHUNT) return "kh";
410 else if (g == GAME_ONSLAUGHT) return "ons";
411 else if (g == GAME_ASSAULT) return "as";
412 else if (g == GAME_RACE) return "rc";
413 else if (g == GAME_NEXBALL) return "nexball";
414 else if (g == GAME_CTS) return "cts";
415 else if (g == GAME_FREEZETAG) return "freezetag";
416 else if (g == GAME_KEEPAWAY) return "ka";
420 string mmsss(float tenths)
424 tenths = floor(tenths + 0.5);
425 minutes = floor(tenths / 600);
426 tenths -= minutes * 600;
427 s = ftos(1000 + tenths);
428 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
431 string mmssss(float hundredths)
435 hundredths = floor(hundredths + 0.5);
436 minutes = floor(hundredths / 6000);
437 hundredths -= minutes * 6000;
438 s = ftos(10000 + hundredths);
439 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
442 string ScoreString(float pFlags, float pValue)
447 pValue = floor(pValue + 0.5); // round
449 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
451 else if(pFlags & SFL_RANK)
453 valstr = ftos(pValue);
455 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
456 valstr = strcat(valstr, "th");
457 else if(substring(valstr, l - 1, 1) == "1")
458 valstr = strcat(valstr, "st");
459 else if(substring(valstr, l - 1, 1) == "2")
460 valstr = strcat(valstr, "nd");
461 else if(substring(valstr, l - 1, 1) == "3")
462 valstr = strcat(valstr, "rd");
464 valstr = strcat(valstr, "th");
466 else if(pFlags & SFL_TIME)
467 valstr = TIME_ENCODED_TOSTRING(pValue);
469 valstr = ftos(pValue);
474 vector cross(vector a, vector b)
477 '1 0 0' * (a_y * b_z - a_z * b_y)
478 + '0 1 0' * (a_z * b_x - a_x * b_z)
479 + '0 0 1' * (a_x * b_y - a_y * b_x);
482 // compressed vector format:
483 // like MD3, just even shorter
484 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
485 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
486 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
487 // length = 2^(length_encoded/8) / 8
488 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
489 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
490 // the special value 0 indicates the zero vector
492 float lengthLogTable[128];
494 float invertLengthLog(float x)
496 float l, r, m, lerr, rerr;
498 if(x >= lengthLogTable[127])
500 if(x <= lengthLogTable[0])
508 m = floor((l + r) / 2);
509 if(lengthLogTable[m] < x)
515 // now: r is >=, l is <
516 lerr = (x - lengthLogTable[l]);
517 rerr = (lengthLogTable[r] - x);
523 vector decompressShortVector(float data)
526 float pitch, yaw, len;
529 pitch = (data & 0xF000) / 0x1000;
530 yaw = (data & 0x0F80) / 0x80;
531 len = (data & 0x007F);
533 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
546 yaw = .19634954084936207740 * yaw;
547 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
548 out_x = cos(yaw) * cos(pitch);
549 out_y = sin(yaw) * cos(pitch);
553 //print("decompressed: ", vtos(out), "\n");
555 return out * lengthLogTable[len];
558 float compressShortVector(vector vec)
561 float pitch, yaw, len;
564 //print("compress: ", vtos(vec), "\n");
565 ang = vectoangles(vec);
569 if(ang_x < -90 && ang_x > +90)
570 error("BOGUS vectoangles");
571 //print("angles: ", vtos(ang), "\n");
573 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
582 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
583 len = invertLengthLog(vlen(vec));
585 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
587 return (pitch * 0x1000) + (yaw * 0x80) + len;
590 void compressShortVector_init()
595 for(i = 0; i < 128; ++i)
597 lengthLogTable[i] = l;
601 if(cvar("developer"))
603 print("Verifying vector compression table...\n");
604 for(i = 0x0F00; i < 0xFFFF; ++i)
605 if(i != compressShortVector(decompressShortVector(i)))
607 print("BROKEN vector compression: ", ftos(i));
608 print(" -> ", vtos(decompressShortVector(i)));
609 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
618 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
620 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
628 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
629 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
630 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
631 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
636 string fixPriorityList(string order, float from, float to, float subtract, float complete)
641 n = tokenize_console(order);
643 for(i = 0; i < n; ++i)
648 if(w >= from && w <= to)
649 neworder = strcat(neworder, ftos(w), " ");
653 if(w >= from && w <= to)
654 neworder = strcat(neworder, ftos(w), " ");
661 n = tokenize_console(neworder);
662 for(w = to; w >= from; --w)
664 for(i = 0; i < n; ++i)
665 if(stof(argv(i)) == w)
667 if(i == n) // not found
668 neworder = strcat(neworder, ftos(w), " ");
672 return substring(neworder, 0, strlen(neworder) - 1);
675 string mapPriorityList(string order, string(string) mapfunc)
680 n = tokenize_console(order);
682 for(i = 0; i < n; ++i)
683 neworder = strcat(neworder, mapfunc(argv(i)), " ");
685 return substring(neworder, 0, strlen(neworder) - 1);
688 string swapInPriorityList(string order, float i, float j)
693 n = tokenize_console(order);
695 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
698 for(w = 0; w < n; ++w)
701 s = strcat(s, argv(j), " ");
703 s = strcat(s, argv(i), " ");
705 s = strcat(s, argv(w), " ");
707 return substring(s, 0, strlen(s) - 1);
713 float cvar_value_issafe(string s)
715 if(strstrofs(s, "\"", 0) >= 0)
717 if(strstrofs(s, "\\", 0) >= 0)
719 if(strstrofs(s, ";", 0) >= 0)
721 if(strstrofs(s, "$", 0) >= 0)
723 if(strstrofs(s, "\r", 0) >= 0)
725 if(strstrofs(s, "\n", 0) >= 0)
731 void get_mi_min_max(float mode)
736 strunzone(mi_shortname);
737 mi_shortname = mapname;
738 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
739 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
740 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
741 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
742 mi_shortname = strzone(mi_shortname);
754 MapInfo_Get_ByName(mi_shortname, 0, 0);
755 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
757 mi_min = MapInfo_Map_mins;
758 mi_max = MapInfo_Map_maxs;
766 tracebox('1 0 0' * mi_x,
767 '0 1 0' * mi_y + '0 0 1' * mi_z,
768 '0 1 0' * ma_y + '0 0 1' * ma_z,
772 if(!trace_startsolid)
773 mi_min_x = trace_endpos_x;
775 tracebox('0 1 0' * mi_y,
776 '1 0 0' * mi_x + '0 0 1' * mi_z,
777 '1 0 0' * ma_x + '0 0 1' * ma_z,
781 if(!trace_startsolid)
782 mi_min_y = trace_endpos_y;
784 tracebox('0 0 1' * mi_z,
785 '1 0 0' * mi_x + '0 1 0' * mi_y,
786 '1 0 0' * ma_x + '0 1 0' * ma_y,
790 if(!trace_startsolid)
791 mi_min_z = trace_endpos_z;
793 tracebox('1 0 0' * ma_x,
794 '0 1 0' * mi_y + '0 0 1' * mi_z,
795 '0 1 0' * ma_y + '0 0 1' * ma_z,
799 if(!trace_startsolid)
800 mi_max_x = trace_endpos_x;
802 tracebox('0 1 0' * ma_y,
803 '1 0 0' * mi_x + '0 0 1' * mi_z,
804 '1 0 0' * ma_x + '0 0 1' * ma_z,
808 if(!trace_startsolid)
809 mi_max_y = trace_endpos_y;
811 tracebox('0 0 1' * ma_z,
812 '1 0 0' * mi_x + '0 1 0' * mi_y,
813 '1 0 0' * ma_x + '0 1 0' * ma_y,
817 if(!trace_startsolid)
818 mi_max_z = trace_endpos_z;
823 void get_mi_min_max_texcoords(float mode)
827 get_mi_min_max(mode);
832 // extend mi_picmax to get a square aspect ratio
833 // center the map in that area
834 extend = mi_picmax - mi_picmin;
835 if(extend_y > extend_x)
837 mi_picmin_x -= (extend_y - extend_x) * 0.5;
838 mi_picmax_x += (extend_y - extend_x) * 0.5;
842 mi_picmin_y -= (extend_x - extend_y) * 0.5;
843 mi_picmax_y += (extend_x - extend_y) * 0.5;
846 // add another some percent
847 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
851 // calculate the texcoords
852 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
853 // first the two corners of the origin
854 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
855 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
856 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
857 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
858 // then the other corners
859 mi_pictexcoord1_x = mi_pictexcoord0_x;
860 mi_pictexcoord1_y = mi_pictexcoord2_y;
861 mi_pictexcoord3_x = mi_pictexcoord2_x;
862 mi_pictexcoord3_y = mi_pictexcoord0_y;
867 void cvar_settemp(string pKey, string pValue)
869 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
871 void cvar_settemp_restore()
873 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
876 void cvar_settemp(string pKey, string pValue)
880 if(cvar_string(pKey) == pValue)
882 i = cvar("settemp_idx");
883 cvar_set("settemp_idx", ftos(i+1));
884 settemp_var = strcat("_settemp_x", ftos(i));
886 registercvar(settemp_var, "", 0);
888 registercvar(settemp_var, "");
890 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
891 cvar_set(settemp_var, cvar_string(pKey));
892 cvar_set(pKey, pValue);
895 void cvar_settemp_restore()
897 // undo what cvar_settemp did
899 n = tokenize_console(cvar_string("settemp_list"));
900 for(i = 0; i < n - 3; i += 3)
901 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
902 cvar_set("settemp_list", "0");
906 float almost_equals(float a, float b)
909 eps = (max(a, -a) + max(b, -b)) * 0.001;
910 if(a - b < eps && b - a < eps)
915 float almost_in_bounds(float a, float b, float c)
918 eps = (max(a, -a) + max(c, -c)) * 0.001;
919 return b == median(a - eps, b, c + eps);
922 float power2of(float e)
926 float log2of(float x)
928 // NOTE: generated code
1001 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1005 else if(ma == rgb_x)
1008 return (rgb_y - rgb_z) / (ma - mi);
1010 return (rgb_y - rgb_z) / (ma - mi) + 6;
1012 else if(ma == rgb_y)
1013 return (rgb_z - rgb_x) / (ma - mi) + 2;
1014 else // if(ma == rgb_z)
1015 return (rgb_x - rgb_y) / (ma - mi) + 4;
1018 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1022 hue -= 6 * floor(hue / 6);
1024 //else if(ma == rgb_x)
1025 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1029 rgb_y = hue * (ma - mi) + mi;
1032 //else if(ma == rgb_y)
1033 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1036 rgb_x = (2 - hue) * (ma - mi) + mi;
1044 rgb_z = (hue - 2) * (ma - mi) + mi;
1046 //else // if(ma == rgb_z)
1047 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1051 rgb_y = (4 - hue) * (ma - mi) + mi;
1056 rgb_x = (hue - 4) * (ma - mi) + mi;
1060 //else if(ma == rgb_x)
1061 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1062 else // if(hue <= 6)
1066 rgb_z = (6 - hue) * (ma - mi) + mi;
1072 vector rgb_to_hsv(vector rgb)
1077 mi = min3(rgb_x, rgb_y, rgb_z);
1078 ma = max3(rgb_x, rgb_y, rgb_z);
1080 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1091 vector hsv_to_rgb(vector hsv)
1093 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1096 vector rgb_to_hsl(vector rgb)
1101 mi = min3(rgb_x, rgb_y, rgb_z);
1102 ma = max3(rgb_x, rgb_y, rgb_z);
1104 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1106 hsl_z = 0.5 * (mi + ma);
1109 else if(hsl_z <= 0.5)
1110 hsl_y = (ma - mi) / (2*hsl_z);
1111 else // if(hsl_z > 0.5)
1112 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1117 vector hsl_to_rgb(vector hsl)
1119 float mi, ma, maminusmi;
1122 maminusmi = hsl_y * 2 * hsl_z;
1124 maminusmi = hsl_y * (2 - 2 * hsl_z);
1126 // hsl_z = 0.5 * mi + 0.5 * ma
1127 // maminusmi = - mi + ma
1128 mi = hsl_z - 0.5 * maminusmi;
1129 ma = hsl_z + 0.5 * maminusmi;
1131 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1134 string rgb_to_hexcolor(vector rgb)
1139 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1140 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1141 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1145 // requires that m2>m1 in all coordinates, and that m4>m3
1146 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;}
1148 // requires the same, but is a stronger condition
1149 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;}
1154 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1157 // The following function is SLOW.
1158 // For your safety and for the protection of those around you...
1159 // DO NOT CALL THIS AT HOME.
1160 // No really, don't.
1161 if(w(theText, theSize) <= maxWidth)
1162 return strlen(theText); // yeah!
1164 // binary search for right place to cut string
1166 float left, right, middle; // this always works
1168 right = strlen(theText); // this always fails
1171 middle = floor((left + right) / 2);
1172 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1177 while(left < right - 1);
1179 if(w("^7", theSize) == 0) // detect color codes support in the width function
1181 // NOTE: when color codes are involved, this binary search is,
1182 // mathematically, BROKEN. However, it is obviously guaranteed to
1183 // terminate, as the range still halves each time - but nevertheless, it is
1184 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1185 // range, and "right" is outside).
1187 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1188 // and decrease left on the basis of the chars detected of the truncated tag
1189 // Even if the ^xrgb tag is not complete/correct, left is decreased
1190 // (sometimes too much but with a correct result)
1191 // it fixes also ^[0-9]
1192 while(left >= 1 && substring(theText, left-1, 1) == "^")
1195 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1197 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1199 ch = str2chr(theText, left-1);
1200 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1203 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1205 ch = str2chr(theText, left-2);
1206 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1208 ch = str2chr(theText, left-1);
1209 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1218 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
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);
1243 if(w("^7") == 0) // detect color codes support in the width function
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 find_last_color_code(string s)
1284 float start, len, i, carets;
1285 start = strstrofs(s, "^", 0);
1286 if (start == -1) // no caret found
1289 for(i = len; i >= start; --i)
1291 if(substring(s, i, 1) != "^")
1295 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1298 // check if carets aren't all escaped
1299 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1302 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1303 return substring(s, i, 2);
1306 if(substring(s, i+1, 1) == "x")
1307 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1308 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1309 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1310 return substring(s, i, 5);
1312 i -= carets; // this also skips one char before the carets
1318 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1324 s = getWrappedLine_remaining;
1328 getWrappedLine_remaining = string_null;
1329 return s; // the line has no size ANYWAY, nothing would be displayed.
1332 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1333 if(cantake > 0 && cantake < strlen(s))
1336 while(take > 0 && substring(s, take, 1) != " ")
1340 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1341 if(getWrappedLine_remaining == "")
1342 getWrappedLine_remaining = string_null;
1343 else if (tw("^7", theFontSize) == 0)
1344 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1345 return substring(s, 0, cantake);
1349 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1350 if(getWrappedLine_remaining == "")
1351 getWrappedLine_remaining = string_null;
1352 else if (tw("^7", theFontSize) == 0)
1353 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1354 return substring(s, 0, take);
1359 getWrappedLine_remaining = string_null;
1364 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1370 s = getWrappedLine_remaining;
1374 getWrappedLine_remaining = string_null;
1375 return s; // the line has no size ANYWAY, nothing would be displayed.
1378 cantake = textLengthUpToLength(s, w, tw);
1379 if(cantake > 0 && cantake < strlen(s))
1382 while(take > 0 && substring(s, take, 1) != " ")
1386 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1387 if(getWrappedLine_remaining == "")
1388 getWrappedLine_remaining = string_null;
1389 else if (tw("^7") == 0)
1390 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1391 return substring(s, 0, cantake);
1395 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1396 if(getWrappedLine_remaining == "")
1397 getWrappedLine_remaining = string_null;
1398 else if (tw("^7") == 0)
1399 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1400 return substring(s, 0, take);
1405 getWrappedLine_remaining = string_null;
1410 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1412 if(tw(theText, theFontSize) <= maxWidth)
1415 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1418 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1420 if(tw(theText) <= maxWidth)
1423 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1426 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1428 string subpattern, subpattern2, subpattern3, subpattern4;
1429 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1431 subpattern2 = ",teams,";
1433 subpattern2 = ",noteams,";
1435 subpattern3 = ",teamspawns,";
1437 subpattern3 = ",noteamspawns,";
1438 if(gt == GAME_RACE || gt == GAME_CTS)
1439 subpattern4 = ",race,";
1441 subpattern4 = string_null;
1443 if(substring(pattern, 0, 1) == "-")
1445 pattern = substring(pattern, 1, strlen(pattern) - 1);
1446 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1448 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1450 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1452 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1457 if(substring(pattern, 0, 1) == "+")
1458 pattern = substring(pattern, 1, strlen(pattern) - 1);
1459 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1460 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1461 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1462 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1468 void shuffle(float n, swapfunc_t swap, entity pass)
1471 for(i = 1; i < n; ++i)
1473 // swap i-th item at a random position from 0 to i
1474 // proof for even distribution:
1477 // item n+1 gets at any position with chance 1/(n+1)
1478 // all others will get their 1/n chance reduced by factor n/(n+1)
1479 // to be on place n+1, their chance will be 1/(n+1)
1480 // 1/n * n/(n+1) = 1/(n+1)
1482 j = floor(random() * (i + 1));
1488 string substring_range(string s, float b, float e)
1490 return substring(s, b, e - b);
1493 string swapwords(string str, float i, float j)
1496 string s1, s2, s3, s4, s5;
1497 float si, ei, sj, ej, s0, en;
1498 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1499 si = argv_start_index(i);
1500 sj = argv_start_index(j);
1501 ei = argv_end_index(i);
1502 ej = argv_end_index(j);
1503 s0 = argv_start_index(0);
1504 en = argv_end_index(n-1);
1505 s1 = substring_range(str, s0, si);
1506 s2 = substring_range(str, si, ei);
1507 s3 = substring_range(str, ei, sj);
1508 s4 = substring_range(str, sj, ej);
1509 s5 = substring_range(str, ej, en);
1510 return strcat(s1, s4, s3, s2, s5);
1513 string _shufflewords_str;
1514 void _shufflewords_swapfunc(float i, float j, entity pass)
1516 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1518 string shufflewords(string str)
1521 _shufflewords_str = str;
1522 n = tokenizebyseparator(str, " ");
1523 shuffle(n, _shufflewords_swapfunc, world);
1524 str = _shufflewords_str;
1525 _shufflewords_str = string_null;
1529 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1545 // actually, every number solves the equation!
1556 if(a > 0) // put the smaller solution first
1558 v_x = ((-b)-D) / (2*a);
1559 v_y = ((-b)+D) / (2*a);
1563 v_x = (-b+D) / (2*a);
1564 v_y = (-b-D) / (2*a);
1570 // complex solutions!
1583 void check_unacceptable_compiler_bugs()
1585 if(cvar("_allow_unacceptable_compiler_bugs"))
1587 tokenize_console("foo bar");
1588 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1589 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.");
1592 float compressShotOrigin(vector v)
1596 y = rint(v_y * 4) + 128;
1597 z = rint(v_z * 4) + 128;
1598 if(x > 255 || x < 0)
1600 print("shot origin ", vtos(v), " x out of bounds\n");
1601 x = bound(0, x, 255);
1603 if(y > 255 || y < 0)
1605 print("shot origin ", vtos(v), " y out of bounds\n");
1606 y = bound(0, y, 255);
1608 if(z > 255 || z < 0)
1610 print("shot origin ", vtos(v), " z out of bounds\n");
1611 z = bound(0, z, 255);
1613 return x * 0x10000 + y * 0x100 + z;
1615 vector decompressShotOrigin(float f)
1618 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1619 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1620 v_z = ((f & 0xFF) - 128) / 4;
1624 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1626 float start, end, root, child;
1629 start = floor((n - 2) / 2);
1632 // siftdown(start, count-1);
1634 while(root * 2 + 1 <= n-1)
1636 child = root * 2 + 1;
1638 if(cmp(child, child+1, pass) < 0)
1640 if(cmp(root, child, pass) < 0)
1642 swap(root, child, pass);
1658 // siftdown(0, end);
1660 while(root * 2 + 1 <= end)
1662 child = root * 2 + 1;
1663 if(child < end && cmp(child, child+1, pass) < 0)
1665 if(cmp(root, child, pass) < 0)
1667 swap(root, child, pass);
1677 void RandomSelection_Init()
1679 RandomSelection_totalweight = 0;
1680 RandomSelection_chosen_ent = world;
1681 RandomSelection_chosen_float = 0;
1682 RandomSelection_chosen_string = string_null;
1683 RandomSelection_best_priority = -1;
1685 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1687 if(priority > RandomSelection_best_priority)
1689 RandomSelection_best_priority = priority;
1690 RandomSelection_chosen_ent = e;
1691 RandomSelection_chosen_float = f;
1692 RandomSelection_chosen_string = s;
1693 RandomSelection_totalweight = weight;
1695 else if(priority == RandomSelection_best_priority)
1697 RandomSelection_totalweight += weight;
1698 if(random() * RandomSelection_totalweight <= weight)
1700 RandomSelection_chosen_ent = e;
1701 RandomSelection_chosen_float = f;
1702 RandomSelection_chosen_string = s;
1707 vector healtharmor_maxdamage(float h, float a, float armorblock)
1709 // NOTE: we'll always choose the SMALLER value...
1710 float healthdamage, armordamage, armorideal;
1712 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1713 armordamage = a + (h - 1); // damage we can take if we could use more armor
1714 armorideal = healthdamage * armorblock;
1716 if(armordamage < healthdamage)
1729 vector healtharmor_applydamage(float a, float armorblock, float damage)
1732 v_y = bound(0, damage * armorblock, a); // save
1733 v_x = bound(0, damage - v_y, damage); // take
1738 string getcurrentmod()
1742 m = cvar_string("fs_gamedir");
1743 n = tokenize_console(m);
1755 v = ReadShort() * 256; // note: this is signed
1756 v += ReadByte(); // note: this is unsigned
1760 void WriteInt24_t(float dest, float val)
1763 WriteShort(dest, (v = floor(val / 256)));
1764 WriteByte(dest, val - v * 256); // 0..255
1769 float float2range11(float f)
1771 // continuous function mapping all reals into -1..1
1772 return f / (fabs(f) + 1);
1775 float float2range01(float f)
1777 // continuous function mapping all reals into 0..1
1778 return 0.5 + 0.5 * float2range11(f);
1781 // from the GNU Scientific Library
1782 float gsl_ran_gaussian_lastvalue;
1783 float gsl_ran_gaussian_lastvalue_set;
1784 float gsl_ran_gaussian(float sigma)
1787 if(gsl_ran_gaussian_lastvalue_set)
1789 gsl_ran_gaussian_lastvalue_set = 0;
1790 return sigma * gsl_ran_gaussian_lastvalue;
1794 a = random() * 2 * M_PI;
1795 b = sqrt(-2 * log(random()));
1796 gsl_ran_gaussian_lastvalue = cos(a) * b;
1797 gsl_ran_gaussian_lastvalue_set = 1;
1798 return sigma * sin(a) * b;
1802 string car(string s)
1805 o = strstrofs(s, " ", 0);
1808 return substring(s, 0, o);
1810 string cdr(string s)
1813 o = strstrofs(s, " ", 0);
1816 return substring(s, o + 1, strlen(s) - (o + 1));
1818 float matchacl(string acl, string str)
1825 t = car(acl); acl = cdr(acl);
1827 if(substring(t, 0, 1) == "-")
1830 t = substring(t, 1, strlen(t) - 1);
1832 else if(substring(t, 0, 1) == "+")
1833 t = substring(t, 1, strlen(t) - 1);
1834 if(substring(t, -1, 1) == "*")
1836 t = substring(t, 0, strlen(t) - 1);
1837 s = substring(s, 0, strlen(t));
1849 float startsWith(string haystack, string needle)
1851 return substring(haystack, 0, strlen(needle)) == needle;
1853 float startsWithNocase(string haystack, string needle)
1855 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1858 string get_model_datafilename(string m, float sk, string fil)
1863 m = "models/player/*_";
1865 m = strcat(m, ftos(sk));
1868 return strcat(m, ".", fil);
1871 float get_model_parameters(string m, float sk)
1876 get_model_parameters_modelname = string_null;
1877 get_model_parameters_modelskin = -1;
1878 get_model_parameters_name = string_null;
1879 get_model_parameters_species = -1;
1880 get_model_parameters_sex = string_null;
1881 get_model_parameters_weight = -1;
1882 get_model_parameters_age = -1;
1883 get_model_parameters_desc = string_null;
1889 if(substring(m, -4, -1) != ".txt")
1891 if(substring(m, -6, 1) != "_")
1893 sk = stof(substring(m, -5, 1));
1894 m = substring(m, 0, -7);
1897 fn = get_model_datafilename(m, sk, "txt");
1898 fh = fopen(fn, FILE_READ);
1902 fn = get_model_datafilename(m, sk, "txt");
1903 fh = fopen(fn, FILE_READ);
1908 get_model_parameters_modelname = m;
1909 get_model_parameters_modelskin = sk;
1910 while((s = fgets(fh)))
1913 break; // next lines will be description
1917 get_model_parameters_name = s;
1921 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1922 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1923 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1924 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1925 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1926 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1927 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1930 get_model_parameters_sex = s;
1932 get_model_parameters_weight = stof(s);
1934 get_model_parameters_age = stof(s);
1937 while((s = fgets(fh)))
1939 if(get_model_parameters_desc)
1940 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1942 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1950 vector vec2(vector v)
1957 vector NearestPointOnBox(entity box, vector org)
1959 vector m1, m2, nearest;
1961 m1 = box.mins + box.origin;
1962 m2 = box.maxs + box.origin;
1964 nearest_x = bound(m1_x, org_x, m2_x);
1965 nearest_y = bound(m1_y, org_y, m2_y);
1966 nearest_z = bound(m1_z, org_z, m2_z);
1972 float vercmp_recursive(string v1, string v2)
1978 dot1 = strstrofs(v1, ".", 0);
1979 dot2 = strstrofs(v2, ".", 0);
1983 s1 = substring(v1, 0, dot1);
1987 s2 = substring(v2, 0, dot2);
1989 r = stof(s1) - stof(s2);
1993 r = strcasecmp(s1, s2);
2006 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2009 float vercmp(string v1, string v2)
2011 if(strcasecmp(v1, v2) == 0) // early out check
2020 return vercmp_recursive(v1, v2);
2023 float u8_strsize(string s)
2043 // translation helpers
2044 string language_filename(string s)
2049 if(fn == "" || fn == "dump")
2051 fn = strcat(s, ".", fn);
2052 if((fh = fopen(fn, FILE_READ)) >= 0)
2059 string CTX(string s)
2061 float p = strstrofs(s, "^", 0);
2064 return substring(s, p+1, -1);
2067 // x-encoding (encoding as zero length invisible string)
2068 const string XENCODE_2 = "xX";
2069 const string XENCODE_22 = "0123456789abcdefABCDEF";
2070 string xencode(float f)
2073 d = mod(f, 22); f = floor(f / 22);
2074 c = mod(f, 22); f = floor(f / 22);
2075 b = mod(f, 22); f = floor(f / 22);
2076 a = mod(f, 2); // f = floor(f / 2);
2079 substring(XENCODE_2, a, 1),
2080 substring(XENCODE_22, b, 1),
2081 substring(XENCODE_22, c, 1),
2082 substring(XENCODE_22, d, 1)
2085 float xdecode(string s)
2088 if(substring(s, 0, 1) != "^")
2092 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2093 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2094 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2095 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2096 if(a < 0 || b < 0 || c < 0 || d < 0)
2098 return ((a * 22 + b) * 22 + c) * 22 + d;
2101 float lowestbit(float f)