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)
229 // we have sprintf...
230 return sprintf("%.*f", decimals, number);
234 vector colormapPaletteColor(float c, float isPants)
238 case 0: return '0.800000 0.800000 0.800000';
239 case 1: return '0.600000 0.400000 0.000000';
240 case 2: return '0.000000 1.000000 0.501961';
241 case 3: return '0.000000 1.000000 0.000000';
242 case 4: return '1.000000 0.000000 0.000000';
243 case 5: return '0.000000 0.658824 1.000000';
244 case 6: return '0.000000 1.000000 1.000000';
245 case 7: return '0.501961 1.000000 0.000000';
246 case 8: return '0.501961 0.000000 1.000000';
247 case 9: return '1.000000 0.000000 1.000000';
248 case 10: return '1.000000 0.000000 0.501961';
249 case 11: return '0.600000 0.600000 0.600000';
250 case 12: return '1.000000 1.000000 0.000000';
251 case 13: return '0.000000 0.313725 1.000000';
252 case 14: return '1.000000 0.501961 0.000000';
256 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
257 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
258 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
261 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
262 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
263 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
264 default: return '0.000 0.000 0.000';
268 // unzone the string, and return it as tempstring. Safe to be called on string_null
269 string fstrunzone(string s)
279 // Databases (hash tables)
280 #define DB_BUCKETS 8192
281 void db_save(float db, string pFilename)
284 fh = fopen(pFilename, FILE_WRITE);
287 print(strcat("^1Can't write DB to ", pFilename));
291 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
292 for(i = 0; i < n; ++i)
293 fputs(fh, strcat(bufstr_get(db, i), "\n"));
302 float db_load(string pFilename)
304 float db, fh, i, j, n;
309 fh = fopen(pFilename, FILE_READ);
313 if(stof(l) == DB_BUCKETS)
316 while((l = fgets(fh)))
319 bufstr_set(db, i, l);
325 // different count of buckets, or a dump?
326 // need to reorganize the database then (SLOW)
328 // note: we also parse the first line (l) in case the DB file is
329 // missing the bucket count
332 n = tokenizebyseparator(l, "\\");
333 for(j = 2; j < n; j += 2)
334 db_put(db, argv(j-1), uri_unescape(argv(j)));
336 while((l = fgets(fh)));
342 void db_dump(float db, string pFilename)
344 float fh, i, j, n, m;
345 fh = fopen(pFilename, FILE_WRITE);
347 error(strcat("Can't dump DB to ", pFilename));
350 for(i = 0; i < n; ++i)
352 m = tokenizebyseparator(bufstr_get(db, i), "\\");
353 for(j = 2; j < m; j += 2)
354 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
359 void db_close(float db)
364 string db_get(float db, string pKey)
367 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
368 return uri_unescape(infoget(bufstr_get(db, h), pKey));
371 void db_put(float db, string pKey, string pValue)
374 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
375 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
382 db = db_load("foo.db");
383 print("LOADED. FILL...\n");
384 for(i = 0; i < DB_BUCKETS; ++i)
385 db_put(db, ftos(random()), "X");
386 print("FILLED. SAVE...\n");
387 db_save(db, "foo.db");
388 print("SAVED. CLOSE...\n");
393 // Multiline text file buffers
394 float buf_load(string pFilename)
401 fh = fopen(pFilename, FILE_READ);
405 while((l = fgets(fh)))
407 bufstr_set(buf, i, l);
414 void buf_save(float buf, string pFilename)
417 fh = fopen(pFilename, FILE_WRITE);
419 error(strcat("Can't write buf to ", pFilename));
420 n = buf_getsize(buf);
421 for(i = 0; i < n; ++i)
422 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
426 string GametypeNameFromType(float g)
428 if (g == GAME_DEATHMATCH) return "dm";
429 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
430 else if (g == GAME_DOMINATION) return "dom";
431 else if (g == GAME_CTF) return "ctf";
432 else if (g == GAME_RUNEMATCH) return "rune";
433 else if (g == GAME_LMS) return "lms";
434 else if (g == GAME_ARENA) return "arena";
435 else if (g == GAME_CA) return "ca";
436 else if (g == GAME_KEYHUNT) return "kh";
437 else if (g == GAME_ONSLAUGHT) return "ons";
438 else if (g == GAME_ASSAULT) return "as";
439 else if (g == GAME_RACE) return "rc";
440 else if (g == GAME_NEXBALL) return "nexball";
441 else if (g == GAME_CTS) return "cts";
445 string mmsss(float tenths)
449 tenths = floor(tenths + 0.5);
450 minutes = floor(tenths / 600);
451 tenths -= minutes * 600;
452 s = ftos(1000 + tenths);
453 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
456 string mmssss(float hundredths)
460 hundredths = floor(hundredths + 0.5);
461 minutes = floor(hundredths / 6000);
462 hundredths -= minutes * 6000;
463 s = ftos(10000 + hundredths);
464 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
467 string ScoreString(float pFlags, float pValue)
472 pValue = floor(pValue + 0.5); // round
474 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
476 else if(pFlags & SFL_RANK)
478 valstr = ftos(pValue);
480 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
481 valstr = strcat(valstr, "th");
482 else if(substring(valstr, l - 1, 1) == "1")
483 valstr = strcat(valstr, "st");
484 else if(substring(valstr, l - 1, 1) == "2")
485 valstr = strcat(valstr, "nd");
486 else if(substring(valstr, l - 1, 1) == "3")
487 valstr = strcat(valstr, "rd");
489 valstr = strcat(valstr, "th");
491 else if(pFlags & SFL_TIME)
492 valstr = TIME_ENCODED_TOSTRING(pValue);
494 valstr = ftos(pValue);
499 vector cross(vector a, vector b)
502 '1 0 0' * (a_y * b_z - a_z * b_y)
503 + '0 1 0' * (a_z * b_x - a_x * b_z)
504 + '0 0 1' * (a_x * b_y - a_y * b_x);
507 // compressed vector format:
508 // like MD3, just even shorter
509 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
510 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
511 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
512 // length = 2^(length_encoded/8) / 8
513 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
514 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
515 // the special value 0 indicates the zero vector
517 float lengthLogTable[128];
519 float invertLengthLog(float x)
521 float l, r, m, lerr, rerr;
523 if(x >= lengthLogTable[127])
525 if(x <= lengthLogTable[0])
533 m = floor((l + r) / 2);
534 if(lengthLogTable[m] < x)
540 // now: r is >=, l is <
541 lerr = (x - lengthLogTable[l]);
542 rerr = (lengthLogTable[r] - x);
548 vector decompressShortVector(float data)
551 float pitch, yaw, len;
554 pitch = (data & 0xF000) / 0x1000;
555 yaw = (data & 0x0F80) / 0x80;
556 len = (data & 0x007F);
558 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
571 yaw = .19634954084936207740 * yaw;
572 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
573 out_x = cos(yaw) * cos(pitch);
574 out_y = sin(yaw) * cos(pitch);
578 //print("decompressed: ", vtos(out), "\n");
580 return out * lengthLogTable[len];
583 float compressShortVector(vector vec)
586 float pitch, yaw, len;
589 //print("compress: ", vtos(vec), "\n");
590 ang = vectoangles(vec);
594 if(ang_x < -90 && ang_x > +90)
595 error("BOGUS vectoangles");
596 //print("angles: ", vtos(ang), "\n");
598 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
607 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
608 len = invertLengthLog(vlen(vec));
610 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
612 return (pitch * 0x1000) + (yaw * 0x80) + len;
615 void compressShortVector_init()
620 for(i = 0; i < 128; ++i)
622 lengthLogTable[i] = l;
626 if(cvar("developer"))
628 print("Verifying vector compression table...\n");
629 for(i = 0x0F00; i < 0xFFFF; ++i)
630 if(i != compressShortVector(decompressShortVector(i)))
632 print("BROKEN vector compression: ", ftos(i));
633 print(" -> ", vtos(decompressShortVector(i)));
634 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
643 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
645 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
646 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
647 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
648 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
649 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
650 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
651 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
652 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
653 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
654 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
655 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
656 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
661 string fixPriorityList(string order, float from, float to, float subtract, float complete)
666 n = tokenize_console(order);
668 for(i = 0; i < n; ++i)
673 if(w >= from && w <= to)
674 neworder = strcat(neworder, ftos(w), " ");
678 if(w >= from && w <= to)
679 neworder = strcat(neworder, ftos(w), " ");
686 n = tokenize_console(neworder);
687 for(w = to; w >= from; --w)
689 for(i = 0; i < n; ++i)
690 if(stof(argv(i)) == w)
692 if(i == n) // not found
693 neworder = strcat(neworder, ftos(w), " ");
697 return substring(neworder, 0, strlen(neworder) - 1);
700 string mapPriorityList(string order, string(string) mapfunc)
705 n = tokenize_console(order);
707 for(i = 0; i < n; ++i)
708 neworder = strcat(neworder, mapfunc(argv(i)), " ");
710 return substring(neworder, 0, strlen(neworder) - 1);
713 string swapInPriorityList(string order, float i, float j)
718 n = tokenize_console(order);
720 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
723 for(w = 0; w < n; ++w)
726 s = strcat(s, argv(j), " ");
728 s = strcat(s, argv(i), " ");
730 s = strcat(s, argv(w), " ");
732 return substring(s, 0, strlen(s) - 1);
738 float cvar_value_issafe(string s)
740 if(strstrofs(s, "\"", 0) >= 0)
742 if(strstrofs(s, "\\", 0) >= 0)
744 if(strstrofs(s, ";", 0) >= 0)
746 if(strstrofs(s, "$", 0) >= 0)
748 if(strstrofs(s, "\r", 0) >= 0)
750 if(strstrofs(s, "\n", 0) >= 0)
756 void get_mi_min_max(float mode)
761 strunzone(mi_shortname);
762 mi_shortname = mapname;
763 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
764 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
765 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
766 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
767 mi_shortname = strzone(mi_shortname);
779 MapInfo_Get_ByName(mi_shortname, 0, 0);
780 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
782 mi_min = MapInfo_Map_mins;
783 mi_max = MapInfo_Map_maxs;
791 tracebox('1 0 0' * mi_x,
792 '0 1 0' * mi_y + '0 0 1' * mi_z,
793 '0 1 0' * ma_y + '0 0 1' * ma_z,
797 if(!trace_startsolid)
798 mi_min_x = trace_endpos_x;
800 tracebox('0 1 0' * mi_y,
801 '1 0 0' * mi_x + '0 0 1' * mi_z,
802 '1 0 0' * ma_x + '0 0 1' * ma_z,
806 if(!trace_startsolid)
807 mi_min_y = trace_endpos_y;
809 tracebox('0 0 1' * mi_z,
810 '1 0 0' * mi_x + '0 1 0' * mi_y,
811 '1 0 0' * ma_x + '0 1 0' * ma_y,
815 if(!trace_startsolid)
816 mi_min_z = trace_endpos_z;
818 tracebox('1 0 0' * ma_x,
819 '0 1 0' * mi_y + '0 0 1' * mi_z,
820 '0 1 0' * ma_y + '0 0 1' * ma_z,
824 if(!trace_startsolid)
825 mi_max_x = trace_endpos_x;
827 tracebox('0 1 0' * ma_y,
828 '1 0 0' * mi_x + '0 0 1' * mi_z,
829 '1 0 0' * ma_x + '0 0 1' * ma_z,
833 if(!trace_startsolid)
834 mi_max_y = trace_endpos_y;
836 tracebox('0 0 1' * ma_z,
837 '1 0 0' * mi_x + '0 1 0' * mi_y,
838 '1 0 0' * ma_x + '0 1 0' * ma_y,
842 if(!trace_startsolid)
843 mi_max_z = trace_endpos_z;
848 void get_mi_min_max_texcoords(float mode)
852 get_mi_min_max(mode);
857 // extend mi_picmax to get a square aspect ratio
858 // center the map in that area
859 extend = mi_picmax - mi_picmin;
860 if(extend_y > extend_x)
862 mi_picmin_x -= (extend_y - extend_x) * 0.5;
863 mi_picmax_x += (extend_y - extend_x) * 0.5;
867 mi_picmin_y -= (extend_x - extend_y) * 0.5;
868 mi_picmax_y += (extend_x - extend_y) * 0.5;
871 // add another some percent
872 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
876 // calculate the texcoords
877 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
878 // first the two corners of the origin
879 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
880 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
881 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
882 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
883 // then the other corners
884 mi_pictexcoord1_x = mi_pictexcoord0_x;
885 mi_pictexcoord1_y = mi_pictexcoord2_y;
886 mi_pictexcoord3_x = mi_pictexcoord2_x;
887 mi_pictexcoord3_y = mi_pictexcoord0_y;
892 void cvar_settemp(string pKey, string pValue)
894 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
896 void cvar_settemp_restore()
898 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
901 void cvar_settemp(string pKey, string pValue)
905 if(cvar_string(pKey) == pValue)
907 i = cvar("settemp_idx");
908 cvar_set("settemp_idx", ftos(i+1));
909 settemp_var = strcat("_settemp_x", ftos(i));
911 registercvar(settemp_var, "", 0);
913 registercvar(settemp_var, "");
915 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
916 cvar_set(settemp_var, cvar_string(pKey));
917 cvar_set(pKey, pValue);
920 void cvar_settemp_restore()
922 // undo what cvar_settemp did
924 n = tokenize_console(cvar_string("settemp_list"));
925 for(i = 0; i < n - 3; i += 3)
926 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
927 cvar_set("settemp_list", "0");
931 float almost_equals(float a, float b)
934 eps = (max(a, -a) + max(b, -b)) * 0.001;
935 if(a - b < eps && b - a < eps)
940 float almost_in_bounds(float a, float b, float c)
943 eps = (max(a, -a) + max(c, -c)) * 0.001;
944 return b == median(a - eps, b, c + eps);
947 float power2of(float e)
951 float log2of(float x)
953 // NOTE: generated code
1026 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1030 else if(ma == rgb_x)
1033 return (rgb_y - rgb_z) / (ma - mi);
1035 return (rgb_y - rgb_z) / (ma - mi) + 6;
1037 else if(ma == rgb_y)
1038 return (rgb_z - rgb_x) / (ma - mi) + 2;
1039 else // if(ma == rgb_z)
1040 return (rgb_x - rgb_y) / (ma - mi) + 4;
1043 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1047 hue -= 6 * floor(hue / 6);
1049 //else if(ma == rgb_x)
1050 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1054 rgb_y = hue * (ma - mi) + mi;
1057 //else if(ma == rgb_y)
1058 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1061 rgb_x = (2 - hue) * (ma - mi) + mi;
1069 rgb_z = (hue - 2) * (ma - mi) + mi;
1071 //else // if(ma == rgb_z)
1072 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1076 rgb_y = (4 - hue) * (ma - mi) + mi;
1081 rgb_x = (hue - 4) * (ma - mi) + mi;
1085 //else if(ma == rgb_x)
1086 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1087 else // if(hue <= 6)
1091 rgb_z = (6 - hue) * (ma - mi) + mi;
1097 vector rgb_to_hsv(vector rgb)
1102 mi = min3(rgb_x, rgb_y, rgb_z);
1103 ma = max3(rgb_x, rgb_y, rgb_z);
1105 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1116 vector hsv_to_rgb(vector hsv)
1118 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1121 vector rgb_to_hsl(vector rgb)
1126 mi = min3(rgb_x, rgb_y, rgb_z);
1127 ma = max3(rgb_x, rgb_y, rgb_z);
1129 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1131 hsl_z = 0.5 * (mi + ma);
1134 else if(hsl_z <= 0.5)
1135 hsl_y = (ma - mi) / (2*hsl_z);
1136 else // if(hsl_z > 0.5)
1137 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1142 vector hsl_to_rgb(vector hsl)
1144 float mi, ma, maminusmi;
1147 maminusmi = hsl_y * 2 * hsl_z;
1149 maminusmi = hsl_y * (2 - 2 * hsl_z);
1151 // hsl_z = 0.5 * mi + 0.5 * ma
1152 // maminusmi = - mi + ma
1153 mi = hsl_z - 0.5 * maminusmi;
1154 ma = hsl_z + 0.5 * maminusmi;
1156 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1159 string rgb_to_hexcolor(vector rgb)
1164 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1165 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1166 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1170 // requires that m2>m1 in all coordinates, and that m4>m3
1171 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;};
1173 // requires the same, but is a stronger condition
1174 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;};
1179 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1181 float ICanHasKallerz;
1183 // detect color codes support in the width function
1184 ICanHasKallerz = (w("^7", theSize) == 0);
1187 // The following function is SLOW.
1188 // For your safety and for the protection of those around you...
1189 // DO NOT CALL THIS AT HOME.
1190 // No really, don't.
1191 if(w(theText, theSize) <= maxWidth)
1192 return strlen(theText); // yeah!
1194 // binary search for right place to cut string
1196 float left, right, middle; // this always works
1198 right = strlen(theText); // this always fails
1201 middle = floor((left + right) / 2);
1202 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1207 while(left < right - 1);
1211 // NOTE: when color codes are involved, this binary search is,
1212 // mathematically, BROKEN. However, it is obviously guaranteed to
1213 // terminate, as the range still halves each time - but nevertheless, it is
1214 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1215 // range, and "right" is outside).
1217 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1218 // and decrease left on the basis of the chars detected of the truncated tag
1219 // Even if the ^xrgb tag is not complete/correct, left is decreased
1220 // (sometimes too much but with a correct result)
1221 // it fixes also ^[0-9]
1222 while(left >= 1 && substring(theText, left-1, 1) == "^")
1225 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1227 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1229 ch = str2chr(theText, left-1);
1230 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1233 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1235 ch = str2chr(theText, left-2);
1236 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1238 ch = str2chr(theText, left-1);
1239 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1248 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_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, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1323 s = getWrappedLine_remaining;
1325 cantake = textLengthUpToWidth(s, w, theFontSize, 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 getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1359 s = getWrappedLine_remaining;
1361 cantake = textLengthUpToLength(s, w, tw);
1362 if(cantake > 0 && cantake < strlen(s))
1365 while(take > 0 && substring(s, take, 1) != " ")
1369 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1370 if(getWrappedLine_remaining == "")
1371 getWrappedLine_remaining = string_null;
1372 return substring(s, 0, cantake);
1376 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1377 if(getWrappedLine_remaining == "")
1378 getWrappedLine_remaining = string_null;
1379 return substring(s, 0, take);
1384 getWrappedLine_remaining = string_null;
1389 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1391 if(tw(theText, theFontSize) <= maxWidth)
1394 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1397 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1399 if(tw(theText) <= maxWidth)
1402 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1405 float isGametypeInFilter(float gt, float tp, string pattern)
1407 string subpattern, subpattern2, subpattern3;
1408 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1410 subpattern2 = ",teams,";
1412 subpattern2 = ",noteams,";
1413 if(gt == GAME_RACE || gt == GAME_CTS)
1414 subpattern3 = ",race,";
1416 subpattern3 = string_null;
1418 if(substring(pattern, 0, 1) == "-")
1420 pattern = substring(pattern, 1, strlen(pattern) - 1);
1421 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1423 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1425 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1430 if(substring(pattern, 0, 1) == "+")
1431 pattern = substring(pattern, 1, strlen(pattern) - 1);
1432 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1433 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1434 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1440 void shuffle(float n, swapfunc_t swap, entity pass)
1443 for(i = 1; i < n; ++i)
1445 // swap i-th item at a random position from 0 to i
1446 // proof for even distribution:
1449 // item n+1 gets at any position with chance 1/(n+1)
1450 // all others will get their 1/n chance reduced by factor n/(n+1)
1451 // to be on place n+1, their chance will be 1/(n+1)
1452 // 1/n * n/(n+1) = 1/(n+1)
1454 j = floor(random() * (i + 1));
1460 string substring_range(string s, float b, float e)
1462 return substring(s, b, e - b);
1465 string swapwords(string str, float i, float j)
1468 string s1, s2, s3, s4, s5;
1469 float si, ei, sj, ej, s0, en;
1470 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1471 si = argv_start_index(i);
1472 sj = argv_start_index(j);
1473 ei = argv_end_index(i);
1474 ej = argv_end_index(j);
1475 s0 = argv_start_index(0);
1476 en = argv_end_index(n-1);
1477 s1 = substring_range(str, s0, si);
1478 s2 = substring_range(str, si, ei);
1479 s3 = substring_range(str, ei, sj);
1480 s4 = substring_range(str, sj, ej);
1481 s5 = substring_range(str, ej, en);
1482 return strcat(s1, s4, s3, s2, s5);
1485 string _shufflewords_str;
1486 void _shufflewords_swapfunc(float i, float j, entity pass)
1488 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1490 string shufflewords(string str)
1493 _shufflewords_str = str;
1494 n = tokenizebyseparator(str, " ");
1495 shuffle(n, _shufflewords_swapfunc, world);
1496 str = _shufflewords_str;
1497 _shufflewords_str = string_null;
1501 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1517 // actually, every number solves the equation!
1528 if(a > 0) // put the smaller solution first
1530 v_x = ((-b)-D) / (2*a);
1531 v_y = ((-b)+D) / (2*a);
1535 v_x = (-b+D) / (2*a);
1536 v_y = (-b-D) / (2*a);
1542 // complex solutions!
1556 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1557 float _unacceptable_compiler_bug_1_b() { return 1; }
1558 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1559 float _unacceptable_compiler_bug_1_d() { return 1; }
1561 void check_unacceptable_compiler_bugs()
1563 if(cvar("_allow_unacceptable_compiler_bugs"))
1565 tokenize_console("foo bar");
1566 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1567 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.");
1570 float compressShotOrigin(vector v)
1574 y = rint(v_y * 4) + 128;
1575 z = rint(v_z * 4) + 128;
1576 if(x > 255 || x < 0)
1578 print("shot origin ", vtos(v), " x out of bounds\n");
1579 x = bound(0, x, 255);
1581 if(y > 255 || y < 0)
1583 print("shot origin ", vtos(v), " y out of bounds\n");
1584 y = bound(0, y, 255);
1586 if(z > 255 || z < 0)
1588 print("shot origin ", vtos(v), " z out of bounds\n");
1589 z = bound(0, z, 255);
1591 return x * 0x10000 + y * 0x100 + z;
1593 vector decompressShotOrigin(float f)
1596 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1597 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1598 v_z = ((f & 0xFF) - 128) / 4;
1602 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1604 float start, end, root, child;
1607 start = floor((n - 2) / 2);
1610 // siftdown(start, count-1);
1612 while(root * 2 + 1 <= n-1)
1614 child = root * 2 + 1;
1616 if(cmp(child, child+1, pass) < 0)
1618 if(cmp(root, child, pass) < 0)
1620 swap(root, child, pass);
1636 // siftdown(0, end);
1638 while(root * 2 + 1 <= end)
1640 child = root * 2 + 1;
1641 if(child < end && cmp(child, child+1, pass) < 0)
1643 if(cmp(root, child, pass) < 0)
1645 swap(root, child, pass);
1655 void RandomSelection_Init()
1657 RandomSelection_totalweight = 0;
1658 RandomSelection_chosen_ent = world;
1659 RandomSelection_chosen_float = 0;
1660 RandomSelection_chosen_string = string_null;
1661 RandomSelection_best_priority = -1;
1663 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1665 if(priority > RandomSelection_best_priority)
1667 RandomSelection_best_priority = priority;
1668 RandomSelection_chosen_ent = e;
1669 RandomSelection_chosen_float = f;
1670 RandomSelection_chosen_string = s;
1671 RandomSelection_totalweight = weight;
1673 else if(priority == RandomSelection_best_priority)
1675 RandomSelection_totalweight += weight;
1676 if(random() * RandomSelection_totalweight <= weight)
1678 RandomSelection_chosen_ent = e;
1679 RandomSelection_chosen_float = f;
1680 RandomSelection_chosen_string = s;
1685 vector healtharmor_maxdamage(float h, float a, float armorblock)
1687 // NOTE: we'll always choose the SMALLER value...
1688 float healthdamage, armordamage, armorideal;
1690 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1691 armordamage = a + (h - 1); // damage we can take if we could use more armor
1692 armorideal = healthdamage * armorblock;
1694 if(armordamage < healthdamage)
1707 vector healtharmor_applydamage(float a, float armorblock, float damage)
1710 v_y = bound(0, damage * armorblock, a); // save
1711 v_x = bound(0, damage - v_y, damage); // take
1716 string getcurrentmod()
1720 m = cvar_string("fs_gamedir");
1721 n = tokenize_console(m);
1733 v = ReadShort() * 256; // note: this is signed
1734 v += ReadByte(); // note: this is unsigned
1738 void WriteInt24_t(float dest, float val)
1741 WriteShort(dest, (v = floor(val / 256)));
1742 WriteByte(dest, val - v * 256); // 0..255
1747 float float2range11(float f)
1749 // continuous function mapping all reals into -1..1
1750 return f / (fabs(f) + 1);
1753 float float2range01(float f)
1755 // continuous function mapping all reals into 0..1
1756 return 0.5 + 0.5 * float2range11(f);
1759 // from the GNU Scientific Library
1760 float gsl_ran_gaussian_lastvalue;
1761 float gsl_ran_gaussian_lastvalue_set;
1762 float gsl_ran_gaussian(float sigma)
1765 if(gsl_ran_gaussian_lastvalue_set)
1767 gsl_ran_gaussian_lastvalue_set = 0;
1768 return sigma * gsl_ran_gaussian_lastvalue;
1772 a = random() * 2 * M_PI;
1773 b = sqrt(-2 * log(random()));
1774 gsl_ran_gaussian_lastvalue = cos(a) * b;
1775 gsl_ran_gaussian_lastvalue_set = 1;
1776 return sigma * sin(a) * b;
1780 string car(string s)
1783 o = strstrofs(s, " ", 0);
1786 return substring(s, 0, o);
1788 string cdr(string s)
1791 o = strstrofs(s, " ", 0);
1794 return substring(s, o + 1, strlen(s) - (o + 1));
1796 float matchacl(string acl, string str)
1803 t = car(acl); acl = cdr(acl);
1805 if(substring(t, 0, 1) == "-")
1808 t = substring(t, 1, strlen(t) - 1);
1810 else if(substring(t, 0, 1) == "+")
1811 t = substring(t, 1, strlen(t) - 1);
1812 if(substring(t, -1, 1) == "*")
1814 t = substring(t, 0, strlen(t) - 1);
1815 s = substring(s, 0, strlen(t));
1827 float startsWith(string haystack, string needle)
1829 return substring(haystack, 0, strlen(needle)) == needle;
1831 float startsWithNocase(string haystack, string needle)
1833 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1836 string get_model_datafilename(string m, float sk, string fil)
1841 m = "models/player/*_";
1843 m = strcat(m, ftos(sk));
1846 return strcat(m, ".", fil);
1849 float get_model_parameters(string m, float sk)
1854 get_model_parameters_modelname = string_null;
1855 get_model_parameters_modelskin = -1;
1856 get_model_parameters_name = string_null;
1857 get_model_parameters_species = -1;
1858 get_model_parameters_sex = string_null;
1859 get_model_parameters_weight = -1;
1860 get_model_parameters_age = -1;
1861 get_model_parameters_desc = string_null;
1867 if(substring(m, -4, -1) != ".txt")
1869 if(substring(m, -6, 1) != "_")
1871 sk = stof(substring(m, -5, 1));
1872 m = substring(m, 0, -7);
1875 fn = get_model_datafilename(m, sk, "txt");
1876 fh = fopen(fn, FILE_READ);
1880 get_model_parameters_modelname = m;
1881 get_model_parameters_modelskin = sk;
1882 while((s = fgets(fh)))
1885 break; // next lines will be description
1889 get_model_parameters_name = s;
1893 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1894 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1895 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1896 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1897 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1898 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1899 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1902 get_model_parameters_sex = s;
1904 get_model_parameters_weight = stof(s);
1906 get_model_parameters_age = stof(s);
1909 while((s = fgets(fh)))
1911 if(get_model_parameters_desc)
1912 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1914 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1922 vector vec2(vector v)
1929 vector NearestPointOnBox(entity box, vector org)
1931 vector m1, m2, nearest;
1933 m1 = box.mins + box.origin;
1934 m2 = box.maxs + box.origin;
1936 nearest_x = bound(m1_x, org_x, m2_x);
1937 nearest_y = bound(m1_y, org_y, m2_y);
1938 nearest_z = bound(m1_z, org_z, m2_z);
1944 float vercmp_recursive(string v1, string v2)
1950 dot1 = strstrofs(v1, ".", 0);
1951 dot2 = strstrofs(v2, ".", 0);
1955 s1 = substring(v1, 0, dot1);
1959 s2 = substring(v2, 0, dot2);
1961 r = stof(s1) - stof(s2);
1965 r = strcasecmp(s1, s2);
1978 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1981 float vercmp(string v1, string v2)
1983 if(strcasecmp(v1, v2) == 0) // early out check
1992 return vercmp_recursive(v1, v2);