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";
442 else if (g == GAME_KEEPAWAY) return "ka";
446 string mmsss(float tenths)
450 tenths = floor(tenths + 0.5);
451 minutes = floor(tenths / 600);
452 tenths -= minutes * 600;
453 s = ftos(1000 + tenths);
454 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
457 string mmssss(float hundredths)
461 hundredths = floor(hundredths + 0.5);
462 minutes = floor(hundredths / 6000);
463 hundredths -= minutes * 6000;
464 s = ftos(10000 + hundredths);
465 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
468 string ScoreString(float pFlags, float pValue)
473 pValue = floor(pValue + 0.5); // round
475 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
477 else if(pFlags & SFL_RANK)
479 valstr = ftos(pValue);
481 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
482 valstr = strcat(valstr, "th");
483 else if(substring(valstr, l - 1, 1) == "1")
484 valstr = strcat(valstr, "st");
485 else if(substring(valstr, l - 1, 1) == "2")
486 valstr = strcat(valstr, "nd");
487 else if(substring(valstr, l - 1, 1) == "3")
488 valstr = strcat(valstr, "rd");
490 valstr = strcat(valstr, "th");
492 else if(pFlags & SFL_TIME)
493 valstr = TIME_ENCODED_TOSTRING(pValue);
495 valstr = ftos(pValue);
500 vector cross(vector a, vector b)
503 '1 0 0' * (a_y * b_z - a_z * b_y)
504 + '0 1 0' * (a_z * b_x - a_x * b_z)
505 + '0 0 1' * (a_x * b_y - a_y * b_x);
508 // compressed vector format:
509 // like MD3, just even shorter
510 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
511 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
512 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
513 // length = 2^(length_encoded/8) / 8
514 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
515 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
516 // the special value 0 indicates the zero vector
518 float lengthLogTable[128];
520 float invertLengthLog(float x)
522 float l, r, m, lerr, rerr;
524 if(x >= lengthLogTable[127])
526 if(x <= lengthLogTable[0])
534 m = floor((l + r) / 2);
535 if(lengthLogTable[m] < x)
541 // now: r is >=, l is <
542 lerr = (x - lengthLogTable[l]);
543 rerr = (lengthLogTable[r] - x);
549 vector decompressShortVector(float data)
552 float pitch, yaw, len;
555 pitch = (data & 0xF000) / 0x1000;
556 yaw = (data & 0x0F80) / 0x80;
557 len = (data & 0x007F);
559 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
572 yaw = .19634954084936207740 * yaw;
573 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
574 out_x = cos(yaw) * cos(pitch);
575 out_y = sin(yaw) * cos(pitch);
579 //print("decompressed: ", vtos(out), "\n");
581 return out * lengthLogTable[len];
584 float compressShortVector(vector vec)
587 float pitch, yaw, len;
590 //print("compress: ", vtos(vec), "\n");
591 ang = vectoangles(vec);
595 if(ang_x < -90 && ang_x > +90)
596 error("BOGUS vectoangles");
597 //print("angles: ", vtos(ang), "\n");
599 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
608 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
609 len = invertLengthLog(vlen(vec));
611 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
613 return (pitch * 0x1000) + (yaw * 0x80) + len;
616 void compressShortVector_init()
621 for(i = 0; i < 128; ++i)
623 lengthLogTable[i] = l;
627 if(cvar("developer"))
629 print("Verifying vector compression table...\n");
630 for(i = 0x0F00; i < 0xFFFF; ++i)
631 if(i != compressShortVector(decompressShortVector(i)))
633 print("BROKEN vector compression: ", ftos(i));
634 print(" -> ", vtos(decompressShortVector(i)));
635 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
644 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
646 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
647 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
648 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
649 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
650 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
651 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
652 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
653 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
654 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
655 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
656 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
657 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
662 string fixPriorityList(string order, float from, float to, float subtract, float complete)
667 n = tokenize_console(order);
669 for(i = 0; i < n; ++i)
674 if(w >= from && w <= to)
675 neworder = strcat(neworder, ftos(w), " ");
679 if(w >= from && w <= to)
680 neworder = strcat(neworder, ftos(w), " ");
687 n = tokenize_console(neworder);
688 for(w = to; w >= from; --w)
690 for(i = 0; i < n; ++i)
691 if(stof(argv(i)) == w)
693 if(i == n) // not found
694 neworder = strcat(neworder, ftos(w), " ");
698 return substring(neworder, 0, strlen(neworder) - 1);
701 string mapPriorityList(string order, string(string) mapfunc)
706 n = tokenize_console(order);
708 for(i = 0; i < n; ++i)
709 neworder = strcat(neworder, mapfunc(argv(i)), " ");
711 return substring(neworder, 0, strlen(neworder) - 1);
714 string swapInPriorityList(string order, float i, float j)
719 n = tokenize_console(order);
721 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
724 for(w = 0; w < n; ++w)
727 s = strcat(s, argv(j), " ");
729 s = strcat(s, argv(i), " ");
731 s = strcat(s, argv(w), " ");
733 return substring(s, 0, strlen(s) - 1);
739 float cvar_value_issafe(string s)
741 if(strstrofs(s, "\"", 0) >= 0)
743 if(strstrofs(s, "\\", 0) >= 0)
745 if(strstrofs(s, ";", 0) >= 0)
747 if(strstrofs(s, "$", 0) >= 0)
749 if(strstrofs(s, "\r", 0) >= 0)
751 if(strstrofs(s, "\n", 0) >= 0)
757 void get_mi_min_max(float mode)
762 strunzone(mi_shortname);
763 mi_shortname = mapname;
764 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
765 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
766 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
767 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
768 mi_shortname = strzone(mi_shortname);
780 MapInfo_Get_ByName(mi_shortname, 0, 0);
781 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
783 mi_min = MapInfo_Map_mins;
784 mi_max = MapInfo_Map_maxs;
792 tracebox('1 0 0' * mi_x,
793 '0 1 0' * mi_y + '0 0 1' * mi_z,
794 '0 1 0' * ma_y + '0 0 1' * ma_z,
798 if(!trace_startsolid)
799 mi_min_x = trace_endpos_x;
801 tracebox('0 1 0' * mi_y,
802 '1 0 0' * mi_x + '0 0 1' * mi_z,
803 '1 0 0' * ma_x + '0 0 1' * ma_z,
807 if(!trace_startsolid)
808 mi_min_y = trace_endpos_y;
810 tracebox('0 0 1' * mi_z,
811 '1 0 0' * mi_x + '0 1 0' * mi_y,
812 '1 0 0' * ma_x + '0 1 0' * ma_y,
816 if(!trace_startsolid)
817 mi_min_z = trace_endpos_z;
819 tracebox('1 0 0' * ma_x,
820 '0 1 0' * mi_y + '0 0 1' * mi_z,
821 '0 1 0' * ma_y + '0 0 1' * ma_z,
825 if(!trace_startsolid)
826 mi_max_x = trace_endpos_x;
828 tracebox('0 1 0' * ma_y,
829 '1 0 0' * mi_x + '0 0 1' * mi_z,
830 '1 0 0' * ma_x + '0 0 1' * ma_z,
834 if(!trace_startsolid)
835 mi_max_y = trace_endpos_y;
837 tracebox('0 0 1' * ma_z,
838 '1 0 0' * mi_x + '0 1 0' * mi_y,
839 '1 0 0' * ma_x + '0 1 0' * ma_y,
843 if(!trace_startsolid)
844 mi_max_z = trace_endpos_z;
849 void get_mi_min_max_texcoords(float mode)
853 get_mi_min_max(mode);
858 // extend mi_picmax to get a square aspect ratio
859 // center the map in that area
860 extend = mi_picmax - mi_picmin;
861 if(extend_y > extend_x)
863 mi_picmin_x -= (extend_y - extend_x) * 0.5;
864 mi_picmax_x += (extend_y - extend_x) * 0.5;
868 mi_picmin_y -= (extend_x - extend_y) * 0.5;
869 mi_picmax_y += (extend_x - extend_y) * 0.5;
872 // add another some percent
873 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
877 // calculate the texcoords
878 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
879 // first the two corners of the origin
880 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
881 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
882 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
883 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
884 // then the other corners
885 mi_pictexcoord1_x = mi_pictexcoord0_x;
886 mi_pictexcoord1_y = mi_pictexcoord2_y;
887 mi_pictexcoord3_x = mi_pictexcoord2_x;
888 mi_pictexcoord3_y = mi_pictexcoord0_y;
893 void cvar_settemp(string pKey, string pValue)
895 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
897 void cvar_settemp_restore()
899 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
902 void cvar_settemp(string pKey, string pValue)
906 if(cvar_string(pKey) == pValue)
908 i = cvar("settemp_idx");
909 cvar_set("settemp_idx", ftos(i+1));
910 settemp_var = strcat("_settemp_x", ftos(i));
912 registercvar(settemp_var, "", 0);
914 registercvar(settemp_var, "");
916 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
917 cvar_set(settemp_var, cvar_string(pKey));
918 cvar_set(pKey, pValue);
921 void cvar_settemp_restore()
923 // undo what cvar_settemp did
925 n = tokenize_console(cvar_string("settemp_list"));
926 for(i = 0; i < n - 3; i += 3)
927 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
928 cvar_set("settemp_list", "0");
932 float almost_equals(float a, float b)
935 eps = (max(a, -a) + max(b, -b)) * 0.001;
936 if(a - b < eps && b - a < eps)
941 float almost_in_bounds(float a, float b, float c)
944 eps = (max(a, -a) + max(c, -c)) * 0.001;
945 return b == median(a - eps, b, c + eps);
948 float power2of(float e)
952 float log2of(float x)
954 // NOTE: generated code
1027 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1031 else if(ma == rgb_x)
1034 return (rgb_y - rgb_z) / (ma - mi);
1036 return (rgb_y - rgb_z) / (ma - mi) + 6;
1038 else if(ma == rgb_y)
1039 return (rgb_z - rgb_x) / (ma - mi) + 2;
1040 else // if(ma == rgb_z)
1041 return (rgb_x - rgb_y) / (ma - mi) + 4;
1044 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1048 hue -= 6 * floor(hue / 6);
1050 //else if(ma == rgb_x)
1051 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1055 rgb_y = hue * (ma - mi) + mi;
1058 //else if(ma == rgb_y)
1059 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1062 rgb_x = (2 - hue) * (ma - mi) + mi;
1070 rgb_z = (hue - 2) * (ma - mi) + mi;
1072 //else // if(ma == rgb_z)
1073 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1077 rgb_y = (4 - hue) * (ma - mi) + mi;
1082 rgb_x = (hue - 4) * (ma - mi) + mi;
1086 //else if(ma == rgb_x)
1087 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1088 else // if(hue <= 6)
1092 rgb_z = (6 - hue) * (ma - mi) + mi;
1098 vector rgb_to_hsv(vector rgb)
1103 mi = min3(rgb_x, rgb_y, rgb_z);
1104 ma = max3(rgb_x, rgb_y, rgb_z);
1106 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1117 vector hsv_to_rgb(vector hsv)
1119 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1122 vector rgb_to_hsl(vector rgb)
1127 mi = min3(rgb_x, rgb_y, rgb_z);
1128 ma = max3(rgb_x, rgb_y, rgb_z);
1130 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1132 hsl_z = 0.5 * (mi + ma);
1135 else if(hsl_z <= 0.5)
1136 hsl_y = (ma - mi) / (2*hsl_z);
1137 else // if(hsl_z > 0.5)
1138 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1143 vector hsl_to_rgb(vector hsl)
1145 float mi, ma, maminusmi;
1148 maminusmi = hsl_y * 2 * hsl_z;
1150 maminusmi = hsl_y * (2 - 2 * hsl_z);
1152 // hsl_z = 0.5 * mi + 0.5 * ma
1153 // maminusmi = - mi + ma
1154 mi = hsl_z - 0.5 * maminusmi;
1155 ma = hsl_z + 0.5 * maminusmi;
1157 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1160 string rgb_to_hexcolor(vector rgb)
1165 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1166 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1167 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1171 // requires that m2>m1 in all coordinates, and that m4>m3
1172 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;};
1174 // requires the same, but is a stronger condition
1175 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;};
1180 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1182 float ICanHasKallerz;
1184 // detect color codes support in the width function
1185 ICanHasKallerz = (w("^7", theSize) == 0);
1188 // The following function is SLOW.
1189 // For your safety and for the protection of those around you...
1190 // DO NOT CALL THIS AT HOME.
1191 // No really, don't.
1192 if(w(theText, theSize) <= maxWidth)
1193 return strlen(theText); // yeah!
1195 // binary search for right place to cut string
1197 float left, right, middle; // this always works
1199 right = strlen(theText); // this always fails
1202 middle = floor((left + right) / 2);
1203 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1208 while(left < right - 1);
1212 // NOTE: when color codes are involved, this binary search is,
1213 // mathematically, BROKEN. However, it is obviously guaranteed to
1214 // terminate, as the range still halves each time - but nevertheless, it is
1215 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1216 // range, and "right" is outside).
1218 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1219 // and decrease left on the basis of the chars detected of the truncated tag
1220 // Even if the ^xrgb tag is not complete/correct, left is decreased
1221 // (sometimes too much but with a correct result)
1222 // it fixes also ^[0-9]
1223 while(left >= 1 && substring(theText, left-1, 1) == "^")
1226 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1228 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1230 ch = str2chr(theText, left-1);
1231 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1234 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1236 ch = str2chr(theText, left-2);
1237 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1239 ch = str2chr(theText, left-1);
1240 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1249 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1251 float ICanHasKallerz;
1253 // detect color codes support in the width function
1254 ICanHasKallerz = (w("^7") == 0);
1257 // The following function is SLOW.
1258 // For your safety and for the protection of those around you...
1259 // DO NOT CALL THIS AT HOME.
1260 // No really, don't.
1261 if(w(theText) <= maxWidth)
1262 return strlen(theText); // yeah!
1264 // binary search for right place to cut string
1266 float left, right, middle; // this always works
1268 right = strlen(theText); // this always fails
1271 middle = floor((left + right) / 2);
1272 if(w(substring(theText, 0, middle)) <= maxWidth)
1277 while(left < right - 1);
1281 // NOTE: when color codes are involved, this binary search is,
1282 // mathematically, BROKEN. However, it is obviously guaranteed to
1283 // terminate, as the range still halves each time - but nevertheless, it is
1284 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1285 // range, and "right" is outside).
1287 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1288 // and decrease left on the basis of the chars detected of the truncated tag
1289 // Even if the ^xrgb tag is not complete/correct, left is decreased
1290 // (sometimes too much but with a correct result)
1291 // it fixes also ^[0-9]
1292 while(left >= 1 && substring(theText, left-1, 1) == "^")
1295 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1297 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1299 ch = str2chr(theText, left-1);
1300 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1303 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1305 ch = str2chr(theText, left-2);
1306 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1308 ch = str2chr(theText, left-1);
1309 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1318 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1324 s = getWrappedLine_remaining;
1326 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1327 if(cantake > 0 && cantake < strlen(s))
1330 while(take > 0 && substring(s, take, 1) != " ")
1334 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1335 if(getWrappedLine_remaining == "")
1336 getWrappedLine_remaining = string_null;
1337 return substring(s, 0, cantake);
1341 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1342 if(getWrappedLine_remaining == "")
1343 getWrappedLine_remaining = string_null;
1344 return substring(s, 0, take);
1349 getWrappedLine_remaining = string_null;
1354 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1360 s = getWrappedLine_remaining;
1362 cantake = textLengthUpToLength(s, w, tw);
1363 if(cantake > 0 && cantake < strlen(s))
1366 while(take > 0 && substring(s, take, 1) != " ")
1370 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1371 if(getWrappedLine_remaining == "")
1372 getWrappedLine_remaining = string_null;
1373 return substring(s, 0, cantake);
1377 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1378 if(getWrappedLine_remaining == "")
1379 getWrappedLine_remaining = string_null;
1380 return substring(s, 0, take);
1385 getWrappedLine_remaining = string_null;
1390 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1392 if(tw(theText, theFontSize) <= maxWidth)
1395 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1398 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1400 if(tw(theText) <= maxWidth)
1403 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1406 float isGametypeInFilter(float gt, float tp, string pattern)
1408 string subpattern, subpattern2, subpattern3;
1409 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1411 subpattern2 = ",teams,";
1413 subpattern2 = ",noteams,";
1414 if(gt == GAME_RACE || gt == GAME_CTS)
1415 subpattern3 = ",race,";
1417 subpattern3 = string_null;
1419 if(substring(pattern, 0, 1) == "-")
1421 pattern = substring(pattern, 1, strlen(pattern) - 1);
1422 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1424 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1426 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1431 if(substring(pattern, 0, 1) == "+")
1432 pattern = substring(pattern, 1, strlen(pattern) - 1);
1433 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1434 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1435 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1441 void shuffle(float n, swapfunc_t swap, entity pass)
1444 for(i = 1; i < n; ++i)
1446 // swap i-th item at a random position from 0 to i
1447 // proof for even distribution:
1450 // item n+1 gets at any position with chance 1/(n+1)
1451 // all others will get their 1/n chance reduced by factor n/(n+1)
1452 // to be on place n+1, their chance will be 1/(n+1)
1453 // 1/n * n/(n+1) = 1/(n+1)
1455 j = floor(random() * (i + 1));
1461 string substring_range(string s, float b, float e)
1463 return substring(s, b, e - b);
1466 string swapwords(string str, float i, float j)
1469 string s1, s2, s3, s4, s5;
1470 float si, ei, sj, ej, s0, en;
1471 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1472 si = argv_start_index(i);
1473 sj = argv_start_index(j);
1474 ei = argv_end_index(i);
1475 ej = argv_end_index(j);
1476 s0 = argv_start_index(0);
1477 en = argv_end_index(n-1);
1478 s1 = substring_range(str, s0, si);
1479 s2 = substring_range(str, si, ei);
1480 s3 = substring_range(str, ei, sj);
1481 s4 = substring_range(str, sj, ej);
1482 s5 = substring_range(str, ej, en);
1483 return strcat(s1, s4, s3, s2, s5);
1486 string _shufflewords_str;
1487 void _shufflewords_swapfunc(float i, float j, entity pass)
1489 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1491 string shufflewords(string str)
1494 _shufflewords_str = str;
1495 n = tokenizebyseparator(str, " ");
1496 shuffle(n, _shufflewords_swapfunc, world);
1497 str = _shufflewords_str;
1498 _shufflewords_str = string_null;
1502 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1518 // actually, every number solves the equation!
1529 if(a > 0) // put the smaller solution first
1531 v_x = ((-b)-D) / (2*a);
1532 v_y = ((-b)+D) / (2*a);
1536 v_x = (-b+D) / (2*a);
1537 v_y = (-b-D) / (2*a);
1543 // complex solutions!
1557 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1558 float _unacceptable_compiler_bug_1_b() { return 1; }
1559 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1560 float _unacceptable_compiler_bug_1_d() { return 1; }
1562 void check_unacceptable_compiler_bugs()
1564 if(cvar("_allow_unacceptable_compiler_bugs"))
1566 tokenize_console("foo bar");
1567 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1568 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.");
1571 float compressShotOrigin(vector v)
1575 y = rint(v_y * 4) + 128;
1576 z = rint(v_z * 4) + 128;
1577 if(x > 255 || x < 0)
1579 print("shot origin ", vtos(v), " x out of bounds\n");
1580 x = bound(0, x, 255);
1582 if(y > 255 || y < 0)
1584 print("shot origin ", vtos(v), " y out of bounds\n");
1585 y = bound(0, y, 255);
1587 if(z > 255 || z < 0)
1589 print("shot origin ", vtos(v), " z out of bounds\n");
1590 z = bound(0, z, 255);
1592 return x * 0x10000 + y * 0x100 + z;
1594 vector decompressShotOrigin(float f)
1597 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1598 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1599 v_z = ((f & 0xFF) - 128) / 4;
1603 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1605 float start, end, root, child;
1608 start = floor((n - 2) / 2);
1611 // siftdown(start, count-1);
1613 while(root * 2 + 1 <= n-1)
1615 child = root * 2 + 1;
1617 if(cmp(child, child+1, pass) < 0)
1619 if(cmp(root, child, pass) < 0)
1621 swap(root, child, pass);
1637 // siftdown(0, end);
1639 while(root * 2 + 1 <= end)
1641 child = root * 2 + 1;
1642 if(child < end && cmp(child, child+1, pass) < 0)
1644 if(cmp(root, child, pass) < 0)
1646 swap(root, child, pass);
1656 void RandomSelection_Init()
1658 RandomSelection_totalweight = 0;
1659 RandomSelection_chosen_ent = world;
1660 RandomSelection_chosen_float = 0;
1661 RandomSelection_chosen_string = string_null;
1662 RandomSelection_best_priority = -1;
1664 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1666 if(priority > RandomSelection_best_priority)
1668 RandomSelection_best_priority = priority;
1669 RandomSelection_chosen_ent = e;
1670 RandomSelection_chosen_float = f;
1671 RandomSelection_chosen_string = s;
1672 RandomSelection_totalweight = weight;
1674 else if(priority == RandomSelection_best_priority)
1676 RandomSelection_totalweight += weight;
1677 if(random() * RandomSelection_totalweight <= weight)
1679 RandomSelection_chosen_ent = e;
1680 RandomSelection_chosen_float = f;
1681 RandomSelection_chosen_string = s;
1686 vector healtharmor_maxdamage(float h, float a, float armorblock)
1688 // NOTE: we'll always choose the SMALLER value...
1689 float healthdamage, armordamage, armorideal;
1691 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1692 armordamage = a + (h - 1); // damage we can take if we could use more armor
1693 armorideal = healthdamage * armorblock;
1695 if(armordamage < healthdamage)
1708 vector healtharmor_applydamage(float a, float armorblock, float damage)
1711 v_y = bound(0, damage * armorblock, a); // save
1712 v_x = bound(0, damage - v_y, damage); // take
1717 string getcurrentmod()
1721 m = cvar_string("fs_gamedir");
1722 n = tokenize_console(m);
1734 v = ReadShort() * 256; // note: this is signed
1735 v += ReadByte(); // note: this is unsigned
1739 void WriteInt24_t(float dest, float val)
1742 WriteShort(dest, (v = floor(val / 256)));
1743 WriteByte(dest, val - v * 256); // 0..255
1748 float float2range11(float f)
1750 // continuous function mapping all reals into -1..1
1751 return f / (fabs(f) + 1);
1754 float float2range01(float f)
1756 // continuous function mapping all reals into 0..1
1757 return 0.5 + 0.5 * float2range11(f);
1760 // from the GNU Scientific Library
1761 float gsl_ran_gaussian_lastvalue;
1762 float gsl_ran_gaussian_lastvalue_set;
1763 float gsl_ran_gaussian(float sigma)
1766 if(gsl_ran_gaussian_lastvalue_set)
1768 gsl_ran_gaussian_lastvalue_set = 0;
1769 return sigma * gsl_ran_gaussian_lastvalue;
1773 a = random() * 2 * M_PI;
1774 b = sqrt(-2 * log(random()));
1775 gsl_ran_gaussian_lastvalue = cos(a) * b;
1776 gsl_ran_gaussian_lastvalue_set = 1;
1777 return sigma * sin(a) * b;
1781 string car(string s)
1784 o = strstrofs(s, " ", 0);
1787 return substring(s, 0, o);
1789 string cdr(string s)
1792 o = strstrofs(s, " ", 0);
1795 return substring(s, o + 1, strlen(s) - (o + 1));
1797 float matchacl(string acl, string str)
1804 t = car(acl); acl = cdr(acl);
1806 if(substring(t, 0, 1) == "-")
1809 t = substring(t, 1, strlen(t) - 1);
1811 else if(substring(t, 0, 1) == "+")
1812 t = substring(t, 1, strlen(t) - 1);
1813 if(substring(t, -1, 1) == "*")
1815 t = substring(t, 0, strlen(t) - 1);
1816 s = substring(s, 0, strlen(t));
1828 float startsWith(string haystack, string needle)
1830 return substring(haystack, 0, strlen(needle)) == needle;
1832 float startsWithNocase(string haystack, string needle)
1834 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1837 string get_model_datafilename(string m, float sk, string fil)
1842 m = "models/player/*_";
1844 m = strcat(m, ftos(sk));
1847 return strcat(m, ".", fil);
1850 float get_model_parameters(string m, float sk)
1855 get_model_parameters_modelname = string_null;
1856 get_model_parameters_modelskin = -1;
1857 get_model_parameters_name = string_null;
1858 get_model_parameters_species = -1;
1859 get_model_parameters_sex = string_null;
1860 get_model_parameters_weight = -1;
1861 get_model_parameters_age = -1;
1862 get_model_parameters_desc = string_null;
1868 if(substring(m, -4, -1) != ".txt")
1870 if(substring(m, -6, 1) != "_")
1872 sk = stof(substring(m, -5, 1));
1873 m = substring(m, 0, -7);
1876 fn = get_model_datafilename(m, sk, "txt");
1877 fh = fopen(fn, FILE_READ);
1881 get_model_parameters_modelname = m;
1882 get_model_parameters_modelskin = sk;
1883 while((s = fgets(fh)))
1886 break; // next lines will be description
1890 get_model_parameters_name = s;
1894 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1895 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1896 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1897 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1898 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1899 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1900 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1903 get_model_parameters_sex = s;
1905 get_model_parameters_weight = stof(s);
1907 get_model_parameters_age = stof(s);
1910 while((s = fgets(fh)))
1912 if(get_model_parameters_desc)
1913 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1915 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1923 vector vec2(vector v)
1930 vector NearestPointOnBox(entity box, vector org)
1932 vector m1, m2, nearest;
1934 m1 = box.mins + box.origin;
1935 m2 = box.maxs + box.origin;
1937 nearest_x = bound(m1_x, org_x, m2_x);
1938 nearest_y = bound(m1_y, org_y, m2_y);
1939 nearest_z = bound(m1_z, org_z, m2_z);
1945 float vercmp_recursive(string v1, string v2)
1951 dot1 = strstrofs(v1, ".", 0);
1952 dot2 = strstrofs(v2, ".", 0);
1956 s1 = substring(v1, 0, dot1);
1960 s2 = substring(v2, 0, dot2);
1962 r = stof(s1) - stof(s2);
1966 r = strcasecmp(s1, s2);
1979 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1982 float vercmp(string v1, string v2)
1984 if(strcasecmp(v1, v2) == 0) // early out check
1993 return vercmp_recursive(v1, v2);