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_KEYHUNT) return "kh";
466 else if (g == GAME_ONSLAUGHT) return "ons";
467 else if (g == GAME_ASSAULT) return "as";
468 else if (g == GAME_RACE) return "rc";
469 else if (g == GAME_NEXBALL) return "nexball";
470 else if (g == GAME_CTS) return "cts";
474 string mmsss(float tenths)
478 tenths = floor(tenths + 0.5);
479 minutes = floor(tenths / 600);
480 tenths -= minutes * 600;
481 s = ftos(1000 + tenths);
482 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
485 string mmssss(float hundredths)
489 hundredths = floor(hundredths + 0.5);
490 minutes = floor(hundredths / 6000);
491 hundredths -= minutes * 6000;
492 s = ftos(10000 + hundredths);
493 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
496 string ScoreString(float pFlags, float pValue)
501 pValue = floor(pValue + 0.5); // round
503 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
505 else if(pFlags & SFL_RANK)
507 valstr = ftos(pValue);
509 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
510 valstr = strcat(valstr, "th");
511 else if(substring(valstr, l - 1, 1) == "1")
512 valstr = strcat(valstr, "st");
513 else if(substring(valstr, l - 1, 1) == "2")
514 valstr = strcat(valstr, "nd");
515 else if(substring(valstr, l - 1, 1) == "3")
516 valstr = strcat(valstr, "rd");
518 valstr = strcat(valstr, "th");
520 else if(pFlags & SFL_TIME)
521 valstr = TIME_ENCODED_TOSTRING(pValue);
523 valstr = ftos(pValue);
528 vector cross(vector a, vector b)
531 '1 0 0' * (a_y * b_z - a_z * b_y)
532 + '0 1 0' * (a_z * b_x - a_x * b_z)
533 + '0 0 1' * (a_x * b_y - a_y * b_x);
536 // compressed vector format:
537 // like MD3, just even shorter
538 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
539 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
540 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
541 // length = 2^(length_encoded/8) / 8
542 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
543 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
544 // the special value 0 indicates the zero vector
546 float lengthLogTable[128];
548 float invertLengthLog(float x)
550 float l, r, m, lerr, rerr;
552 if(x >= lengthLogTable[127])
554 if(x <= lengthLogTable[0])
562 m = floor((l + r) / 2);
563 if(lengthLogTable[m] < x)
569 // now: r is >=, l is <
570 lerr = (x - lengthLogTable[l]);
571 rerr = (lengthLogTable[r] - x);
577 vector decompressShortVector(float data)
580 float pitch, yaw, len;
583 pitch = (data & 0xF000) / 0x1000;
584 yaw = (data & 0x0F80) / 0x80;
585 len = (data & 0x007F);
587 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
600 yaw = .19634954084936207740 * yaw;
601 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
602 out_x = cos(yaw) * cos(pitch);
603 out_y = sin(yaw) * cos(pitch);
607 //print("decompressed: ", vtos(out), "\n");
609 return out * lengthLogTable[len];
612 float compressShortVector(vector vec)
615 float pitch, yaw, len;
618 //print("compress: ", vtos(vec), "\n");
619 ang = vectoangles(vec);
623 if(ang_x < -90 && ang_x > +90)
624 error("BOGUS vectoangles");
625 //print("angles: ", vtos(ang), "\n");
627 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
636 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
637 len = invertLengthLog(vlen(vec));
639 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
641 return (pitch * 0x1000) + (yaw * 0x80) + len;
644 void compressShortVector_init()
649 for(i = 0; i < 128; ++i)
651 lengthLogTable[i] = l;
655 if(cvar("developer"))
657 print("Verifying vector compression table...\n");
658 for(i = 0x0F00; i < 0xFFFF; ++i)
659 if(i != compressShortVector(decompressShortVector(i)))
661 print("BROKEN vector compression: ", ftos(i));
662 print(" -> ", vtos(decompressShortVector(i)));
663 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
672 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
674 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
675 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
676 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
677 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
678 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
679 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
680 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
681 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
682 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
683 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
684 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
685 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
689 void fixedmakevectors(vector a)
691 // a makevectors that actually inverts vectoangles
697 string fixPriorityList(string order, float from, float to, float subtract, float complete)
702 n = tokenize_console(order);
703 for(i = 0; i < n; ++i)
708 if(w >= from && w <= to)
709 neworder = strcat(neworder, ftos(w), " ");
713 if(w >= from && w <= to)
714 neworder = strcat(neworder, ftos(w), " ");
721 n = tokenize_console(neworder);
722 for(w = to; w >= from; --w)
724 for(i = 0; i < n; ++i)
725 if(stof(argv(i)) == w)
727 if(i == n) // not found
728 neworder = strcat(neworder, ftos(w), " ");
732 return substring(neworder, 0, strlen(neworder) - 1);
735 string swapInPriorityList(string order, float i, float j)
740 n = tokenize_console(order);
742 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
745 for(w = 0; w < n; ++w)
748 s = strcat(s, argv(j), " ");
750 s = strcat(s, argv(i), " ");
752 s = strcat(s, argv(w), " ");
754 return substring(s, 0, strlen(s) - 1);
760 float cvar_value_issafe(string s)
762 if(strstrofs(s, "\"", 0) >= 0)
764 if(strstrofs(s, "\\", 0) >= 0)
766 if(strstrofs(s, ";", 0) >= 0)
768 if(strstrofs(s, "$", 0) >= 0)
770 if(strstrofs(s, "\r", 0) >= 0)
772 if(strstrofs(s, "\n", 0) >= 0)
778 void get_mi_min_max(float mode)
783 strunzone(mi_shortname);
784 mi_shortname = mapname;
785 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
786 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
787 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
788 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
789 mi_shortname = strzone(mi_shortname);
801 MapInfo_Get_ByName(mi_shortname, 0, 0);
802 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
804 mi_min = MapInfo_Map_mins;
805 mi_max = MapInfo_Map_maxs;
813 tracebox('1 0 0' * mi_x,
814 '0 1 0' * mi_y + '0 0 1' * mi_z,
815 '0 1 0' * ma_y + '0 0 1' * ma_z,
819 if(!trace_startsolid)
820 mi_min_x = trace_endpos_x;
822 tracebox('0 1 0' * mi_y,
823 '1 0 0' * mi_x + '0 0 1' * mi_z,
824 '1 0 0' * ma_x + '0 0 1' * ma_z,
828 if(!trace_startsolid)
829 mi_min_y = trace_endpos_y;
831 tracebox('0 0 1' * mi_z,
832 '1 0 0' * mi_x + '0 1 0' * mi_y,
833 '1 0 0' * ma_x + '0 1 0' * ma_y,
837 if(!trace_startsolid)
838 mi_min_z = trace_endpos_z;
840 tracebox('1 0 0' * ma_x,
841 '0 1 0' * mi_y + '0 0 1' * mi_z,
842 '0 1 0' * ma_y + '0 0 1' * ma_z,
846 if(!trace_startsolid)
847 mi_max_x = trace_endpos_x;
849 tracebox('0 1 0' * ma_y,
850 '1 0 0' * mi_x + '0 0 1' * mi_z,
851 '1 0 0' * ma_x + '0 0 1' * ma_z,
855 if(!trace_startsolid)
856 mi_max_y = trace_endpos_y;
858 tracebox('0 0 1' * ma_z,
859 '1 0 0' * mi_x + '0 1 0' * mi_y,
860 '1 0 0' * ma_x + '0 1 0' * ma_y,
864 if(!trace_startsolid)
865 mi_max_z = trace_endpos_z;
870 void get_mi_min_max_texcoords(float mode)
874 get_mi_min_max(mode);
879 // extend mi_picmax to get a square aspect ratio
880 // center the map in that area
881 extend = mi_picmax - mi_picmin;
882 if(extend_y > extend_x)
884 mi_picmin_x -= (extend_y - extend_x) * 0.5;
885 mi_picmax_x += (extend_y - extend_x) * 0.5;
889 mi_picmin_y -= (extend_x - extend_y) * 0.5;
890 mi_picmax_y += (extend_x - extend_y) * 0.5;
893 // add another some percent
894 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
898 // calculate the texcoords
899 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
900 // first the two corners of the origin
901 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
902 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
903 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
904 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
905 // then the other corners
906 mi_pictexcoord1_x = mi_pictexcoord0_x;
907 mi_pictexcoord1_y = mi_pictexcoord2_y;
908 mi_pictexcoord3_x = mi_pictexcoord2_x;
909 mi_pictexcoord3_y = mi_pictexcoord0_y;
914 void cvar_settemp(string pKey, string pValue)
916 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
918 void cvar_settemp_restore()
920 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
923 void cvar_settemp(string pKey, string pValue)
927 if(cvar_string(pKey) == pValue)
929 i = cvar("settemp_idx");
930 cvar_set("settemp_idx", ftos(i+1));
931 settemp_var = strcat("_settemp_x", ftos(i));
933 registercvar(settemp_var, "", 0);
935 registercvar(settemp_var, "");
937 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
938 cvar_set(settemp_var, cvar_string(pKey));
939 cvar_set(pKey, pValue);
942 void cvar_settemp_restore()
944 // undo what cvar_settemp did
946 n = tokenize_console(cvar_string("settemp_list"));
947 for(i = 0; i < n - 3; i += 3)
948 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
949 cvar_set("settemp_list", "0");
953 float almost_equals(float a, float b)
956 eps = (max(a, -a) + max(b, -b)) * 0.001;
957 if(a - b < eps && b - a < eps)
962 float almost_in_bounds(float a, float b, float c)
965 eps = (max(a, -a) + max(c, -c)) * 0.001;
966 return b == median(a - eps, b, c + eps);
969 float power2of(float e)
973 float log2of(float x)
975 // NOTE: generated code
1048 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1052 else if(ma == rgb_x)
1055 return (rgb_y - rgb_z) / (ma - mi);
1057 return (rgb_y - rgb_z) / (ma - mi) + 6;
1059 else if(ma == rgb_y)
1060 return (rgb_z - rgb_x) / (ma - mi) + 2;
1061 else // if(ma == rgb_z)
1062 return (rgb_x - rgb_y) / (ma - mi) + 4;
1065 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1069 hue -= 6 * floor(hue / 6);
1071 //else if(ma == rgb_x)
1072 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1076 rgb_y = hue * (ma - mi) + mi;
1079 //else if(ma == rgb_y)
1080 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1083 rgb_x = (2 - hue) * (ma - mi) + mi;
1091 rgb_z = (hue - 2) * (ma - mi) + mi;
1093 //else // if(ma == rgb_z)
1094 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1098 rgb_y = (4 - hue) * (ma - mi) + mi;
1103 rgb_x = (hue - 4) * (ma - mi) + mi;
1107 //else if(ma == rgb_x)
1108 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1109 else // if(hue <= 6)
1113 rgb_z = (6 - hue) * (ma - mi) + mi;
1119 vector rgb_to_hsv(vector rgb)
1124 mi = min3(rgb_x, rgb_y, rgb_z);
1125 ma = max3(rgb_x, rgb_y, rgb_z);
1127 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1138 vector hsv_to_rgb(vector hsv)
1140 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1143 vector rgb_to_hsl(vector rgb)
1148 mi = min3(rgb_x, rgb_y, rgb_z);
1149 ma = max3(rgb_x, rgb_y, rgb_z);
1151 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1153 hsl_z = 0.5 * (mi + ma);
1156 else if(hsl_z <= 0.5)
1157 hsl_y = (ma - mi) / (2*hsl_z);
1158 else // if(hsl_z > 0.5)
1159 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1164 vector hsl_to_rgb(vector hsl)
1166 float mi, ma, maminusmi;
1169 maminusmi = hsl_y * 2 * hsl_z;
1171 maminusmi = hsl_y * (2 - 2 * hsl_z);
1173 // hsl_z = 0.5 * mi + 0.5 * ma
1174 // maminusmi = - mi + ma
1175 mi = hsl_z - 0.5 * maminusmi;
1176 ma = hsl_z + 0.5 * maminusmi;
1178 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1181 string rgb_to_hexcolor(vector rgb)
1186 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1187 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1188 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1192 // requires that m2>m1 in all coordinates, and that m4>m3
1193 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;};
1195 // requires the same, but is a stronger condition
1196 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;};
1199 // angles transforms
1200 // angles in fixedmakevectors/fixedvectoangles space
1201 vector AnglesTransform_Apply(vector transform, vector v)
1203 fixedmakevectors(transform);
1204 return v_forward * v_x
1209 vector AnglesTransform_Multiply(vector t1, vector t2)
1211 vector m_forward, m_up;
1212 fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
1213 m_forward = AnglesTransform_Apply(t1, m_forward); m_up = AnglesTransform_Apply(t1, m_up);
1214 return fixedvectoangles2(m_forward, m_up);
1217 vector AnglesTransform_Invert(vector transform)
1219 vector i_forward, i_up;
1220 fixedmakevectors(transform);
1221 // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
1222 // but these are orthogonal unit vectors!
1223 // so to invert, we can simply fixedvectoangles the TRANSPOSED matrix
1224 // TODO is this always -transform?
1225 i_forward_x = v_forward_x;
1226 i_forward_y = -v_right_x;
1227 i_forward_z = v_up_x;
1228 i_up_x = v_forward_z;
1229 i_up_y = -v_right_z;
1231 return fixedvectoangles2(i_forward, i_up);
1234 vector AnglesTransform_TurnDirection(vector transform)
1236 // turn 180 degrees around v_up
1237 // changes in-direction to out-direction
1238 fixedmakevectors(transform);
1239 return fixedvectoangles2(-1 * v_forward, 1 * v_up);
1242 vector AnglesTransform_Divide(vector to_transform, vector from_transform)
1244 return AnglesTransform_Multiply(to_transform, AnglesTransform_Invert(from_transform));
1248 float textLengthUpToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t w)
1250 float ICanHasKallerz;
1252 // detect color codes support in the width function
1253 ICanHasKallerz = (w("^7") == 0);
1256 // The following function is SLOW.
1257 // For your safety and for the protection of those around you...
1258 // DO NOT CALL THIS AT HOME.
1259 // No really, don't.
1260 if(w(theText) <= maxWidth)
1261 return strlen(theText); // yeah!
1263 // binary search for right place to cut string
1265 float left, right, middle; // this always works
1267 right = strlen(theText); // this always fails
1270 middle = floor((left + right) / 2);
1271 if(w(substring(theText, 0, middle)) <= maxWidth)
1276 while(left < right - 1);
1280 // NOTE: when color codes are involved, this binary search is,
1281 // mathematically, BROKEN. However, it is obviously guaranteed to
1282 // terminate, as the range still halves each time - but nevertheless, it is
1283 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1284 // range, and "right" is outside).
1286 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1287 // and decrease left on the basis of the chars detected of the truncated tag
1288 // Even if the ^xrgb tag is not complete/correct, left is decreased
1289 // (sometimes too much but with a correct result)
1290 // it fixes also ^[0-9]
1291 while(left >= 1 && substring(theText, left-1, 1) == "^")
1294 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1296 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1298 ch = str2chr(theText, left-1);
1299 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1302 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1304 ch = str2chr(theText, left-2);
1305 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1307 ch = str2chr(theText, left-1);
1308 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1317 string getWrappedLine(float w, textLengthUpToWidth_widthFunction_t tw)
1323 s = getWrappedLine_remaining;
1325 cantake = textLengthUpToWidth(s, w, tw);
1326 if(cantake > 0 && cantake < strlen(s))
1329 while(take > 0 && substring(s, take, 1) != " ")
1333 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1334 if(getWrappedLine_remaining == "")
1335 getWrappedLine_remaining = string_null;
1336 return substring(s, 0, cantake);
1340 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1341 if(getWrappedLine_remaining == "")
1342 getWrappedLine_remaining = string_null;
1343 return substring(s, 0, take);
1348 getWrappedLine_remaining = string_null;
1353 string textShortenToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t tw)
1355 if(tw(theText) <= maxWidth)
1358 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("..."), tw)), "...");
1361 float isGametypeInFilter(float gt, float tp, string pattern)
1363 string subpattern, subpattern2, subpattern3;
1364 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1366 subpattern2 = ",teams,";
1368 subpattern2 = ",noteams,";
1369 if(gt == GAME_RACE || gt == GAME_CTS)
1370 subpattern3 = ",race,";
1372 subpattern3 = string_null;
1374 if(substring(pattern, 0, 1) == "-")
1376 pattern = substring(pattern, 1, strlen(pattern) - 1);
1377 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1379 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1381 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1386 if(substring(pattern, 0, 1) == "+")
1387 pattern = substring(pattern, 1, strlen(pattern) - 1);
1388 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1389 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1390 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1396 void shuffle(float n, swapfunc_t swap, entity pass)
1399 for(i = 1; i < n; ++i)
1401 // swap i-th item at a random position from 0 to i
1402 // proof for even distribution:
1405 // item n+1 gets at any position with chance 1/(n+1)
1406 // all others will get their 1/n chance reduced by factor n/(n+1)
1407 // to be on place n+1, their chance will be 1/(n+1)
1408 // 1/n * n/(n+1) = 1/(n+1)
1410 j = floor(random() * (i + 1));
1416 string substring_range(string s, float b, float e)
1418 return substring(s, b, e - b);
1421 string swapwords(string str, float i, float j)
1424 string s1, s2, s3, s4, s5;
1425 float si, ei, sj, ej, s0, en;
1426 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1427 si = argv_start_index(i);
1428 sj = argv_start_index(j);
1429 ei = argv_end_index(i);
1430 ej = argv_end_index(j);
1431 s0 = argv_start_index(0);
1432 en = argv_end_index(n-1);
1433 s1 = substring_range(str, s0, si);
1434 s2 = substring_range(str, si, ei);
1435 s3 = substring_range(str, ei, sj);
1436 s4 = substring_range(str, sj, ej);
1437 s5 = substring_range(str, ej, en);
1438 return strcat(s1, s4, s3, s2, s5);
1441 string _shufflewords_str;
1442 void _shufflewords_swapfunc(float i, float j, entity pass)
1444 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1446 string shufflewords(string str)
1449 _shufflewords_str = str;
1450 n = tokenizebyseparator(str, " ");
1451 shuffle(n, _shufflewords_swapfunc, world);
1452 str = _shufflewords_str;
1453 _shufflewords_str = string_null;
1457 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1473 // actually, every number solves the equation!
1484 if(a > 0) // put the smaller solution first
1486 v_x = ((-b)-D) / (2*a);
1487 v_y = ((-b)+D) / (2*a);
1491 v_x = (-b+D) / (2*a);
1492 v_y = (-b-D) / (2*a);
1498 // complex solutions!
1512 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1513 float _unacceptable_compiler_bug_1_b() { return 1; }
1514 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1515 float _unacceptable_compiler_bug_1_d() { return 1; }
1517 void check_unacceptable_compiler_bugs()
1519 if(cvar("_allow_unacceptable_compiler_bugs"))
1521 tokenize_console("foo bar");
1522 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1523 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.");
1526 float compressShotOrigin(vector v)
1530 y = rint(v_y * 4) + 128;
1531 z = rint(v_z * 4) + 128;
1532 if(x > 255 || x < 0)
1533 error("shot origin x out of bounds");
1534 if(y > 255 || y < 0)
1535 error("shot origin y out of bounds");
1536 if(z > 255 || z < 0)
1537 error("shot origin z out of bounds");
1538 return x * 0x10000 + y * 0x100 + z;
1540 vector decompressShotOrigin(float f)
1543 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1544 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1545 v_z = ((f & 0xFF) - 128) / 4;
1549 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1551 float start, end, root, child;
1554 start = floor((n - 2) / 2);
1557 // siftdown(start, count-1);
1559 while(root * 2 + 1 <= n-1)
1561 child = root * 2 + 1;
1563 if(cmp(child, child+1, pass) < 0)
1565 if(cmp(root, child, pass) < 0)
1567 swap(root, child, pass);
1583 // siftdown(0, end);
1585 while(root * 2 + 1 <= end)
1587 child = root * 2 + 1;
1588 if(child < end && cmp(child, child+1, pass) < 0)
1590 if(cmp(root, child, pass) < 0)
1592 swap(root, child, pass);
1602 void RandomSelection_Init()
1604 RandomSelection_totalweight = 0;
1605 RandomSelection_chosen_ent = world;
1606 RandomSelection_chosen_float = 0;
1607 RandomSelection_chosen_string = string_null;
1608 RandomSelection_best_priority = -1;
1610 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1612 if(priority > RandomSelection_best_priority)
1614 RandomSelection_best_priority = priority;
1615 RandomSelection_chosen_ent = e;
1616 RandomSelection_chosen_float = f;
1617 RandomSelection_chosen_string = s;
1618 RandomSelection_totalweight = weight;
1620 else if(priority == RandomSelection_best_priority)
1622 RandomSelection_totalweight += weight;
1623 if(random() * RandomSelection_totalweight <= weight)
1625 RandomSelection_chosen_ent = e;
1626 RandomSelection_chosen_float = f;
1627 RandomSelection_chosen_string = s;
1632 vector healtharmor_maxdamage(float h, float a, float armorblock)
1634 // NOTE: we'll always choose the SMALLER value...
1635 float healthdamage, armordamage, armorideal;
1637 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1638 armordamage = a + (h - 1); // damage we can take if we could use more armor
1639 armorideal = healthdamage * armorblock;
1641 if(armordamage < healthdamage)
1654 vector healtharmor_applydamage(float a, float armorblock, float damage)
1657 v_y = bound(0, damage * armorblock, a); // save
1658 v_x = bound(0, damage - v_y, damage); // take
1663 string getcurrentmod()
1667 m = cvar_string("fs_gamedir");
1668 n = tokenize_console(m);
1680 v = ReadShort() * 256; // note: this is signed
1681 v += ReadByte(); // note: this is unsigned
1685 void WriteInt24_t(float dest, float val)
1688 WriteShort(dest, (v = floor(val / 256)));
1689 WriteByte(dest, val - v * 256); // 0..255
1694 float float2range11(float f)
1696 // continuous function mapping all reals into -1..1
1697 return f / (fabs(f) + 1);
1700 float float2range01(float f)
1702 // continuous function mapping all reals into 0..1
1703 return 0.5 + 0.5 * float2range11(f);
1706 // from the GNU Scientific Library
1707 float gsl_ran_gaussian_lastvalue;
1708 float gsl_ran_gaussian_lastvalue_set;
1709 float gsl_ran_gaussian(float sigma)
1712 if(gsl_ran_gaussian_lastvalue_set)
1714 gsl_ran_gaussian_lastvalue_set = 0;
1715 return sigma * gsl_ran_gaussian_lastvalue;
1719 a = random() * 2 * M_PI;
1720 b = sqrt(-2 * log(random()));
1721 gsl_ran_gaussian_lastvalue = cos(a) * b;
1722 gsl_ran_gaussian_lastvalue_set = 1;
1723 return sigma * sin(a) * b;