1 // checkextension wrapper for log
2 float sqrt(float f); // declared later
3 float exp(float f); // declared later
4 float pow(float f, float e); // declared later
5 float checkextension(string s); // declared later
6 float log_synth(float f)
10 return sqrt(-1); // nan? -inf?
12 return sqrt(-1); // ACTUALLY this should rather be -inf, but we cannot create a +inf in QC
27 // two steps are good enough
28 l = ((6-f) * f - 5) / 4.32808512266689022212;
35 if(checkextension("DP_QC_LOG"))
36 return log_builtin(f);
41 string wordwrap_buffer;
43 void wordwrap_buffer_put(string s)
45 wordwrap_buffer = strcat(wordwrap_buffer, s);
48 string wordwrap(string s, float l)
52 wordwrap_cb(s, l, wordwrap_buffer_put);
60 void wordwrap_buffer_sprint(string s)
62 wordwrap_buffer = strcat(wordwrap_buffer, s);
65 sprint(self, wordwrap_buffer);
70 void wordwrap_sprint(string s, float l)
73 wordwrap_cb(s, l, wordwrap_buffer_sprint);
74 if(wordwrap_buffer != "")
75 sprint(self, strcat(wordwrap_buffer, "\n"));
82 string unescape(string in)
87 // but it doesn't seem to be necessary in my tests at least
92 for(i = 0; i < len; ++i)
94 s = substring(in, i, 1);
97 s = substring(in, i+1, 1);
99 str = strcat(str, "\n");
101 str = strcat(str, "\\");
103 str = strcat(str, substring(in, i, 2));
106 str = strcat(str, s);
113 void wordwrap_cb(string s, float l, void(string) callback)
116 local float lleft, i, j, wlen;
120 for (i = 0;i < strlen(s);++i)
122 if (substring(s, i, 2) == "\\n")
128 else if (substring(s, i, 1) == "\n")
133 else if (substring(s, i, 1) == " ")
143 for (j = i+1;j < strlen(s);++j)
144 // ^^ this skips over the first character of a word, which
145 // is ALWAYS part of the word
146 // this is safe since if i+1 == strlen(s), i will become
147 // strlen(s)-1 at the end of this block and the function
148 // will terminate. A space can't be the first character we
149 // read here, and neither can a \n be the start, since these
150 // two cases have been handled above.
152 c = substring(s, j, 1);
159 // we need to keep this tempstring alive even if substring is
160 // called repeatedly, so call strcat even though we're not
170 callback(substring(s, i, wlen));
171 lleft = lleft - wlen;
178 float dist_point_line(vector p, vector l0, vector ldir)
180 ldir = normalize(ldir);
182 // remove the component in line direction
183 p = p - (p * ldir) * ldir;
185 // vlen of the remaining vector
189 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
218 float median(float a, float b, float c)
221 return bound(a, b, c);
222 return bound(c, b, a);
225 // converts a number to a string with the indicated number of decimals
226 // works for up to 10 decimals!
227 string ftos_decimals(float number, float decimals)
233 // if negative, cut off the sign first
235 return strcat("-", ftos_decimals(-number, decimals));
236 // it now is always positive!
239 number = floor(number * pow(10, decimals) + 0.5);
242 result = ftos(number);
243 len = strlen(result);
244 // does it have a decimal point (should not happen)? If there is one, it is always at len-7)
245 // if ftos had messed it up, which should never happen: "34278.000000"
247 if(substring(result, len - 7, 1) == ".")
249 dprint("ftos(integer) has comma? Can't be. Affected result: ", result, "\n");
250 result = substring(result, 0, len - 7);
255 return result; // don't insert a point for zero decimals
256 // is it too short? If yes, insert leading zeroes
259 result = strcat(substring("0000000000", 0, decimals - len + 1), result);
262 // and now... INSERT THE POINT!
263 tmp = substring(result, len - decimals, decimals);
264 result = strcat(substring(result, 0, len - decimals), ".", tmp);
269 vector colormapPaletteColor(float c, float isPants)
273 case 0: return '0.800000 0.800000 0.800000';
274 case 1: return '0.600000 0.400000 0.000000';
275 case 2: return '0.000000 1.000000 0.501961';
276 case 3: return '0.000000 1.000000 0.000000';
277 case 4: return '1.000000 0.000000 0.000000';
278 case 5: return '0.000000 0.658824 1.000000';
279 case 6: return '0.000000 1.000000 1.000000';
280 case 7: return '0.501961 1.000000 0.000000';
281 case 8: return '0.501961 0.000000 1.000000';
282 case 9: return '1.000000 0.000000 1.000000';
283 case 10: return '1.000000 0.000000 0.501961';
284 case 11: return '0.600000 0.600000 0.600000';
285 case 12: return '1.000000 1.000000 0.000000';
286 case 13: return '0.000000 0.313725 1.000000';
287 case 14: return '1.000000 0.501961 0.000000';
291 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
292 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
293 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
296 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
297 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
298 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
299 default: return '0.000 0.000 0.000';
303 // unzone the string, and return it as tempstring. Safe to be called on string_null
304 string fstrunzone(string s)
314 // Databases (hash tables)
315 #define DB_BUCKETS 8192
316 void db_save(float db, string pFilename)
319 fh = fopen(pFilename, FILE_WRITE);
322 print(strcat("^1Can't write DB to ", pFilename));
326 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
327 for(i = 0; i < n; ++i)
328 fputs(fh, strcat(bufstr_get(db, i), "\n"));
337 float db_load(string pFilename)
339 float db, fh, i, j, n;
344 fh = fopen(pFilename, FILE_READ);
347 if(stof(fgets(fh)) == DB_BUCKETS)
350 while((l = fgets(fh)))
353 bufstr_set(db, i, l);
359 // different count of buckets?
360 // need to reorganize the database then (SLOW)
361 while((l = fgets(fh)))
363 n = tokenizebyseparator(l, "\\");
364 for(j = 2; j < n; j += 2)
365 db_put(db, argv(j-1), uri_unescape(argv(j)));
372 void db_dump(float db, string pFilename)
374 float fh, i, j, n, m;
375 fh = fopen(pFilename, FILE_WRITE);
377 error(strcat("Can't dump DB to ", pFilename));
380 for(i = 0; i < n; ++i)
382 m = tokenizebyseparator(bufstr_get(db, i), "\\");
383 for(j = 2; j < m; j += 2)
384 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
389 void db_close(float db)
394 string db_get(float db, string pKey)
397 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
398 return uri_unescape(infoget(bufstr_get(db, h), pKey));
401 void db_put(float db, string pKey, string pValue)
404 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
405 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
412 db = db_load("foo.db");
413 print("LOADED. FILL...\n");
414 for(i = 0; i < DB_BUCKETS; ++i)
415 db_put(db, ftos(random()), "X");
416 print("FILLED. SAVE...\n");
417 db_save(db, "foo.db");
418 print("SAVED. CLOSE...\n");
423 // Multiline text file buffers
424 float buf_load(string pFilename)
431 fh = fopen(pFilename, FILE_READ);
435 while((l = fgets(fh)))
437 bufstr_set(buf, i, l);
444 void buf_save(float buf, string pFilename)
447 fh = fopen(pFilename, FILE_WRITE);
449 error(strcat("Can't write buf to ", pFilename));
450 n = buf_getsize(buf);
451 for(i = 0; i < n; ++i)
452 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
456 string GametypeNameFromType(float g)
458 if (g == GAME_DEATHMATCH) return "dm";
459 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
460 else if (g == GAME_DOMINATION) return "dom";
461 else if (g == GAME_CTF) return "ctf";
462 else if (g == GAME_RUNEMATCH) return "rune";
463 else if (g == GAME_LMS) return "lms";
464 else if (g == GAME_ARENA) return "arena";
465 else if (g == GAME_CA) return "ca";
466 else if (g == GAME_KEYHUNT) return "kh";
467 else if (g == GAME_ONSLAUGHT) return "ons";
468 else if (g == GAME_ASSAULT) return "as";
469 else if (g == GAME_RACE) return "rc";
470 else if (g == GAME_NEXBALL) return "nexball";
471 else if (g == GAME_CTS) return "cts";
475 string mmsss(float tenths)
479 tenths = floor(tenths + 0.5);
480 minutes = floor(tenths / 600);
481 tenths -= minutes * 600;
482 s = ftos(1000 + tenths);
483 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
486 string mmssss(float hundredths)
490 hundredths = floor(hundredths + 0.5);
491 minutes = floor(hundredths / 6000);
492 hundredths -= minutes * 6000;
493 s = ftos(10000 + hundredths);
494 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
497 string ScoreString(float pFlags, float pValue)
502 pValue = floor(pValue + 0.5); // round
504 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
506 else if(pFlags & SFL_RANK)
508 valstr = ftos(pValue);
510 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
511 valstr = strcat(valstr, "th");
512 else if(substring(valstr, l - 1, 1) == "1")
513 valstr = strcat(valstr, "st");
514 else if(substring(valstr, l - 1, 1) == "2")
515 valstr = strcat(valstr, "nd");
516 else if(substring(valstr, l - 1, 1) == "3")
517 valstr = strcat(valstr, "rd");
519 valstr = strcat(valstr, "th");
521 else if(pFlags & SFL_TIME)
522 valstr = TIME_ENCODED_TOSTRING(pValue);
524 valstr = ftos(pValue);
529 vector cross(vector a, vector b)
532 '1 0 0' * (a_y * b_z - a_z * b_y)
533 + '0 1 0' * (a_z * b_x - a_x * b_z)
534 + '0 0 1' * (a_x * b_y - a_y * b_x);
537 // compressed vector format:
538 // like MD3, just even shorter
539 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
540 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
541 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
542 // length = 2^(length_encoded/8) / 8
543 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
544 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
545 // the special value 0 indicates the zero vector
547 float lengthLogTable[128];
549 float invertLengthLog(float x)
551 float l, r, m, lerr, rerr;
553 if(x >= lengthLogTable[127])
555 if(x <= lengthLogTable[0])
563 m = floor((l + r) / 2);
564 if(lengthLogTable[m] < x)
570 // now: r is >=, l is <
571 lerr = (x - lengthLogTable[l]);
572 rerr = (lengthLogTable[r] - x);
578 vector decompressShortVector(float data)
581 float pitch, yaw, len;
584 pitch = (data & 0xF000) / 0x1000;
585 yaw = (data & 0x0F80) / 0x80;
586 len = (data & 0x007F);
588 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
601 yaw = .19634954084936207740 * yaw;
602 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
603 out_x = cos(yaw) * cos(pitch);
604 out_y = sin(yaw) * cos(pitch);
608 //print("decompressed: ", vtos(out), "\n");
610 return out * lengthLogTable[len];
613 float compressShortVector(vector vec)
616 float pitch, yaw, len;
619 //print("compress: ", vtos(vec), "\n");
620 ang = vectoangles(vec);
624 if(ang_x < -90 && ang_x > +90)
625 error("BOGUS vectoangles");
626 //print("angles: ", vtos(ang), "\n");
628 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
637 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
638 len = invertLengthLog(vlen(vec));
640 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
642 return (pitch * 0x1000) + (yaw * 0x80) + len;
645 void compressShortVector_init()
650 for(i = 0; i < 128; ++i)
652 lengthLogTable[i] = l;
656 if(cvar("developer"))
658 print("Verifying vector compression table...\n");
659 for(i = 0x0F00; i < 0xFFFF; ++i)
660 if(i != compressShortVector(decompressShortVector(i)))
662 print("BROKEN vector compression: ", ftos(i));
663 print(" -> ", vtos(decompressShortVector(i)));
664 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
673 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
675 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
676 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
677 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
678 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
679 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
680 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
681 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
682 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
683 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
684 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
685 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
686 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
690 void fixedmakevectors(vector a)
692 // a makevectors that actually inverts vectoangles
698 string fixPriorityList(string order, float from, float to, float subtract, float complete)
703 n = tokenize_console(order);
705 for(i = 0; i < n; ++i)
710 if(w >= from && w <= to)
711 neworder = strcat(neworder, ftos(w), " ");
715 if(w >= from && w <= to)
716 neworder = strcat(neworder, ftos(w), " ");
723 n = tokenize_console(neworder);
724 for(w = to; w >= from; --w)
726 for(i = 0; i < n; ++i)
727 if(stof(argv(i)) == w)
729 if(i == n) // not found
730 neworder = strcat(neworder, ftos(w), " ");
734 return substring(neworder, 0, strlen(neworder) - 1);
737 string mapPriorityList(string order, string(string) mapfunc)
742 n = tokenize_console(order);
744 for(i = 0; i < n; ++i)
745 neworder = strcat(neworder, mapfunc(argv(i)), " ");
747 return substring(neworder, 0, strlen(neworder) - 1);
750 string swapInPriorityList(string order, float i, float j)
755 n = tokenize_console(order);
757 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
760 for(w = 0; w < n; ++w)
763 s = strcat(s, argv(j), " ");
765 s = strcat(s, argv(i), " ");
767 s = strcat(s, argv(w), " ");
769 return substring(s, 0, strlen(s) - 1);
775 float cvar_value_issafe(string s)
777 if(strstrofs(s, "\"", 0) >= 0)
779 if(strstrofs(s, "\\", 0) >= 0)
781 if(strstrofs(s, ";", 0) >= 0)
783 if(strstrofs(s, "$", 0) >= 0)
785 if(strstrofs(s, "\r", 0) >= 0)
787 if(strstrofs(s, "\n", 0) >= 0)
793 void get_mi_min_max(float mode)
798 strunzone(mi_shortname);
799 mi_shortname = mapname;
800 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
801 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
802 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
803 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
804 mi_shortname = strzone(mi_shortname);
816 MapInfo_Get_ByName(mi_shortname, 0, 0);
817 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
819 mi_min = MapInfo_Map_mins;
820 mi_max = MapInfo_Map_maxs;
828 tracebox('1 0 0' * mi_x,
829 '0 1 0' * mi_y + '0 0 1' * mi_z,
830 '0 1 0' * ma_y + '0 0 1' * ma_z,
834 if(!trace_startsolid)
835 mi_min_x = trace_endpos_x;
837 tracebox('0 1 0' * mi_y,
838 '1 0 0' * mi_x + '0 0 1' * mi_z,
839 '1 0 0' * ma_x + '0 0 1' * ma_z,
843 if(!trace_startsolid)
844 mi_min_y = trace_endpos_y;
846 tracebox('0 0 1' * mi_z,
847 '1 0 0' * mi_x + '0 1 0' * mi_y,
848 '1 0 0' * ma_x + '0 1 0' * ma_y,
852 if(!trace_startsolid)
853 mi_min_z = trace_endpos_z;
855 tracebox('1 0 0' * ma_x,
856 '0 1 0' * mi_y + '0 0 1' * mi_z,
857 '0 1 0' * ma_y + '0 0 1' * ma_z,
861 if(!trace_startsolid)
862 mi_max_x = trace_endpos_x;
864 tracebox('0 1 0' * ma_y,
865 '1 0 0' * mi_x + '0 0 1' * mi_z,
866 '1 0 0' * ma_x + '0 0 1' * ma_z,
870 if(!trace_startsolid)
871 mi_max_y = trace_endpos_y;
873 tracebox('0 0 1' * ma_z,
874 '1 0 0' * mi_x + '0 1 0' * mi_y,
875 '1 0 0' * ma_x + '0 1 0' * ma_y,
879 if(!trace_startsolid)
880 mi_max_z = trace_endpos_z;
885 void get_mi_min_max_texcoords(float mode)
889 get_mi_min_max(mode);
894 // extend mi_picmax to get a square aspect ratio
895 // center the map in that area
896 extend = mi_picmax - mi_picmin;
897 if(extend_y > extend_x)
899 mi_picmin_x -= (extend_y - extend_x) * 0.5;
900 mi_picmax_x += (extend_y - extend_x) * 0.5;
904 mi_picmin_y -= (extend_x - extend_y) * 0.5;
905 mi_picmax_y += (extend_x - extend_y) * 0.5;
908 // add another some percent
909 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
913 // calculate the texcoords
914 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
915 // first the two corners of the origin
916 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
917 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
918 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
919 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
920 // then the other corners
921 mi_pictexcoord1_x = mi_pictexcoord0_x;
922 mi_pictexcoord1_y = mi_pictexcoord2_y;
923 mi_pictexcoord3_x = mi_pictexcoord2_x;
924 mi_pictexcoord3_y = mi_pictexcoord0_y;
929 void cvar_settemp(string pKey, string pValue)
931 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
933 void cvar_settemp_restore()
935 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
938 void cvar_settemp(string pKey, string pValue)
942 if(cvar_string(pKey) == pValue)
944 i = cvar("settemp_idx");
945 cvar_set("settemp_idx", ftos(i+1));
946 settemp_var = strcat("_settemp_x", ftos(i));
948 registercvar(settemp_var, "", 0);
950 registercvar(settemp_var, "");
952 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
953 cvar_set(settemp_var, cvar_string(pKey));
954 cvar_set(pKey, pValue);
957 void cvar_settemp_restore()
959 // undo what cvar_settemp did
961 n = tokenize_console(cvar_string("settemp_list"));
962 for(i = 0; i < n - 3; i += 3)
963 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
964 cvar_set("settemp_list", "0");
968 float almost_equals(float a, float b)
971 eps = (max(a, -a) + max(b, -b)) * 0.001;
972 if(a - b < eps && b - a < eps)
977 float almost_in_bounds(float a, float b, float c)
980 eps = (max(a, -a) + max(c, -c)) * 0.001;
981 return b == median(a - eps, b, c + eps);
984 float power2of(float e)
988 float log2of(float x)
990 // NOTE: generated code
1063 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1067 else if(ma == rgb_x)
1070 return (rgb_y - rgb_z) / (ma - mi);
1072 return (rgb_y - rgb_z) / (ma - mi) + 6;
1074 else if(ma == rgb_y)
1075 return (rgb_z - rgb_x) / (ma - mi) + 2;
1076 else // if(ma == rgb_z)
1077 return (rgb_x - rgb_y) / (ma - mi) + 4;
1080 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1084 hue -= 6 * floor(hue / 6);
1086 //else if(ma == rgb_x)
1087 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1091 rgb_y = hue * (ma - mi) + mi;
1094 //else if(ma == rgb_y)
1095 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1098 rgb_x = (2 - hue) * (ma - mi) + mi;
1106 rgb_z = (hue - 2) * (ma - mi) + mi;
1108 //else // if(ma == rgb_z)
1109 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1113 rgb_y = (4 - hue) * (ma - mi) + mi;
1118 rgb_x = (hue - 4) * (ma - mi) + mi;
1122 //else if(ma == rgb_x)
1123 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1124 else // if(hue <= 6)
1128 rgb_z = (6 - hue) * (ma - mi) + mi;
1134 vector rgb_to_hsv(vector rgb)
1139 mi = min3(rgb_x, rgb_y, rgb_z);
1140 ma = max3(rgb_x, rgb_y, rgb_z);
1142 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1153 vector hsv_to_rgb(vector hsv)
1155 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1158 vector rgb_to_hsl(vector rgb)
1163 mi = min3(rgb_x, rgb_y, rgb_z);
1164 ma = max3(rgb_x, rgb_y, rgb_z);
1166 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1168 hsl_z = 0.5 * (mi + ma);
1171 else if(hsl_z <= 0.5)
1172 hsl_y = (ma - mi) / (2*hsl_z);
1173 else // if(hsl_z > 0.5)
1174 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1179 vector hsl_to_rgb(vector hsl)
1181 float mi, ma, maminusmi;
1184 maminusmi = hsl_y * 2 * hsl_z;
1186 maminusmi = hsl_y * (2 - 2 * hsl_z);
1188 // hsl_z = 0.5 * mi + 0.5 * ma
1189 // maminusmi = - mi + ma
1190 mi = hsl_z - 0.5 * maminusmi;
1191 ma = hsl_z + 0.5 * maminusmi;
1193 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1196 string rgb_to_hexcolor(vector rgb)
1201 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1202 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1203 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1207 // requires that m2>m1 in all coordinates, and that m4>m3
1208 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;};
1210 // requires the same, but is a stronger condition
1211 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;};
1214 // angles transforms
1215 // angles in fixedmakevectors/fixedvectoangles space
1216 vector AnglesTransform_Apply(vector transform, vector v)
1218 fixedmakevectors(transform);
1219 return v_forward * v_x
1224 vector AnglesTransform_Multiply(vector t1, vector t2)
1226 vector m_forward, m_up;
1227 fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
1228 m_forward = AnglesTransform_Apply(t1, m_forward); m_up = AnglesTransform_Apply(t1, m_up);
1229 return fixedvectoangles2(m_forward, m_up);
1232 vector AnglesTransform_Invert(vector transform)
1234 vector i_forward, i_up;
1235 fixedmakevectors(transform);
1236 // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
1237 // but these are orthogonal unit vectors!
1238 // so to invert, we can simply fixedvectoangles the TRANSPOSED matrix
1239 // TODO is this always -transform?
1240 i_forward_x = v_forward_x;
1241 i_forward_y = -v_right_x;
1242 i_forward_z = v_up_x;
1243 i_up_x = v_forward_z;
1244 i_up_y = -v_right_z;
1246 return fixedvectoangles2(i_forward, i_up);
1249 vector AnglesTransform_TurnDirection(vector transform)
1251 // turn 180 degrees around v_up
1252 // changes in-direction to out-direction
1253 fixedmakevectors(transform);
1254 return fixedvectoangles2(-1 * v_forward, 1 * v_up);
1257 vector AnglesTransform_Divide(vector to_transform, vector from_transform)
1259 return AnglesTransform_Multiply(to_transform, AnglesTransform_Invert(from_transform));
1263 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1265 float ICanHasKallerz;
1267 // detect color codes support in the width function
1268 ICanHasKallerz = (w("^7", theSize) == 0);
1271 // The following function is SLOW.
1272 // For your safety and for the protection of those around you...
1273 // DO NOT CALL THIS AT HOME.
1274 // No really, don't.
1275 if(w(theText, theSize) <= maxWidth)
1276 return strlen(theText); // yeah!
1278 // binary search for right place to cut string
1280 float left, right, middle; // this always works
1282 right = strlen(theText); // this always fails
1285 middle = floor((left + right) / 2);
1286 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1291 while(left < right - 1);
1295 // NOTE: when color codes are involved, this binary search is,
1296 // mathematically, BROKEN. However, it is obviously guaranteed to
1297 // terminate, as the range still halves each time - but nevertheless, it is
1298 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1299 // range, and "right" is outside).
1301 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1302 // and decrease left on the basis of the chars detected of the truncated tag
1303 // Even if the ^xrgb tag is not complete/correct, left is decreased
1304 // (sometimes too much but with a correct result)
1305 // it fixes also ^[0-9]
1306 while(left >= 1 && substring(theText, left-1, 1) == "^")
1309 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1311 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1313 ch = str2chr(theText, left-1);
1314 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1317 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1319 ch = str2chr(theText, left-2);
1320 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1322 ch = str2chr(theText, left-1);
1323 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1332 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1334 float ICanHasKallerz;
1336 // detect color codes support in the width function
1337 ICanHasKallerz = (w("^7") == 0);
1340 // The following function is SLOW.
1341 // For your safety and for the protection of those around you...
1342 // DO NOT CALL THIS AT HOME.
1343 // No really, don't.
1344 if(w(theText) <= maxWidth)
1345 return strlen(theText); // yeah!
1347 // binary search for right place to cut string
1349 float left, right, middle; // this always works
1351 right = strlen(theText); // this always fails
1354 middle = floor((left + right) / 2);
1355 if(w(substring(theText, 0, middle)) <= maxWidth)
1360 while(left < right - 1);
1364 // NOTE: when color codes are involved, this binary search is,
1365 // mathematically, BROKEN. However, it is obviously guaranteed to
1366 // terminate, as the range still halves each time - but nevertheless, it is
1367 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1368 // range, and "right" is outside).
1370 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1371 // and decrease left on the basis of the chars detected of the truncated tag
1372 // Even if the ^xrgb tag is not complete/correct, left is decreased
1373 // (sometimes too much but with a correct result)
1374 // it fixes also ^[0-9]
1375 while(left >= 1 && substring(theText, left-1, 1) == "^")
1378 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1380 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1382 ch = str2chr(theText, left-1);
1383 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1386 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1388 ch = str2chr(theText, left-2);
1389 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1391 ch = str2chr(theText, left-1);
1392 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1401 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1407 s = getWrappedLine_remaining;
1409 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1410 if(cantake > 0 && cantake < strlen(s))
1413 while(take > 0 && substring(s, take, 1) != " ")
1417 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1418 if(getWrappedLine_remaining == "")
1419 getWrappedLine_remaining = string_null;
1420 return substring(s, 0, cantake);
1424 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1425 if(getWrappedLine_remaining == "")
1426 getWrappedLine_remaining = string_null;
1427 return substring(s, 0, take);
1432 getWrappedLine_remaining = string_null;
1437 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1443 s = getWrappedLine_remaining;
1445 cantake = textLengthUpToLength(s, w, tw);
1446 if(cantake > 0 && cantake < strlen(s))
1449 while(take > 0 && substring(s, take, 1) != " ")
1453 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1454 if(getWrappedLine_remaining == "")
1455 getWrappedLine_remaining = string_null;
1456 return substring(s, 0, cantake);
1460 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1461 if(getWrappedLine_remaining == "")
1462 getWrappedLine_remaining = string_null;
1463 return substring(s, 0, take);
1468 getWrappedLine_remaining = string_null;
1473 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1475 if(tw(theText, theFontSize) <= maxWidth)
1478 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1481 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1483 if(tw(theText) <= maxWidth)
1486 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1489 float isGametypeInFilter(float gt, float tp, string pattern)
1491 string subpattern, subpattern2, subpattern3;
1492 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1494 subpattern2 = ",teams,";
1496 subpattern2 = ",noteams,";
1497 if(gt == GAME_RACE || gt == GAME_CTS)
1498 subpattern3 = ",race,";
1500 subpattern3 = string_null;
1502 if(substring(pattern, 0, 1) == "-")
1504 pattern = substring(pattern, 1, strlen(pattern) - 1);
1505 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1507 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1509 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1514 if(substring(pattern, 0, 1) == "+")
1515 pattern = substring(pattern, 1, strlen(pattern) - 1);
1516 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1517 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1518 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1524 void shuffle(float n, swapfunc_t swap, entity pass)
1527 for(i = 1; i < n; ++i)
1529 // swap i-th item at a random position from 0 to i
1530 // proof for even distribution:
1533 // item n+1 gets at any position with chance 1/(n+1)
1534 // all others will get their 1/n chance reduced by factor n/(n+1)
1535 // to be on place n+1, their chance will be 1/(n+1)
1536 // 1/n * n/(n+1) = 1/(n+1)
1538 j = floor(random() * (i + 1));
1544 string substring_range(string s, float b, float e)
1546 return substring(s, b, e - b);
1549 string swapwords(string str, float i, float j)
1552 string s1, s2, s3, s4, s5;
1553 float si, ei, sj, ej, s0, en;
1554 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1555 si = argv_start_index(i);
1556 sj = argv_start_index(j);
1557 ei = argv_end_index(i);
1558 ej = argv_end_index(j);
1559 s0 = argv_start_index(0);
1560 en = argv_end_index(n-1);
1561 s1 = substring_range(str, s0, si);
1562 s2 = substring_range(str, si, ei);
1563 s3 = substring_range(str, ei, sj);
1564 s4 = substring_range(str, sj, ej);
1565 s5 = substring_range(str, ej, en);
1566 return strcat(s1, s4, s3, s2, s5);
1569 string _shufflewords_str;
1570 void _shufflewords_swapfunc(float i, float j, entity pass)
1572 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1574 string shufflewords(string str)
1577 _shufflewords_str = str;
1578 n = tokenizebyseparator(str, " ");
1579 shuffle(n, _shufflewords_swapfunc, world);
1580 str = _shufflewords_str;
1581 _shufflewords_str = string_null;
1585 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1601 // actually, every number solves the equation!
1612 if(a > 0) // put the smaller solution first
1614 v_x = ((-b)-D) / (2*a);
1615 v_y = ((-b)+D) / (2*a);
1619 v_x = (-b+D) / (2*a);
1620 v_y = (-b-D) / (2*a);
1626 // complex solutions!
1640 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1641 float _unacceptable_compiler_bug_1_b() { return 1; }
1642 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1643 float _unacceptable_compiler_bug_1_d() { return 1; }
1645 void check_unacceptable_compiler_bugs()
1647 if(cvar("_allow_unacceptable_compiler_bugs"))
1649 tokenize_console("foo bar");
1650 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1651 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.");
1654 float compressShotOrigin(vector v)
1658 y = rint(v_y * 4) + 128;
1659 z = rint(v_z * 4) + 128;
1660 if(x > 255 || x < 0)
1662 print("shot origin ", vtos(v), " x out of bounds\n");
1663 x = bound(0, x, 255);
1665 if(y > 255 || y < 0)
1667 print("shot origin ", vtos(v), " y out of bounds\n");
1668 y = bound(0, y, 255);
1670 if(z > 255 || z < 0)
1672 print("shot origin ", vtos(v), " z out of bounds\n");
1673 z = bound(0, z, 255);
1675 return x * 0x10000 + y * 0x100 + z;
1677 vector decompressShotOrigin(float f)
1680 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1681 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1682 v_z = ((f & 0xFF) - 128) / 4;
1686 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1688 float start, end, root, child;
1691 start = floor((n - 2) / 2);
1694 // siftdown(start, count-1);
1696 while(root * 2 + 1 <= n-1)
1698 child = root * 2 + 1;
1700 if(cmp(child, child+1, pass) < 0)
1702 if(cmp(root, child, pass) < 0)
1704 swap(root, child, pass);
1720 // siftdown(0, end);
1722 while(root * 2 + 1 <= end)
1724 child = root * 2 + 1;
1725 if(child < end && cmp(child, child+1, pass) < 0)
1727 if(cmp(root, child, pass) < 0)
1729 swap(root, child, pass);
1739 void RandomSelection_Init()
1741 RandomSelection_totalweight = 0;
1742 RandomSelection_chosen_ent = world;
1743 RandomSelection_chosen_float = 0;
1744 RandomSelection_chosen_string = string_null;
1745 RandomSelection_best_priority = -1;
1747 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1749 if(priority > RandomSelection_best_priority)
1751 RandomSelection_best_priority = priority;
1752 RandomSelection_chosen_ent = e;
1753 RandomSelection_chosen_float = f;
1754 RandomSelection_chosen_string = s;
1755 RandomSelection_totalweight = weight;
1757 else if(priority == RandomSelection_best_priority)
1759 RandomSelection_totalweight += weight;
1760 if(random() * RandomSelection_totalweight <= weight)
1762 RandomSelection_chosen_ent = e;
1763 RandomSelection_chosen_float = f;
1764 RandomSelection_chosen_string = s;
1769 vector healtharmor_maxdamage(float h, float a, float armorblock)
1771 // NOTE: we'll always choose the SMALLER value...
1772 float healthdamage, armordamage, armorideal;
1774 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1775 armordamage = a + (h - 1); // damage we can take if we could use more armor
1776 armorideal = healthdamage * armorblock;
1778 if(armordamage < healthdamage)
1791 vector healtharmor_applydamage(float a, float armorblock, float damage)
1794 v_y = bound(0, damage * armorblock, a); // save
1795 v_x = bound(0, damage - v_y, damage); // take
1800 string getcurrentmod()
1804 m = cvar_string("fs_gamedir");
1805 n = tokenize_console(m);
1817 v = ReadShort() * 256; // note: this is signed
1818 v += ReadByte(); // note: this is unsigned
1822 void WriteInt24_t(float dest, float val)
1825 WriteShort(dest, (v = floor(val / 256)));
1826 WriteByte(dest, val - v * 256); // 0..255
1831 float float2range11(float f)
1833 // continuous function mapping all reals into -1..1
1834 return f / (fabs(f) + 1);
1837 float float2range01(float f)
1839 // continuous function mapping all reals into 0..1
1840 return 0.5 + 0.5 * float2range11(f);
1843 // from the GNU Scientific Library
1844 float gsl_ran_gaussian_lastvalue;
1845 float gsl_ran_gaussian_lastvalue_set;
1846 float gsl_ran_gaussian(float sigma)
1849 if(gsl_ran_gaussian_lastvalue_set)
1851 gsl_ran_gaussian_lastvalue_set = 0;
1852 return sigma * gsl_ran_gaussian_lastvalue;
1856 a = random() * 2 * M_PI;
1857 b = sqrt(-2 * log(random()));
1858 gsl_ran_gaussian_lastvalue = cos(a) * b;
1859 gsl_ran_gaussian_lastvalue_set = 1;
1860 return sigma * sin(a) * b;
1864 string car(string s)
1867 o = strstrofs(s, " ", 0);
1870 return substring(s, 0, o);
1872 string cdr(string s)
1875 o = strstrofs(s, " ", 0);
1878 return substring(s, o + 1, strlen(s) - (o + 1));
1880 float matchacl(string acl, string str)
1887 t = car(acl); acl = cdr(acl);
1889 if(substring(t, 0, 1) == "-")
1892 t = substring(t, 1, strlen(t) - 1);
1894 else if(substring(t, 0, 1) == "+")
1895 t = substring(t, 1, strlen(t) - 1);
1896 if(substring(t, -1, 1) == "*")
1898 t = substring(t, 0, strlen(t) - 1);
1899 s = substring(s, 0, strlen(t));
1911 float startsWith(string haystack, string needle)
1913 return substring(haystack, 0, strlen(needle)) == needle;
1915 float startsWithNocase(string haystack, string needle)
1917 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1921 vector BoxTouchesBrush_mins;
1922 vector BoxTouchesBrush_maxs;
1923 entity BoxTouchesBrush_ent;
1924 entity BoxTouchesBrush_ignore;
1925 float BoxTouchesBrush_Recurse()
1931 tracebox('0 0 0', BoxTouchesBrush_mins, BoxTouchesBrush_maxs, '0 0 0', MOVE_NOMONSTERS, BoxTouchesBrush_ignore);
1933 if (trace_networkentity)
1935 dprint("hit a network ent, cannot continue BoxTouchesBrush\n");
1936 // we cannot continue, as a player blocks us...
1943 if (trace_ent == BoxTouchesBrush_ent)
1950 se.solid = SOLID_NOT;
1951 f = BoxTouchesBrush_Recurse();
1957 float BoxTouchesBrush(vector mi, vector ma, entity e, entity ig)
1961 if not(e.modelindex)
1965 e.solid = SOLID_BSP;
1966 BoxTouchesBrush_mins = mi;
1967 BoxTouchesBrush_maxs = ma;
1968 BoxTouchesBrush_ent = e;
1969 BoxTouchesBrush_ignore = ig;
1970 f = BoxTouchesBrush_Recurse();