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, textLengthUpToWidth_widthFunction_t w)
1265 float ICanHasKallerz;
1267 // detect color codes support in the width function
1268 ICanHasKallerz = (w("^7") == 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) <= 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)) <= 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 string getWrappedLine(float w, textLengthUpToWidth_widthFunction_t tw)
1338 s = getWrappedLine_remaining;
1340 cantake = textLengthUpToWidth(s, w, tw);
1341 if(cantake > 0 && cantake < strlen(s))
1344 while(take > 0 && substring(s, take, 1) != " ")
1348 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1349 if(getWrappedLine_remaining == "")
1350 getWrappedLine_remaining = string_null;
1351 return substring(s, 0, cantake);
1355 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1356 if(getWrappedLine_remaining == "")
1357 getWrappedLine_remaining = string_null;
1358 return substring(s, 0, take);
1363 getWrappedLine_remaining = string_null;
1368 string textShortenToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t tw)
1370 if(tw(theText) <= maxWidth)
1373 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("..."), tw)), "...");
1376 float isGametypeInFilter(float gt, float tp, string pattern)
1378 string subpattern, subpattern2, subpattern3;
1379 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1381 subpattern2 = ",teams,";
1383 subpattern2 = ",noteams,";
1384 if(gt == GAME_RACE || gt == GAME_CTS)
1385 subpattern3 = ",race,";
1387 subpattern3 = string_null;
1389 if(substring(pattern, 0, 1) == "-")
1391 pattern = substring(pattern, 1, strlen(pattern) - 1);
1392 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1394 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1396 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 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((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1411 void shuffle(float n, swapfunc_t swap, entity pass)
1414 for(i = 1; i < n; ++i)
1416 // swap i-th item at a random position from 0 to i
1417 // proof for even distribution:
1420 // item n+1 gets at any position with chance 1/(n+1)
1421 // all others will get their 1/n chance reduced by factor n/(n+1)
1422 // to be on place n+1, their chance will be 1/(n+1)
1423 // 1/n * n/(n+1) = 1/(n+1)
1425 j = floor(random() * (i + 1));
1431 string substring_range(string s, float b, float e)
1433 return substring(s, b, e - b);
1436 string swapwords(string str, float i, float j)
1439 string s1, s2, s3, s4, s5;
1440 float si, ei, sj, ej, s0, en;
1441 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1442 si = argv_start_index(i);
1443 sj = argv_start_index(j);
1444 ei = argv_end_index(i);
1445 ej = argv_end_index(j);
1446 s0 = argv_start_index(0);
1447 en = argv_end_index(n-1);
1448 s1 = substring_range(str, s0, si);
1449 s2 = substring_range(str, si, ei);
1450 s3 = substring_range(str, ei, sj);
1451 s4 = substring_range(str, sj, ej);
1452 s5 = substring_range(str, ej, en);
1453 return strcat(s1, s4, s3, s2, s5);
1456 string _shufflewords_str;
1457 void _shufflewords_swapfunc(float i, float j, entity pass)
1459 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1461 string shufflewords(string str)
1464 _shufflewords_str = str;
1465 n = tokenizebyseparator(str, " ");
1466 shuffle(n, _shufflewords_swapfunc, world);
1467 str = _shufflewords_str;
1468 _shufflewords_str = string_null;
1472 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1488 // actually, every number solves the equation!
1499 if(a > 0) // put the smaller solution first
1501 v_x = ((-b)-D) / (2*a);
1502 v_y = ((-b)+D) / (2*a);
1506 v_x = (-b+D) / (2*a);
1507 v_y = (-b-D) / (2*a);
1513 // complex solutions!
1527 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1528 float _unacceptable_compiler_bug_1_b() { return 1; }
1529 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1530 float _unacceptable_compiler_bug_1_d() { return 1; }
1532 void check_unacceptable_compiler_bugs()
1534 if(cvar("_allow_unacceptable_compiler_bugs"))
1536 tokenize_console("foo bar");
1537 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1538 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.");
1541 float compressShotOrigin(vector v)
1545 y = rint(v_y * 4) + 128;
1546 z = rint(v_z * 4) + 128;
1547 if(x > 255 || x < 0)
1549 print("shot origin ", vtos(v), " x out of bounds\n");
1550 x = bound(0, x, 255);
1552 if(y > 255 || y < 0)
1554 print("shot origin ", vtos(v), " y out of bounds\n");
1555 y = bound(0, y, 255);
1557 if(z > 255 || z < 0)
1559 print("shot origin ", vtos(v), " z out of bounds\n");
1560 z = bound(0, z, 255);
1562 return x * 0x10000 + y * 0x100 + z;
1564 vector decompressShotOrigin(float f)
1567 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1568 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1569 v_z = ((f & 0xFF) - 128) / 4;
1573 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1575 float start, end, root, child;
1578 start = floor((n - 2) / 2);
1581 // siftdown(start, count-1);
1583 while(root * 2 + 1 <= n-1)
1585 child = root * 2 + 1;
1587 if(cmp(child, child+1, pass) < 0)
1589 if(cmp(root, child, pass) < 0)
1591 swap(root, child, pass);
1607 // siftdown(0, end);
1609 while(root * 2 + 1 <= end)
1611 child = root * 2 + 1;
1612 if(child < end && cmp(child, child+1, pass) < 0)
1614 if(cmp(root, child, pass) < 0)
1616 swap(root, child, pass);
1626 void RandomSelection_Init()
1628 RandomSelection_totalweight = 0;
1629 RandomSelection_chosen_ent = world;
1630 RandomSelection_chosen_float = 0;
1631 RandomSelection_chosen_string = string_null;
1632 RandomSelection_best_priority = -1;
1634 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1636 if(priority > RandomSelection_best_priority)
1638 RandomSelection_best_priority = priority;
1639 RandomSelection_chosen_ent = e;
1640 RandomSelection_chosen_float = f;
1641 RandomSelection_chosen_string = s;
1642 RandomSelection_totalweight = weight;
1644 else if(priority == RandomSelection_best_priority)
1646 RandomSelection_totalweight += weight;
1647 if(random() * RandomSelection_totalweight <= weight)
1649 RandomSelection_chosen_ent = e;
1650 RandomSelection_chosen_float = f;
1651 RandomSelection_chosen_string = s;
1656 vector healtharmor_maxdamage(float h, float a, float armorblock)
1658 // NOTE: we'll always choose the SMALLER value...
1659 float healthdamage, armordamage, armorideal;
1661 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1662 armordamage = a + (h - 1); // damage we can take if we could use more armor
1663 armorideal = healthdamage * armorblock;
1665 if(armordamage < healthdamage)
1678 vector healtharmor_applydamage(float a, float armorblock, float damage)
1681 v_y = bound(0, damage * armorblock, a); // save
1682 v_x = bound(0, damage - v_y, damage); // take
1687 string getcurrentmod()
1691 m = cvar_string("fs_gamedir");
1692 n = tokenize_console(m);
1704 v = ReadShort() * 256; // note: this is signed
1705 v += ReadByte(); // note: this is unsigned
1709 void WriteInt24_t(float dest, float val)
1712 WriteShort(dest, (v = floor(val / 256)));
1713 WriteByte(dest, val - v * 256); // 0..255
1718 float float2range11(float f)
1720 // continuous function mapping all reals into -1..1
1721 return f / (fabs(f) + 1);
1724 float float2range01(float f)
1726 // continuous function mapping all reals into 0..1
1727 return 0.5 + 0.5 * float2range11(f);
1730 // from the GNU Scientific Library
1731 float gsl_ran_gaussian_lastvalue;
1732 float gsl_ran_gaussian_lastvalue_set;
1733 float gsl_ran_gaussian(float sigma)
1736 if(gsl_ran_gaussian_lastvalue_set)
1738 gsl_ran_gaussian_lastvalue_set = 0;
1739 return sigma * gsl_ran_gaussian_lastvalue;
1743 a = random() * 2 * M_PI;
1744 b = sqrt(-2 * log(random()));
1745 gsl_ran_gaussian_lastvalue = cos(a) * b;
1746 gsl_ran_gaussian_lastvalue_set = 1;
1747 return sigma * sin(a) * b;
1751 string car(string s)
1754 o = strstrofs(s, " ", 0);
1757 return substring(s, 0, o);
1759 string cdr(string s)
1762 o = strstrofs(s, " ", 0);
1765 return substring(s, o + 1, strlen(s) - (o + 1));
1767 float matchacl(string acl, string str)
1774 t = car(acl); acl = cdr(acl);
1776 if(substring(t, 0, 1) == "-")
1779 t = substring(t, 1, strlen(t) - 1);
1781 else if(substring(t, 0, 1) == "+")
1782 t = substring(t, 1, strlen(t) - 1);
1783 if(substring(t, -1, 1) == "*")
1785 t = substring(t, 0, strlen(t) - 1);
1786 s = substring(s, 0, strlen(t));
1798 float startsWith(string haystack, string needle)
1800 return substring(haystack, 0, strlen(needle)) == needle;
1802 float startsWithNocase(string haystack, string needle)
1804 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;