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;
691 string fixPriorityList(string order, float from, float to, float subtract, float complete)
696 n = tokenize_console(order);
698 for(i = 0; i < n; ++i)
703 if(w >= from && w <= to)
704 neworder = strcat(neworder, ftos(w), " ");
708 if(w >= from && w <= to)
709 neworder = strcat(neworder, ftos(w), " ");
716 n = tokenize_console(neworder);
717 for(w = to; w >= from; --w)
719 for(i = 0; i < n; ++i)
720 if(stof(argv(i)) == w)
722 if(i == n) // not found
723 neworder = strcat(neworder, ftos(w), " ");
727 return substring(neworder, 0, strlen(neworder) - 1);
730 string mapPriorityList(string order, string(string) mapfunc)
735 n = tokenize_console(order);
737 for(i = 0; i < n; ++i)
738 neworder = strcat(neworder, mapfunc(argv(i)), " ");
740 return substring(neworder, 0, strlen(neworder) - 1);
743 string swapInPriorityList(string order, float i, float j)
748 n = tokenize_console(order);
750 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
753 for(w = 0; w < n; ++w)
756 s = strcat(s, argv(j), " ");
758 s = strcat(s, argv(i), " ");
760 s = strcat(s, argv(w), " ");
762 return substring(s, 0, strlen(s) - 1);
768 float cvar_value_issafe(string s)
770 if(strstrofs(s, "\"", 0) >= 0)
772 if(strstrofs(s, "\\", 0) >= 0)
774 if(strstrofs(s, ";", 0) >= 0)
776 if(strstrofs(s, "$", 0) >= 0)
778 if(strstrofs(s, "\r", 0) >= 0)
780 if(strstrofs(s, "\n", 0) >= 0)
786 void get_mi_min_max(float mode)
791 strunzone(mi_shortname);
792 mi_shortname = mapname;
793 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
794 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
795 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
796 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
797 mi_shortname = strzone(mi_shortname);
809 MapInfo_Get_ByName(mi_shortname, 0, 0);
810 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
812 mi_min = MapInfo_Map_mins;
813 mi_max = MapInfo_Map_maxs;
821 tracebox('1 0 0' * mi_x,
822 '0 1 0' * mi_y + '0 0 1' * mi_z,
823 '0 1 0' * ma_y + '0 0 1' * ma_z,
827 if(!trace_startsolid)
828 mi_min_x = trace_endpos_x;
830 tracebox('0 1 0' * mi_y,
831 '1 0 0' * mi_x + '0 0 1' * mi_z,
832 '1 0 0' * ma_x + '0 0 1' * ma_z,
836 if(!trace_startsolid)
837 mi_min_y = trace_endpos_y;
839 tracebox('0 0 1' * mi_z,
840 '1 0 0' * mi_x + '0 1 0' * mi_y,
841 '1 0 0' * ma_x + '0 1 0' * ma_y,
845 if(!trace_startsolid)
846 mi_min_z = trace_endpos_z;
848 tracebox('1 0 0' * ma_x,
849 '0 1 0' * mi_y + '0 0 1' * mi_z,
850 '0 1 0' * ma_y + '0 0 1' * ma_z,
854 if(!trace_startsolid)
855 mi_max_x = trace_endpos_x;
857 tracebox('0 1 0' * ma_y,
858 '1 0 0' * mi_x + '0 0 1' * mi_z,
859 '1 0 0' * ma_x + '0 0 1' * ma_z,
863 if(!trace_startsolid)
864 mi_max_y = trace_endpos_y;
866 tracebox('0 0 1' * ma_z,
867 '1 0 0' * mi_x + '0 1 0' * mi_y,
868 '1 0 0' * ma_x + '0 1 0' * ma_y,
872 if(!trace_startsolid)
873 mi_max_z = trace_endpos_z;
878 void get_mi_min_max_texcoords(float mode)
882 get_mi_min_max(mode);
887 // extend mi_picmax to get a square aspect ratio
888 // center the map in that area
889 extend = mi_picmax - mi_picmin;
890 if(extend_y > extend_x)
892 mi_picmin_x -= (extend_y - extend_x) * 0.5;
893 mi_picmax_x += (extend_y - extend_x) * 0.5;
897 mi_picmin_y -= (extend_x - extend_y) * 0.5;
898 mi_picmax_y += (extend_x - extend_y) * 0.5;
901 // add another some percent
902 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
906 // calculate the texcoords
907 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
908 // first the two corners of the origin
909 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
910 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
911 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
912 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
913 // then the other corners
914 mi_pictexcoord1_x = mi_pictexcoord0_x;
915 mi_pictexcoord1_y = mi_pictexcoord2_y;
916 mi_pictexcoord3_x = mi_pictexcoord2_x;
917 mi_pictexcoord3_y = mi_pictexcoord0_y;
922 void cvar_settemp(string pKey, string pValue)
924 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
926 void cvar_settemp_restore()
928 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
931 void cvar_settemp(string pKey, string pValue)
935 if(cvar_string(pKey) == pValue)
937 i = cvar("settemp_idx");
938 cvar_set("settemp_idx", ftos(i+1));
939 settemp_var = strcat("_settemp_x", ftos(i));
941 registercvar(settemp_var, "", 0);
943 registercvar(settemp_var, "");
945 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
946 cvar_set(settemp_var, cvar_string(pKey));
947 cvar_set(pKey, pValue);
950 void cvar_settemp_restore()
952 // undo what cvar_settemp did
954 n = tokenize_console(cvar_string("settemp_list"));
955 for(i = 0; i < n - 3; i += 3)
956 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
957 cvar_set("settemp_list", "0");
961 float almost_equals(float a, float b)
964 eps = (max(a, -a) + max(b, -b)) * 0.001;
965 if(a - b < eps && b - a < eps)
970 float almost_in_bounds(float a, float b, float c)
973 eps = (max(a, -a) + max(c, -c)) * 0.001;
974 return b == median(a - eps, b, c + eps);
977 float power2of(float e)
981 float log2of(float x)
983 // NOTE: generated code
1056 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1060 else if(ma == rgb_x)
1063 return (rgb_y - rgb_z) / (ma - mi);
1065 return (rgb_y - rgb_z) / (ma - mi) + 6;
1067 else if(ma == rgb_y)
1068 return (rgb_z - rgb_x) / (ma - mi) + 2;
1069 else // if(ma == rgb_z)
1070 return (rgb_x - rgb_y) / (ma - mi) + 4;
1073 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1077 hue -= 6 * floor(hue / 6);
1079 //else if(ma == rgb_x)
1080 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1084 rgb_y = hue * (ma - mi) + mi;
1087 //else if(ma == rgb_y)
1088 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1091 rgb_x = (2 - hue) * (ma - mi) + mi;
1099 rgb_z = (hue - 2) * (ma - mi) + mi;
1101 //else // if(ma == rgb_z)
1102 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1106 rgb_y = (4 - hue) * (ma - mi) + mi;
1111 rgb_x = (hue - 4) * (ma - mi) + mi;
1115 //else if(ma == rgb_x)
1116 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1117 else // if(hue <= 6)
1121 rgb_z = (6 - hue) * (ma - mi) + mi;
1127 vector rgb_to_hsv(vector rgb)
1132 mi = min3(rgb_x, rgb_y, rgb_z);
1133 ma = max3(rgb_x, rgb_y, rgb_z);
1135 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1146 vector hsv_to_rgb(vector hsv)
1148 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1151 vector rgb_to_hsl(vector rgb)
1156 mi = min3(rgb_x, rgb_y, rgb_z);
1157 ma = max3(rgb_x, rgb_y, rgb_z);
1159 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1161 hsl_z = 0.5 * (mi + ma);
1164 else if(hsl_z <= 0.5)
1165 hsl_y = (ma - mi) / (2*hsl_z);
1166 else // if(hsl_z > 0.5)
1167 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1172 vector hsl_to_rgb(vector hsl)
1174 float mi, ma, maminusmi;
1177 maminusmi = hsl_y * 2 * hsl_z;
1179 maminusmi = hsl_y * (2 - 2 * hsl_z);
1181 // hsl_z = 0.5 * mi + 0.5 * ma
1182 // maminusmi = - mi + ma
1183 mi = hsl_z - 0.5 * maminusmi;
1184 ma = hsl_z + 0.5 * maminusmi;
1186 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1189 string rgb_to_hexcolor(vector rgb)
1194 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1195 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1196 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1200 // requires that m2>m1 in all coordinates, and that m4>m3
1201 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;};
1203 // requires the same, but is a stronger condition
1204 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;};
1209 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1211 float ICanHasKallerz;
1213 // detect color codes support in the width function
1214 ICanHasKallerz = (w("^7", theSize) == 0);
1217 // The following function is SLOW.
1218 // For your safety and for the protection of those around you...
1219 // DO NOT CALL THIS AT HOME.
1220 // No really, don't.
1221 if(w(theText, theSize) <= maxWidth)
1222 return strlen(theText); // yeah!
1224 // binary search for right place to cut string
1226 float left, right, middle; // this always works
1228 right = strlen(theText); // this always fails
1231 middle = floor((left + right) / 2);
1232 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1237 while(left < right - 1);
1241 // NOTE: when color codes are involved, this binary search is,
1242 // mathematically, BROKEN. However, it is obviously guaranteed to
1243 // terminate, as the range still halves each time - but nevertheless, it is
1244 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1245 // range, and "right" is outside).
1247 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1248 // and decrease left on the basis of the chars detected of the truncated tag
1249 // Even if the ^xrgb tag is not complete/correct, left is decreased
1250 // (sometimes too much but with a correct result)
1251 // it fixes also ^[0-9]
1252 while(left >= 1 && substring(theText, left-1, 1) == "^")
1255 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1257 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1259 ch = str2chr(theText, left-1);
1260 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1263 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1265 ch = str2chr(theText, left-2);
1266 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1268 ch = str2chr(theText, left-1);
1269 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1278 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1280 float ICanHasKallerz;
1282 // detect color codes support in the width function
1283 ICanHasKallerz = (w("^7") == 0);
1286 // The following function is SLOW.
1287 // For your safety and for the protection of those around you...
1288 // DO NOT CALL THIS AT HOME.
1289 // No really, don't.
1290 if(w(theText) <= maxWidth)
1291 return strlen(theText); // yeah!
1293 // binary search for right place to cut string
1295 float left, right, middle; // this always works
1297 right = strlen(theText); // this always fails
1300 middle = floor((left + right) / 2);
1301 if(w(substring(theText, 0, middle)) <= maxWidth)
1306 while(left < right - 1);
1310 // NOTE: when color codes are involved, this binary search is,
1311 // mathematically, BROKEN. However, it is obviously guaranteed to
1312 // terminate, as the range still halves each time - but nevertheless, it is
1313 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1314 // range, and "right" is outside).
1316 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1317 // and decrease left on the basis of the chars detected of the truncated tag
1318 // Even if the ^xrgb tag is not complete/correct, left is decreased
1319 // (sometimes too much but with a correct result)
1320 // it fixes also ^[0-9]
1321 while(left >= 1 && substring(theText, left-1, 1) == "^")
1324 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1326 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1328 ch = str2chr(theText, left-1);
1329 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1332 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1334 ch = str2chr(theText, left-2);
1335 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1337 ch = str2chr(theText, left-1);
1338 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1347 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1353 s = getWrappedLine_remaining;
1355 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1356 if(cantake > 0 && cantake < strlen(s))
1359 while(take > 0 && substring(s, take, 1) != " ")
1363 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1364 if(getWrappedLine_remaining == "")
1365 getWrappedLine_remaining = string_null;
1366 return substring(s, 0, cantake);
1370 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1371 if(getWrappedLine_remaining == "")
1372 getWrappedLine_remaining = string_null;
1373 return substring(s, 0, take);
1378 getWrappedLine_remaining = string_null;
1383 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1389 s = getWrappedLine_remaining;
1391 cantake = textLengthUpToLength(s, w, tw);
1392 if(cantake > 0 && cantake < strlen(s))
1395 while(take > 0 && substring(s, take, 1) != " ")
1399 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1400 if(getWrappedLine_remaining == "")
1401 getWrappedLine_remaining = string_null;
1402 return substring(s, 0, cantake);
1406 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1407 if(getWrappedLine_remaining == "")
1408 getWrappedLine_remaining = string_null;
1409 return substring(s, 0, take);
1414 getWrappedLine_remaining = string_null;
1419 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1421 if(tw(theText, theFontSize) <= maxWidth)
1424 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1427 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1429 if(tw(theText) <= maxWidth)
1432 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1435 float isGametypeInFilter(float gt, float tp, string pattern)
1437 string subpattern, subpattern2, subpattern3;
1438 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1440 subpattern2 = ",teams,";
1442 subpattern2 = ",noteams,";
1443 if(gt == GAME_RACE || gt == GAME_CTS)
1444 subpattern3 = ",race,";
1446 subpattern3 = string_null;
1448 if(substring(pattern, 0, 1) == "-")
1450 pattern = substring(pattern, 1, strlen(pattern) - 1);
1451 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1453 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1455 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1460 if(substring(pattern, 0, 1) == "+")
1461 pattern = substring(pattern, 1, strlen(pattern) - 1);
1462 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1463 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1464 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1470 void shuffle(float n, swapfunc_t swap, entity pass)
1473 for(i = 1; i < n; ++i)
1475 // swap i-th item at a random position from 0 to i
1476 // proof for even distribution:
1479 // item n+1 gets at any position with chance 1/(n+1)
1480 // all others will get their 1/n chance reduced by factor n/(n+1)
1481 // to be on place n+1, their chance will be 1/(n+1)
1482 // 1/n * n/(n+1) = 1/(n+1)
1484 j = floor(random() * (i + 1));
1490 string substring_range(string s, float b, float e)
1492 return substring(s, b, e - b);
1495 string swapwords(string str, float i, float j)
1498 string s1, s2, s3, s4, s5;
1499 float si, ei, sj, ej, s0, en;
1500 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1501 si = argv_start_index(i);
1502 sj = argv_start_index(j);
1503 ei = argv_end_index(i);
1504 ej = argv_end_index(j);
1505 s0 = argv_start_index(0);
1506 en = argv_end_index(n-1);
1507 s1 = substring_range(str, s0, si);
1508 s2 = substring_range(str, si, ei);
1509 s3 = substring_range(str, ei, sj);
1510 s4 = substring_range(str, sj, ej);
1511 s5 = substring_range(str, ej, en);
1512 return strcat(s1, s4, s3, s2, s5);
1515 string _shufflewords_str;
1516 void _shufflewords_swapfunc(float i, float j, entity pass)
1518 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1520 string shufflewords(string str)
1523 _shufflewords_str = str;
1524 n = tokenizebyseparator(str, " ");
1525 shuffle(n, _shufflewords_swapfunc, world);
1526 str = _shufflewords_str;
1527 _shufflewords_str = string_null;
1531 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1547 // actually, every number solves the equation!
1558 if(a > 0) // put the smaller solution first
1560 v_x = ((-b)-D) / (2*a);
1561 v_y = ((-b)+D) / (2*a);
1565 v_x = (-b+D) / (2*a);
1566 v_y = (-b-D) / (2*a);
1572 // complex solutions!
1586 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1587 float _unacceptable_compiler_bug_1_b() { return 1; }
1588 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1589 float _unacceptable_compiler_bug_1_d() { return 1; }
1591 void check_unacceptable_compiler_bugs()
1593 if(cvar("_allow_unacceptable_compiler_bugs"))
1595 tokenize_console("foo bar");
1596 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1597 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.");
1600 float compressShotOrigin(vector v)
1604 y = rint(v_y * 4) + 128;
1605 z = rint(v_z * 4) + 128;
1606 if(x > 255 || x < 0)
1608 print("shot origin ", vtos(v), " x out of bounds\n");
1609 x = bound(0, x, 255);
1611 if(y > 255 || y < 0)
1613 print("shot origin ", vtos(v), " y out of bounds\n");
1614 y = bound(0, y, 255);
1616 if(z > 255 || z < 0)
1618 print("shot origin ", vtos(v), " z out of bounds\n");
1619 z = bound(0, z, 255);
1621 return x * 0x10000 + y * 0x100 + z;
1623 vector decompressShotOrigin(float f)
1626 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1627 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1628 v_z = ((f & 0xFF) - 128) / 4;
1632 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1634 float start, end, root, child;
1637 start = floor((n - 2) / 2);
1640 // siftdown(start, count-1);
1642 while(root * 2 + 1 <= n-1)
1644 child = root * 2 + 1;
1646 if(cmp(child, child+1, pass) < 0)
1648 if(cmp(root, child, pass) < 0)
1650 swap(root, child, pass);
1666 // siftdown(0, end);
1668 while(root * 2 + 1 <= end)
1670 child = root * 2 + 1;
1671 if(child < end && cmp(child, child+1, pass) < 0)
1673 if(cmp(root, child, pass) < 0)
1675 swap(root, child, pass);
1685 void RandomSelection_Init()
1687 RandomSelection_totalweight = 0;
1688 RandomSelection_chosen_ent = world;
1689 RandomSelection_chosen_float = 0;
1690 RandomSelection_chosen_string = string_null;
1691 RandomSelection_best_priority = -1;
1693 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1695 if(priority > RandomSelection_best_priority)
1697 RandomSelection_best_priority = priority;
1698 RandomSelection_chosen_ent = e;
1699 RandomSelection_chosen_float = f;
1700 RandomSelection_chosen_string = s;
1701 RandomSelection_totalweight = weight;
1703 else if(priority == RandomSelection_best_priority)
1705 RandomSelection_totalweight += weight;
1706 if(random() * RandomSelection_totalweight <= weight)
1708 RandomSelection_chosen_ent = e;
1709 RandomSelection_chosen_float = f;
1710 RandomSelection_chosen_string = s;
1715 vector healtharmor_maxdamage(float h, float a, float armorblock)
1717 // NOTE: we'll always choose the SMALLER value...
1718 float healthdamage, armordamage, armorideal;
1720 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1721 armordamage = a + (h - 1); // damage we can take if we could use more armor
1722 armorideal = healthdamage * armorblock;
1724 if(armordamage < healthdamage)
1737 vector healtharmor_applydamage(float a, float armorblock, float damage)
1740 v_y = bound(0, damage * armorblock, a); // save
1741 v_x = bound(0, damage - v_y, damage); // take
1746 string getcurrentmod()
1750 m = cvar_string("fs_gamedir");
1751 n = tokenize_console(m);
1763 v = ReadShort() * 256; // note: this is signed
1764 v += ReadByte(); // note: this is unsigned
1768 void WriteInt24_t(float dest, float val)
1771 WriteShort(dest, (v = floor(val / 256)));
1772 WriteByte(dest, val - v * 256); // 0..255
1777 float float2range11(float f)
1779 // continuous function mapping all reals into -1..1
1780 return f / (fabs(f) + 1);
1783 float float2range01(float f)
1785 // continuous function mapping all reals into 0..1
1786 return 0.5 + 0.5 * float2range11(f);
1789 // from the GNU Scientific Library
1790 float gsl_ran_gaussian_lastvalue;
1791 float gsl_ran_gaussian_lastvalue_set;
1792 float gsl_ran_gaussian(float sigma)
1795 if(gsl_ran_gaussian_lastvalue_set)
1797 gsl_ran_gaussian_lastvalue_set = 0;
1798 return sigma * gsl_ran_gaussian_lastvalue;
1802 a = random() * 2 * M_PI;
1803 b = sqrt(-2 * log(random()));
1804 gsl_ran_gaussian_lastvalue = cos(a) * b;
1805 gsl_ran_gaussian_lastvalue_set = 1;
1806 return sigma * sin(a) * b;
1810 string car(string s)
1813 o = strstrofs(s, " ", 0);
1816 return substring(s, 0, o);
1818 string cdr(string s)
1821 o = strstrofs(s, " ", 0);
1824 return substring(s, o + 1, strlen(s) - (o + 1));
1826 float matchacl(string acl, string str)
1833 t = car(acl); acl = cdr(acl);
1835 if(substring(t, 0, 1) == "-")
1838 t = substring(t, 1, strlen(t) - 1);
1840 else if(substring(t, 0, 1) == "+")
1841 t = substring(t, 1, strlen(t) - 1);
1842 if(substring(t, -1, 1) == "*")
1844 t = substring(t, 0, strlen(t) - 1);
1845 s = substring(s, 0, strlen(t));
1857 float startsWith(string haystack, string needle)
1859 return substring(haystack, 0, strlen(needle)) == needle;
1861 float startsWithNocase(string haystack, string needle)
1863 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1866 string get_model_datafilename(string m, float sk, string fil)
1871 m = "models/player/*_";
1873 m = strcat(m, ftos(sk));
1876 return strcat(m, ".", fil);
1879 float get_model_parameters(string m, float sk)
1884 get_model_parameters_modelname = string_null;
1885 get_model_parameters_modelskin = -1;
1886 get_model_parameters_name = string_null;
1887 get_model_parameters_species = -1;
1888 get_model_parameters_sex = string_null;
1889 get_model_parameters_weight = -1;
1890 get_model_parameters_age = -1;
1891 get_model_parameters_desc = string_null;
1897 if(substring(m, -4, -1) != ".txt")
1899 if(substring(m, -6, 1) != "_")
1901 sk = stof(substring(m, -5, 1));
1902 m = substring(m, 0, -7);
1905 fn = get_model_datafilename(m, sk, "txt");
1906 fh = fopen(fn, FILE_READ);
1910 get_model_parameters_modelname = m;
1911 get_model_parameters_modelskin = sk;
1912 while((s = fgets(fh)))
1915 break; // next lines will be description
1919 get_model_parameters_name = s;
1923 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1924 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1925 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1926 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1927 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1928 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1929 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1932 get_model_parameters_sex = s;
1934 get_model_parameters_weight = stof(s);
1936 get_model_parameters_age = stof(s);
1939 while((s = fgets(fh)))
1941 if(get_model_parameters_desc)
1942 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1944 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1952 // we probably want to get rid of GetName in the most places in hud.qc, TODO!
1953 // Get name of specified panel id
1954 #define HUD_Panel_GetName(id) \
1956 case HUD_PANEL_WEAPONICONS: panel_name = HUD_PANELNAME_WEAPONICONS; break; \
1957 case HUD_PANEL_INVENTORY: panel_name = HUD_PANELNAME_INVENTORY; break; \
1958 case HUD_PANEL_POWERUPS: panel_name = HUD_PANELNAME_POWERUPS; break; \
1959 case HUD_PANEL_HEALTHARMOR: panel_name = HUD_PANELNAME_HEALTHARMOR; break; \
1960 case HUD_PANEL_NOTIFY: panel_name = HUD_PANELNAME_NOTIFY; break; \
1961 case HUD_PANEL_TIMER: panel_name = HUD_PANELNAME_TIMER; break; \
1962 case HUD_PANEL_RADAR: panel_name = HUD_PANELNAME_RADAR; break; \
1963 case HUD_PANEL_SCORE: panel_name = HUD_PANELNAME_SCORE; break; \
1964 case HUD_PANEL_RACETIMER: panel_name = HUD_PANELNAME_RACETIMER; break; \
1965 case HUD_PANEL_VOTE: panel_name = HUD_PANELNAME_VOTE; break; \
1966 case HUD_PANEL_MODICONS: panel_name = HUD_PANELNAME_MODICONS; break; \
1967 case HUD_PANEL_PRESSEDKEYS: panel_name = HUD_PANELNAME_PRESSEDKEYS; break; \
1968 case HUD_PANEL_CHAT: panel_name = HUD_PANELNAME_CHAT; break; \
1969 case HUD_PANEL_ENGINEINFO: panel_name = HUD_PANELNAME_ENGINEINFO; break; \