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);
704 for(i = 0; i < n; ++i)
709 if(w >= from && w <= to)
710 neworder = strcat(neworder, ftos(w), " ");
714 if(w >= from && w <= to)
715 neworder = strcat(neworder, ftos(w), " ");
722 n = tokenize_console(neworder);
723 for(w = to; w >= from; --w)
725 for(i = 0; i < n; ++i)
726 if(stof(argv(i)) == w)
728 if(i == n) // not found
729 neworder = strcat(neworder, ftos(w), " ");
733 return substring(neworder, 0, strlen(neworder) - 1);
736 string swapInPriorityList(string order, float i, float j)
741 n = tokenize_console(order);
743 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
746 for(w = 0; w < n; ++w)
749 s = strcat(s, argv(j), " ");
751 s = strcat(s, argv(i), " ");
753 s = strcat(s, argv(w), " ");
755 return substring(s, 0, strlen(s) - 1);
761 float cvar_value_issafe(string s)
763 if(strstrofs(s, "\"", 0) >= 0)
765 if(strstrofs(s, "\\", 0) >= 0)
767 if(strstrofs(s, ";", 0) >= 0)
769 if(strstrofs(s, "$", 0) >= 0)
771 if(strstrofs(s, "\r", 0) >= 0)
773 if(strstrofs(s, "\n", 0) >= 0)
779 void get_mi_min_max(float mode)
784 strunzone(mi_shortname);
785 mi_shortname = mapname;
786 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
787 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
788 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
789 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
790 mi_shortname = strzone(mi_shortname);
802 MapInfo_Get_ByName(mi_shortname, 0, 0);
803 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
805 mi_min = MapInfo_Map_mins;
806 mi_max = MapInfo_Map_maxs;
814 tracebox('1 0 0' * mi_x,
815 '0 1 0' * mi_y + '0 0 1' * mi_z,
816 '0 1 0' * ma_y + '0 0 1' * ma_z,
820 if(!trace_startsolid)
821 mi_min_x = trace_endpos_x;
823 tracebox('0 1 0' * mi_y,
824 '1 0 0' * mi_x + '0 0 1' * mi_z,
825 '1 0 0' * ma_x + '0 0 1' * ma_z,
829 if(!trace_startsolid)
830 mi_min_y = trace_endpos_y;
832 tracebox('0 0 1' * mi_z,
833 '1 0 0' * mi_x + '0 1 0' * mi_y,
834 '1 0 0' * ma_x + '0 1 0' * ma_y,
838 if(!trace_startsolid)
839 mi_min_z = trace_endpos_z;
841 tracebox('1 0 0' * ma_x,
842 '0 1 0' * mi_y + '0 0 1' * mi_z,
843 '0 1 0' * ma_y + '0 0 1' * ma_z,
847 if(!trace_startsolid)
848 mi_max_x = trace_endpos_x;
850 tracebox('0 1 0' * ma_y,
851 '1 0 0' * mi_x + '0 0 1' * mi_z,
852 '1 0 0' * ma_x + '0 0 1' * ma_z,
856 if(!trace_startsolid)
857 mi_max_y = trace_endpos_y;
859 tracebox('0 0 1' * ma_z,
860 '1 0 0' * mi_x + '0 1 0' * mi_y,
861 '1 0 0' * ma_x + '0 1 0' * ma_y,
865 if(!trace_startsolid)
866 mi_max_z = trace_endpos_z;
871 void get_mi_min_max_texcoords(float mode)
875 get_mi_min_max(mode);
880 // extend mi_picmax to get a square aspect ratio
881 // center the map in that area
882 extend = mi_picmax - mi_picmin;
883 if(extend_y > extend_x)
885 mi_picmin_x -= (extend_y - extend_x) * 0.5;
886 mi_picmax_x += (extend_y - extend_x) * 0.5;
890 mi_picmin_y -= (extend_x - extend_y) * 0.5;
891 mi_picmax_y += (extend_x - extend_y) * 0.5;
894 // add another some percent
895 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
899 // calculate the texcoords
900 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
901 // first the two corners of the origin
902 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
903 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
904 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
905 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
906 // then the other corners
907 mi_pictexcoord1_x = mi_pictexcoord0_x;
908 mi_pictexcoord1_y = mi_pictexcoord2_y;
909 mi_pictexcoord3_x = mi_pictexcoord2_x;
910 mi_pictexcoord3_y = mi_pictexcoord0_y;
915 void cvar_settemp(string pKey, string pValue)
917 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
919 void cvar_settemp_restore()
921 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
924 void cvar_settemp(string pKey, string pValue)
928 if(cvar_string(pKey) == pValue)
930 i = cvar("settemp_idx");
931 cvar_set("settemp_idx", ftos(i+1));
932 settemp_var = strcat("_settemp_x", ftos(i));
934 registercvar(settemp_var, "", 0);
936 registercvar(settemp_var, "");
938 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
939 cvar_set(settemp_var, cvar_string(pKey));
940 cvar_set(pKey, pValue);
943 void cvar_settemp_restore()
945 // undo what cvar_settemp did
947 n = tokenize_console(cvar_string("settemp_list"));
948 for(i = 0; i < n - 3; i += 3)
949 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
950 cvar_set("settemp_list", "0");
954 float almost_equals(float a, float b)
957 eps = (max(a, -a) + max(b, -b)) * 0.001;
958 if(a - b < eps && b - a < eps)
963 float almost_in_bounds(float a, float b, float c)
966 eps = (max(a, -a) + max(c, -c)) * 0.001;
967 return b == median(a - eps, b, c + eps);
970 float power2of(float e)
974 float log2of(float x)
976 // NOTE: generated code
1049 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1053 else if(ma == rgb_x)
1056 return (rgb_y - rgb_z) / (ma - mi);
1058 return (rgb_y - rgb_z) / (ma - mi) + 6;
1060 else if(ma == rgb_y)
1061 return (rgb_z - rgb_x) / (ma - mi) + 2;
1062 else // if(ma == rgb_z)
1063 return (rgb_x - rgb_y) / (ma - mi) + 4;
1066 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1070 hue -= 6 * floor(hue / 6);
1072 //else if(ma == rgb_x)
1073 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1077 rgb_y = hue * (ma - mi) + mi;
1080 //else if(ma == rgb_y)
1081 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1084 rgb_x = (2 - hue) * (ma - mi) + mi;
1092 rgb_z = (hue - 2) * (ma - mi) + mi;
1094 //else // if(ma == rgb_z)
1095 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1099 rgb_y = (4 - hue) * (ma - mi) + mi;
1104 rgb_x = (hue - 4) * (ma - mi) + mi;
1108 //else if(ma == rgb_x)
1109 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1110 else // if(hue <= 6)
1114 rgb_z = (6 - hue) * (ma - mi) + mi;
1120 vector rgb_to_hsv(vector rgb)
1125 mi = min3(rgb_x, rgb_y, rgb_z);
1126 ma = max3(rgb_x, rgb_y, rgb_z);
1128 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1139 vector hsv_to_rgb(vector hsv)
1141 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1144 vector rgb_to_hsl(vector rgb)
1149 mi = min3(rgb_x, rgb_y, rgb_z);
1150 ma = max3(rgb_x, rgb_y, rgb_z);
1152 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1154 hsl_z = 0.5 * (mi + ma);
1157 else if(hsl_z <= 0.5)
1158 hsl_y = (ma - mi) / (2*hsl_z);
1159 else // if(hsl_z > 0.5)
1160 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1165 vector hsl_to_rgb(vector hsl)
1167 float mi, ma, maminusmi;
1170 maminusmi = hsl_y * 2 * hsl_z;
1172 maminusmi = hsl_y * (2 - 2 * hsl_z);
1174 // hsl_z = 0.5 * mi + 0.5 * ma
1175 // maminusmi = - mi + ma
1176 mi = hsl_z - 0.5 * maminusmi;
1177 ma = hsl_z + 0.5 * maminusmi;
1179 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1182 string rgb_to_hexcolor(vector rgb)
1187 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1188 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1189 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1193 // requires that m2>m1 in all coordinates, and that m4>m3
1194 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;};
1196 // requires the same, but is a stronger condition
1197 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;};
1200 // angles transforms
1201 // angles in fixedmakevectors/fixedvectoangles space
1202 vector AnglesTransform_Apply(vector transform, vector v)
1204 fixedmakevectors(transform);
1205 return v_forward * v_x
1210 vector AnglesTransform_Multiply(vector t1, vector t2)
1212 vector m_forward, m_up;
1213 fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
1214 m_forward = AnglesTransform_Apply(t1, m_forward); m_up = AnglesTransform_Apply(t1, m_up);
1215 return fixedvectoangles2(m_forward, m_up);
1218 vector AnglesTransform_Invert(vector transform)
1220 vector i_forward, i_up;
1221 fixedmakevectors(transform);
1222 // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
1223 // but these are orthogonal unit vectors!
1224 // so to invert, we can simply fixedvectoangles the TRANSPOSED matrix
1225 // TODO is this always -transform?
1226 i_forward_x = v_forward_x;
1227 i_forward_y = -v_right_x;
1228 i_forward_z = v_up_x;
1229 i_up_x = v_forward_z;
1230 i_up_y = -v_right_z;
1232 return fixedvectoangles2(i_forward, i_up);
1235 vector AnglesTransform_TurnDirection(vector transform)
1237 // turn 180 degrees around v_up
1238 // changes in-direction to out-direction
1239 fixedmakevectors(transform);
1240 return fixedvectoangles2(-1 * v_forward, 1 * v_up);
1243 vector AnglesTransform_Divide(vector to_transform, vector from_transform)
1245 return AnglesTransform_Multiply(to_transform, AnglesTransform_Invert(from_transform));
1249 float textLengthUpToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t w)
1251 float ICanHasKallerz;
1253 // detect color codes support in the width function
1254 ICanHasKallerz = (w("^7") == 0);
1257 // The following function is SLOW.
1258 // For your safety and for the protection of those around you...
1259 // DO NOT CALL THIS AT HOME.
1260 // No really, don't.
1261 if(w(theText) <= maxWidth)
1262 return strlen(theText); // yeah!
1264 // binary search for right place to cut string
1266 float left, right, middle; // this always works
1268 right = strlen(theText); // this always fails
1271 middle = floor((left + right) / 2);
1272 if(w(substring(theText, 0, middle)) <= maxWidth)
1277 while(left < right - 1);
1281 // NOTE: when color codes are involved, this binary search is,
1282 // mathematically, BROKEN. However, it is obviously guaranteed to
1283 // terminate, as the range still halves each time - but nevertheless, it is
1284 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1285 // range, and "right" is outside).
1287 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1288 // and decrease left on the basis of the chars detected of the truncated tag
1289 // Even if the ^xrgb tag is not complete/correct, left is decreased
1290 // (sometimes too much but with a correct result)
1291 // it fixes also ^[0-9]
1292 while(left >= 1 && substring(theText, left-1, 1) == "^")
1295 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1297 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1299 ch = str2chr(theText, left-1);
1300 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1303 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1305 ch = str2chr(theText, left-2);
1306 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1308 ch = str2chr(theText, left-1);
1309 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1318 string getWrappedLine(float w, textLengthUpToWidth_widthFunction_t tw)
1324 s = getWrappedLine_remaining;
1326 cantake = textLengthUpToWidth(s, w, tw);
1327 if(cantake > 0 && cantake < strlen(s))
1330 while(take > 0 && substring(s, take, 1) != " ")
1334 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1335 if(getWrappedLine_remaining == "")
1336 getWrappedLine_remaining = string_null;
1337 return substring(s, 0, cantake);
1341 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1342 if(getWrappedLine_remaining == "")
1343 getWrappedLine_remaining = string_null;
1344 return substring(s, 0, take);
1349 getWrappedLine_remaining = string_null;
1354 string textShortenToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t tw)
1356 if(tw(theText) <= maxWidth)
1359 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("..."), tw)), "...");
1362 float isGametypeInFilter(float gt, float tp, string pattern)
1364 string subpattern, subpattern2, subpattern3;
1365 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1367 subpattern2 = ",teams,";
1369 subpattern2 = ",noteams,";
1370 if(gt == GAME_RACE || gt == GAME_CTS)
1371 subpattern3 = ",race,";
1373 subpattern3 = string_null;
1375 if(substring(pattern, 0, 1) == "-")
1377 pattern = substring(pattern, 1, strlen(pattern) - 1);
1378 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1380 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1382 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1387 if(substring(pattern, 0, 1) == "+")
1388 pattern = substring(pattern, 1, strlen(pattern) - 1);
1389 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1390 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1391 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1397 void shuffle(float n, swapfunc_t swap, entity pass)
1400 for(i = 1; i < n; ++i)
1402 // swap i-th item at a random position from 0 to i
1403 // proof for even distribution:
1406 // item n+1 gets at any position with chance 1/(n+1)
1407 // all others will get their 1/n chance reduced by factor n/(n+1)
1408 // to be on place n+1, their chance will be 1/(n+1)
1409 // 1/n * n/(n+1) = 1/(n+1)
1411 j = floor(random() * (i + 1));
1417 string substring_range(string s, float b, float e)
1419 return substring(s, b, e - b);
1422 string swapwords(string str, float i, float j)
1425 string s1, s2, s3, s4, s5;
1426 float si, ei, sj, ej, s0, en;
1427 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1428 si = argv_start_index(i);
1429 sj = argv_start_index(j);
1430 ei = argv_end_index(i);
1431 ej = argv_end_index(j);
1432 s0 = argv_start_index(0);
1433 en = argv_end_index(n-1);
1434 s1 = substring_range(str, s0, si);
1435 s2 = substring_range(str, si, ei);
1436 s3 = substring_range(str, ei, sj);
1437 s4 = substring_range(str, sj, ej);
1438 s5 = substring_range(str, ej, en);
1439 return strcat(s1, s4, s3, s2, s5);
1442 string _shufflewords_str;
1443 void _shufflewords_swapfunc(float i, float j, entity pass)
1445 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1447 string shufflewords(string str)
1450 _shufflewords_str = str;
1451 n = tokenizebyseparator(str, " ");
1452 shuffle(n, _shufflewords_swapfunc, world);
1453 str = _shufflewords_str;
1454 _shufflewords_str = string_null;
1458 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1474 // actually, every number solves the equation!
1485 if(a > 0) // put the smaller solution first
1487 v_x = ((-b)-D) / (2*a);
1488 v_y = ((-b)+D) / (2*a);
1492 v_x = (-b+D) / (2*a);
1493 v_y = (-b-D) / (2*a);
1499 // complex solutions!
1513 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1514 float _unacceptable_compiler_bug_1_b() { return 1; }
1515 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1516 float _unacceptable_compiler_bug_1_d() { return 1; }
1518 void check_unacceptable_compiler_bugs()
1520 if(cvar("_allow_unacceptable_compiler_bugs"))
1522 tokenize_console("foo bar");
1523 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1524 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.");
1527 float compressShotOrigin(vector v)
1531 y = rint(v_y * 4) + 128;
1532 z = rint(v_z * 4) + 128;
1533 if(x > 255 || x < 0)
1535 print("shot origin ", vtos(v), " x out of bounds\n");
1536 x = bound(0, x, 255);
1538 if(y > 255 || y < 0)
1540 print("shot origin ", vtos(v), " y out of bounds\n");
1541 y = bound(0, y, 255);
1543 if(z > 255 || z < 0)
1545 print("shot origin ", vtos(v), " z out of bounds\n");
1546 z = bound(0, z, 255);
1548 return x * 0x10000 + y * 0x100 + z;
1550 vector decompressShotOrigin(float f)
1553 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1554 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1555 v_z = ((f & 0xFF) - 128) / 4;
1559 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1561 float start, end, root, child;
1564 start = floor((n - 2) / 2);
1567 // siftdown(start, count-1);
1569 while(root * 2 + 1 <= n-1)
1571 child = root * 2 + 1;
1573 if(cmp(child, child+1, pass) < 0)
1575 if(cmp(root, child, pass) < 0)
1577 swap(root, child, pass);
1593 // siftdown(0, end);
1595 while(root * 2 + 1 <= end)
1597 child = root * 2 + 1;
1598 if(child < end && cmp(child, child+1, pass) < 0)
1600 if(cmp(root, child, pass) < 0)
1602 swap(root, child, pass);
1612 void RandomSelection_Init()
1614 RandomSelection_totalweight = 0;
1615 RandomSelection_chosen_ent = world;
1616 RandomSelection_chosen_float = 0;
1617 RandomSelection_chosen_string = string_null;
1618 RandomSelection_best_priority = -1;
1620 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1622 if(priority > RandomSelection_best_priority)
1624 RandomSelection_best_priority = priority;
1625 RandomSelection_chosen_ent = e;
1626 RandomSelection_chosen_float = f;
1627 RandomSelection_chosen_string = s;
1628 RandomSelection_totalweight = weight;
1630 else if(priority == RandomSelection_best_priority)
1632 RandomSelection_totalweight += weight;
1633 if(random() * RandomSelection_totalweight <= weight)
1635 RandomSelection_chosen_ent = e;
1636 RandomSelection_chosen_float = f;
1637 RandomSelection_chosen_string = s;
1642 vector healtharmor_maxdamage(float h, float a, float armorblock)
1644 // NOTE: we'll always choose the SMALLER value...
1645 float healthdamage, armordamage, armorideal;
1647 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1648 armordamage = a + (h - 1); // damage we can take if we could use more armor
1649 armorideal = healthdamage * armorblock;
1651 if(armordamage < healthdamage)
1664 vector healtharmor_applydamage(float a, float armorblock, float damage)
1667 v_y = bound(0, damage * armorblock, a); // save
1668 v_x = bound(0, damage - v_y, damage); // take
1673 string getcurrentmod()
1677 m = cvar_string("fs_gamedir");
1678 n = tokenize_console(m);
1690 v = ReadShort() * 256; // note: this is signed
1691 v += ReadByte(); // note: this is unsigned
1695 void WriteInt24_t(float dest, float val)
1698 WriteShort(dest, (v = floor(val / 256)));
1699 WriteByte(dest, val - v * 256); // 0..255
1704 float float2range11(float f)
1706 // continuous function mapping all reals into -1..1
1707 return f / (fabs(f) + 1);
1710 float float2range01(float f)
1712 // continuous function mapping all reals into 0..1
1713 return 0.5 + 0.5 * float2range11(f);
1716 // from the GNU Scientific Library
1717 float gsl_ran_gaussian_lastvalue;
1718 float gsl_ran_gaussian_lastvalue_set;
1719 float gsl_ran_gaussian(float sigma)
1722 if(gsl_ran_gaussian_lastvalue_set)
1724 gsl_ran_gaussian_lastvalue_set = 0;
1725 return sigma * gsl_ran_gaussian_lastvalue;
1729 a = random() * 2 * M_PI;
1730 b = sqrt(-2 * log(random()));
1731 gsl_ran_gaussian_lastvalue = cos(a) * b;
1732 gsl_ran_gaussian_lastvalue_set = 1;
1733 return sigma * sin(a) * b;
1737 string car(string s)
1740 o = strstrofs(s, " ", 0);
1743 return substring(s, 0, o);
1745 string cdr(string s)
1748 o = strstrofs(s, " ", 0);
1751 return substring(s, o + 1, strlen(s) - (o + 1));
1753 float matchacl(string acl, string str)
1760 t = car(acl); acl = cdr(acl);
1762 if(substring(t, 0, 1) == "-")
1765 t = substring(t, 1, strlen(t) - 1);
1767 else if(substring(t, 0, 1) == "+")
1768 t = substring(t, 1, strlen(t) - 1);
1769 if(substring(t, -1, 1) == "*")
1771 t = substring(t, 0, strlen(t) - 1);
1772 s = substring(s, 0, strlen(t));
1784 float startsWith(string haystack, string needle)
1786 return substring(haystack, 0, strlen(needle)) == needle;
1788 float startsWithNocase(string haystack, string needle)
1790 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;