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);
312 if(stof(fgets(fh)) == DB_BUCKETS)
315 while((l = fgets(fh)))
318 bufstr_set(db, i, l);
324 // different count of buckets?
325 // need to reorganize the database then (SLOW)
326 while((l = fgets(fh)))
328 n = tokenizebyseparator(l, "\\");
329 for(j = 2; j < n; j += 2)
330 db_put(db, argv(j-1), uri_unescape(argv(j)));
337 void db_dump(float db, string pFilename)
339 float fh, i, j, n, m;
340 fh = fopen(pFilename, FILE_WRITE);
342 error(strcat("Can't dump DB to ", pFilename));
345 for(i = 0; i < n; ++i)
347 m = tokenizebyseparator(bufstr_get(db, i), "\\");
348 for(j = 2; j < m; j += 2)
349 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
354 void db_close(float db)
359 string db_get(float db, string pKey)
362 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
363 return uri_unescape(infoget(bufstr_get(db, h), pKey));
366 void db_put(float db, string pKey, string pValue)
369 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
370 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
377 db = db_load("foo.db");
378 print("LOADED. FILL...\n");
379 for(i = 0; i < DB_BUCKETS; ++i)
380 db_put(db, ftos(random()), "X");
381 print("FILLED. SAVE...\n");
382 db_save(db, "foo.db");
383 print("SAVED. CLOSE...\n");
388 // Multiline text file buffers
389 float buf_load(string pFilename)
396 fh = fopen(pFilename, FILE_READ);
400 while((l = fgets(fh)))
402 bufstr_set(buf, i, l);
409 void buf_save(float buf, string pFilename)
412 fh = fopen(pFilename, FILE_WRITE);
414 error(strcat("Can't write buf to ", pFilename));
415 n = buf_getsize(buf);
416 for(i = 0; i < n; ++i)
417 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
421 string GametypeNameFromType(float g)
423 if (g == GAME_DEATHMATCH) return "dm";
424 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
425 else if (g == GAME_DOMINATION) return "dom";
426 else if (g == GAME_CTF) return "ctf";
427 else if (g == GAME_RUNEMATCH) return "rune";
428 else if (g == GAME_LMS) return "lms";
429 else if (g == GAME_ARENA) return "arena";
430 else if (g == GAME_CA) return "ca";
431 else if (g == GAME_KEYHUNT) return "kh";
432 else if (g == GAME_ONSLAUGHT) return "ons";
433 else if (g == GAME_ASSAULT) return "as";
434 else if (g == GAME_RACE) return "rc";
435 else if (g == GAME_NEXBALL) return "nexball";
436 else if (g == GAME_CTS) return "cts";
440 string mmsss(float tenths)
444 tenths = floor(tenths + 0.5);
445 minutes = floor(tenths / 600);
446 tenths -= minutes * 600;
447 s = ftos(1000 + tenths);
448 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
451 string mmssss(float hundredths)
455 hundredths = floor(hundredths + 0.5);
456 minutes = floor(hundredths / 6000);
457 hundredths -= minutes * 6000;
458 s = ftos(10000 + hundredths);
459 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
462 string ScoreString(float pFlags, float pValue)
467 pValue = floor(pValue + 0.5); // round
469 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
471 else if(pFlags & SFL_RANK)
473 valstr = ftos(pValue);
475 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
476 valstr = strcat(valstr, "th");
477 else if(substring(valstr, l - 1, 1) == "1")
478 valstr = strcat(valstr, "st");
479 else if(substring(valstr, l - 1, 1) == "2")
480 valstr = strcat(valstr, "nd");
481 else if(substring(valstr, l - 1, 1) == "3")
482 valstr = strcat(valstr, "rd");
484 valstr = strcat(valstr, "th");
486 else if(pFlags & SFL_TIME)
487 valstr = TIME_ENCODED_TOSTRING(pValue);
489 valstr = ftos(pValue);
494 vector cross(vector a, vector b)
497 '1 0 0' * (a_y * b_z - a_z * b_y)
498 + '0 1 0' * (a_z * b_x - a_x * b_z)
499 + '0 0 1' * (a_x * b_y - a_y * b_x);
502 // compressed vector format:
503 // like MD3, just even shorter
504 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
505 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
506 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
507 // length = 2^(length_encoded/8) / 8
508 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
509 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
510 // the special value 0 indicates the zero vector
512 float lengthLogTable[128];
514 float invertLengthLog(float x)
516 float l, r, m, lerr, rerr;
518 if(x >= lengthLogTable[127])
520 if(x <= lengthLogTable[0])
528 m = floor((l + r) / 2);
529 if(lengthLogTable[m] < x)
535 // now: r is >=, l is <
536 lerr = (x - lengthLogTable[l]);
537 rerr = (lengthLogTable[r] - x);
543 vector decompressShortVector(float data)
546 float pitch, yaw, len;
549 pitch = (data & 0xF000) / 0x1000;
550 yaw = (data & 0x0F80) / 0x80;
551 len = (data & 0x007F);
553 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
566 yaw = .19634954084936207740 * yaw;
567 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
568 out_x = cos(yaw) * cos(pitch);
569 out_y = sin(yaw) * cos(pitch);
573 //print("decompressed: ", vtos(out), "\n");
575 return out * lengthLogTable[len];
578 float compressShortVector(vector vec)
581 float pitch, yaw, len;
584 //print("compress: ", vtos(vec), "\n");
585 ang = vectoangles(vec);
589 if(ang_x < -90 && ang_x > +90)
590 error("BOGUS vectoangles");
591 //print("angles: ", vtos(ang), "\n");
593 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
602 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
603 len = invertLengthLog(vlen(vec));
605 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
607 return (pitch * 0x1000) + (yaw * 0x80) + len;
610 void compressShortVector_init()
615 for(i = 0; i < 128; ++i)
617 lengthLogTable[i] = l;
621 if(cvar("developer"))
623 print("Verifying vector compression table...\n");
624 for(i = 0x0F00; i < 0xFFFF; ++i)
625 if(i != compressShortVector(decompressShortVector(i)))
627 print("BROKEN vector compression: ", ftos(i));
628 print(" -> ", vtos(decompressShortVector(i)));
629 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
638 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
640 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
641 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
642 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
643 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
644 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
645 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
646 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
647 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
648 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
649 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
650 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
651 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
656 string fixPriorityList(string order, float from, float to, float subtract, float complete)
661 n = tokenize_console(order);
663 for(i = 0; i < n; ++i)
668 if(w >= from && w <= to)
669 neworder = strcat(neworder, ftos(w), " ");
673 if(w >= from && w <= to)
674 neworder = strcat(neworder, ftos(w), " ");
681 n = tokenize_console(neworder);
682 for(w = to; w >= from; --w)
684 for(i = 0; i < n; ++i)
685 if(stof(argv(i)) == w)
687 if(i == n) // not found
688 neworder = strcat(neworder, ftos(w), " ");
692 return substring(neworder, 0, strlen(neworder) - 1);
695 string mapPriorityList(string order, string(string) mapfunc)
700 n = tokenize_console(order);
702 for(i = 0; i < n; ++i)
703 neworder = strcat(neworder, mapfunc(argv(i)), " ");
705 return substring(neworder, 0, strlen(neworder) - 1);
708 string swapInPriorityList(string order, float i, float j)
713 n = tokenize_console(order);
715 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
718 for(w = 0; w < n; ++w)
721 s = strcat(s, argv(j), " ");
723 s = strcat(s, argv(i), " ");
725 s = strcat(s, argv(w), " ");
727 return substring(s, 0, strlen(s) - 1);
733 float cvar_value_issafe(string s)
735 if(strstrofs(s, "\"", 0) >= 0)
737 if(strstrofs(s, "\\", 0) >= 0)
739 if(strstrofs(s, ";", 0) >= 0)
741 if(strstrofs(s, "$", 0) >= 0)
743 if(strstrofs(s, "\r", 0) >= 0)
745 if(strstrofs(s, "\n", 0) >= 0)
751 void get_mi_min_max(float mode)
756 strunzone(mi_shortname);
757 mi_shortname = mapname;
758 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
759 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
760 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
761 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
762 mi_shortname = strzone(mi_shortname);
774 MapInfo_Get_ByName(mi_shortname, 0, 0);
775 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
777 mi_min = MapInfo_Map_mins;
778 mi_max = MapInfo_Map_maxs;
786 tracebox('1 0 0' * mi_x,
787 '0 1 0' * mi_y + '0 0 1' * mi_z,
788 '0 1 0' * ma_y + '0 0 1' * ma_z,
792 if(!trace_startsolid)
793 mi_min_x = trace_endpos_x;
795 tracebox('0 1 0' * mi_y,
796 '1 0 0' * mi_x + '0 0 1' * mi_z,
797 '1 0 0' * ma_x + '0 0 1' * ma_z,
801 if(!trace_startsolid)
802 mi_min_y = trace_endpos_y;
804 tracebox('0 0 1' * mi_z,
805 '1 0 0' * mi_x + '0 1 0' * mi_y,
806 '1 0 0' * ma_x + '0 1 0' * ma_y,
810 if(!trace_startsolid)
811 mi_min_z = trace_endpos_z;
813 tracebox('1 0 0' * ma_x,
814 '0 1 0' * mi_y + '0 0 1' * mi_z,
815 '0 1 0' * ma_y + '0 0 1' * ma_z,
819 if(!trace_startsolid)
820 mi_max_x = trace_endpos_x;
822 tracebox('0 1 0' * ma_y,
823 '1 0 0' * mi_x + '0 0 1' * mi_z,
824 '1 0 0' * ma_x + '0 0 1' * ma_z,
828 if(!trace_startsolid)
829 mi_max_y = trace_endpos_y;
831 tracebox('0 0 1' * ma_z,
832 '1 0 0' * mi_x + '0 1 0' * mi_y,
833 '1 0 0' * ma_x + '0 1 0' * ma_y,
837 if(!trace_startsolid)
838 mi_max_z = trace_endpos_z;
843 void get_mi_min_max_texcoords(float mode)
847 get_mi_min_max(mode);
852 // extend mi_picmax to get a square aspect ratio
853 // center the map in that area
854 extend = mi_picmax - mi_picmin;
855 if(extend_y > extend_x)
857 mi_picmin_x -= (extend_y - extend_x) * 0.5;
858 mi_picmax_x += (extend_y - extend_x) * 0.5;
862 mi_picmin_y -= (extend_x - extend_y) * 0.5;
863 mi_picmax_y += (extend_x - extend_y) * 0.5;
866 // add another some percent
867 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
871 // calculate the texcoords
872 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
873 // first the two corners of the origin
874 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
875 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
876 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
877 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
878 // then the other corners
879 mi_pictexcoord1_x = mi_pictexcoord0_x;
880 mi_pictexcoord1_y = mi_pictexcoord2_y;
881 mi_pictexcoord3_x = mi_pictexcoord2_x;
882 mi_pictexcoord3_y = mi_pictexcoord0_y;
887 void cvar_settemp(string pKey, string pValue)
889 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
891 void cvar_settemp_restore()
893 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
896 void cvar_settemp(string pKey, string pValue)
900 if(cvar_string(pKey) == pValue)
902 i = cvar("settemp_idx");
903 cvar_set("settemp_idx", ftos(i+1));
904 settemp_var = strcat("_settemp_x", ftos(i));
906 registercvar(settemp_var, "", 0);
908 registercvar(settemp_var, "");
910 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
911 cvar_set(settemp_var, cvar_string(pKey));
912 cvar_set(pKey, pValue);
915 void cvar_settemp_restore()
917 // undo what cvar_settemp did
919 n = tokenize_console(cvar_string("settemp_list"));
920 for(i = 0; i < n - 3; i += 3)
921 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
922 cvar_set("settemp_list", "0");
926 float almost_equals(float a, float b)
929 eps = (max(a, -a) + max(b, -b)) * 0.001;
930 if(a - b < eps && b - a < eps)
935 float almost_in_bounds(float a, float b, float c)
938 eps = (max(a, -a) + max(c, -c)) * 0.001;
939 return b == median(a - eps, b, c + eps);
942 float power2of(float e)
946 float log2of(float x)
948 // NOTE: generated code
1021 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1025 else if(ma == rgb_x)
1028 return (rgb_y - rgb_z) / (ma - mi);
1030 return (rgb_y - rgb_z) / (ma - mi) + 6;
1032 else if(ma == rgb_y)
1033 return (rgb_z - rgb_x) / (ma - mi) + 2;
1034 else // if(ma == rgb_z)
1035 return (rgb_x - rgb_y) / (ma - mi) + 4;
1038 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1042 hue -= 6 * floor(hue / 6);
1044 //else if(ma == rgb_x)
1045 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1049 rgb_y = hue * (ma - mi) + mi;
1052 //else if(ma == rgb_y)
1053 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1056 rgb_x = (2 - hue) * (ma - mi) + mi;
1064 rgb_z = (hue - 2) * (ma - mi) + mi;
1066 //else // if(ma == rgb_z)
1067 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1071 rgb_y = (4 - hue) * (ma - mi) + mi;
1076 rgb_x = (hue - 4) * (ma - mi) + mi;
1080 //else if(ma == rgb_x)
1081 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1082 else // if(hue <= 6)
1086 rgb_z = (6 - hue) * (ma - mi) + mi;
1092 vector rgb_to_hsv(vector rgb)
1097 mi = min3(rgb_x, rgb_y, rgb_z);
1098 ma = max3(rgb_x, rgb_y, rgb_z);
1100 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1111 vector hsv_to_rgb(vector hsv)
1113 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1116 vector rgb_to_hsl(vector rgb)
1121 mi = min3(rgb_x, rgb_y, rgb_z);
1122 ma = max3(rgb_x, rgb_y, rgb_z);
1124 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1126 hsl_z = 0.5 * (mi + ma);
1129 else if(hsl_z <= 0.5)
1130 hsl_y = (ma - mi) / (2*hsl_z);
1131 else // if(hsl_z > 0.5)
1132 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1137 vector hsl_to_rgb(vector hsl)
1139 float mi, ma, maminusmi;
1142 maminusmi = hsl_y * 2 * hsl_z;
1144 maminusmi = hsl_y * (2 - 2 * hsl_z);
1146 // hsl_z = 0.5 * mi + 0.5 * ma
1147 // maminusmi = - mi + ma
1148 mi = hsl_z - 0.5 * maminusmi;
1149 ma = hsl_z + 0.5 * maminusmi;
1151 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1154 string rgb_to_hexcolor(vector rgb)
1159 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1160 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1161 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1165 // requires that m2>m1 in all coordinates, and that m4>m3
1166 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;};
1168 // requires the same, but is a stronger condition
1169 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;};
1174 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1176 float ICanHasKallerz;
1178 // detect color codes support in the width function
1179 ICanHasKallerz = (w("^7", theSize) == 0);
1182 // The following function is SLOW.
1183 // For your safety and for the protection of those around you...
1184 // DO NOT CALL THIS AT HOME.
1185 // No really, don't.
1186 if(w(theText, theSize) <= maxWidth)
1187 return strlen(theText); // yeah!
1189 // binary search for right place to cut string
1191 float left, right, middle; // this always works
1193 right = strlen(theText); // this always fails
1196 middle = floor((left + right) / 2);
1197 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1202 while(left < right - 1);
1206 // NOTE: when color codes are involved, this binary search is,
1207 // mathematically, BROKEN. However, it is obviously guaranteed to
1208 // terminate, as the range still halves each time - but nevertheless, it is
1209 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1210 // range, and "right" is outside).
1212 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1213 // and decrease left on the basis of the chars detected of the truncated tag
1214 // Even if the ^xrgb tag is not complete/correct, left is decreased
1215 // (sometimes too much but with a correct result)
1216 // it fixes also ^[0-9]
1217 while(left >= 1 && substring(theText, left-1, 1) == "^")
1220 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1222 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1224 ch = str2chr(theText, left-1);
1225 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1228 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1230 ch = str2chr(theText, left-2);
1231 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1233 ch = str2chr(theText, left-1);
1234 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1243 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1245 float ICanHasKallerz;
1247 // detect color codes support in the width function
1248 ICanHasKallerz = (w("^7") == 0);
1251 // The following function is SLOW.
1252 // For your safety and for the protection of those around you...
1253 // DO NOT CALL THIS AT HOME.
1254 // No really, don't.
1255 if(w(theText) <= maxWidth)
1256 return strlen(theText); // yeah!
1258 // binary search for right place to cut string
1260 float left, right, middle; // this always works
1262 right = strlen(theText); // this always fails
1265 middle = floor((left + right) / 2);
1266 if(w(substring(theText, 0, middle)) <= maxWidth)
1271 while(left < right - 1);
1275 // NOTE: when color codes are involved, this binary search is,
1276 // mathematically, BROKEN. However, it is obviously guaranteed to
1277 // terminate, as the range still halves each time - but nevertheless, it is
1278 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1279 // range, and "right" is outside).
1281 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1282 // and decrease left on the basis of the chars detected of the truncated tag
1283 // Even if the ^xrgb tag is not complete/correct, left is decreased
1284 // (sometimes too much but with a correct result)
1285 // it fixes also ^[0-9]
1286 while(left >= 1 && substring(theText, left-1, 1) == "^")
1289 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1291 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1293 ch = str2chr(theText, left-1);
1294 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1297 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1299 ch = str2chr(theText, left-2);
1300 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1302 ch = str2chr(theText, left-1);
1303 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1312 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1318 s = getWrappedLine_remaining;
1320 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1321 if(cantake > 0 && cantake < strlen(s))
1324 while(take > 0 && substring(s, take, 1) != " ")
1328 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1329 if(getWrappedLine_remaining == "")
1330 getWrappedLine_remaining = string_null;
1331 return substring(s, 0, cantake);
1335 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1336 if(getWrappedLine_remaining == "")
1337 getWrappedLine_remaining = string_null;
1338 return substring(s, 0, take);
1343 getWrappedLine_remaining = string_null;
1348 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1354 s = getWrappedLine_remaining;
1356 cantake = textLengthUpToLength(s, w, tw);
1357 if(cantake > 0 && cantake < strlen(s))
1360 while(take > 0 && substring(s, take, 1) != " ")
1364 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1365 if(getWrappedLine_remaining == "")
1366 getWrappedLine_remaining = string_null;
1367 return substring(s, 0, cantake);
1371 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1372 if(getWrappedLine_remaining == "")
1373 getWrappedLine_remaining = string_null;
1374 return substring(s, 0, take);
1379 getWrappedLine_remaining = string_null;
1384 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1386 if(tw(theText, theFontSize) <= maxWidth)
1389 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1392 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1394 if(tw(theText) <= maxWidth)
1397 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1400 float isGametypeInFilter(float gt, float tp, string pattern)
1402 string subpattern, subpattern2, subpattern3;
1403 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1405 subpattern2 = ",teams,";
1407 subpattern2 = ",noteams,";
1408 if(gt == GAME_RACE || gt == GAME_CTS)
1409 subpattern3 = ",race,";
1411 subpattern3 = string_null;
1413 if(substring(pattern, 0, 1) == "-")
1415 pattern = substring(pattern, 1, strlen(pattern) - 1);
1416 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1418 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1420 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1425 if(substring(pattern, 0, 1) == "+")
1426 pattern = substring(pattern, 1, strlen(pattern) - 1);
1427 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1428 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1429 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1435 void shuffle(float n, swapfunc_t swap, entity pass)
1438 for(i = 1; i < n; ++i)
1440 // swap i-th item at a random position from 0 to i
1441 // proof for even distribution:
1444 // item n+1 gets at any position with chance 1/(n+1)
1445 // all others will get their 1/n chance reduced by factor n/(n+1)
1446 // to be on place n+1, their chance will be 1/(n+1)
1447 // 1/n * n/(n+1) = 1/(n+1)
1449 j = floor(random() * (i + 1));
1455 string substring_range(string s, float b, float e)
1457 return substring(s, b, e - b);
1460 string swapwords(string str, float i, float j)
1463 string s1, s2, s3, s4, s5;
1464 float si, ei, sj, ej, s0, en;
1465 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1466 si = argv_start_index(i);
1467 sj = argv_start_index(j);
1468 ei = argv_end_index(i);
1469 ej = argv_end_index(j);
1470 s0 = argv_start_index(0);
1471 en = argv_end_index(n-1);
1472 s1 = substring_range(str, s0, si);
1473 s2 = substring_range(str, si, ei);
1474 s3 = substring_range(str, ei, sj);
1475 s4 = substring_range(str, sj, ej);
1476 s5 = substring_range(str, ej, en);
1477 return strcat(s1, s4, s3, s2, s5);
1480 string _shufflewords_str;
1481 void _shufflewords_swapfunc(float i, float j, entity pass)
1483 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1485 string shufflewords(string str)
1488 _shufflewords_str = str;
1489 n = tokenizebyseparator(str, " ");
1490 shuffle(n, _shufflewords_swapfunc, world);
1491 str = _shufflewords_str;
1492 _shufflewords_str = string_null;
1496 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1512 // actually, every number solves the equation!
1523 if(a > 0) // put the smaller solution first
1525 v_x = ((-b)-D) / (2*a);
1526 v_y = ((-b)+D) / (2*a);
1530 v_x = (-b+D) / (2*a);
1531 v_y = (-b-D) / (2*a);
1537 // complex solutions!
1551 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1552 float _unacceptable_compiler_bug_1_b() { return 1; }
1553 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1554 float _unacceptable_compiler_bug_1_d() { return 1; }
1556 void check_unacceptable_compiler_bugs()
1558 if(cvar("_allow_unacceptable_compiler_bugs"))
1560 tokenize_console("foo bar");
1561 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1562 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.");
1565 float compressShotOrigin(vector v)
1569 y = rint(v_y * 4) + 128;
1570 z = rint(v_z * 4) + 128;
1571 if(x > 255 || x < 0)
1573 print("shot origin ", vtos(v), " x out of bounds\n");
1574 x = bound(0, x, 255);
1576 if(y > 255 || y < 0)
1578 print("shot origin ", vtos(v), " y out of bounds\n");
1579 y = bound(0, y, 255);
1581 if(z > 255 || z < 0)
1583 print("shot origin ", vtos(v), " z out of bounds\n");
1584 z = bound(0, z, 255);
1586 return x * 0x10000 + y * 0x100 + z;
1588 vector decompressShotOrigin(float f)
1591 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1592 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1593 v_z = ((f & 0xFF) - 128) / 4;
1597 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1599 float start, end, root, child;
1602 start = floor((n - 2) / 2);
1605 // siftdown(start, count-1);
1607 while(root * 2 + 1 <= n-1)
1609 child = root * 2 + 1;
1611 if(cmp(child, child+1, pass) < 0)
1613 if(cmp(root, child, pass) < 0)
1615 swap(root, child, pass);
1631 // siftdown(0, end);
1633 while(root * 2 + 1 <= end)
1635 child = root * 2 + 1;
1636 if(child < end && cmp(child, child+1, pass) < 0)
1638 if(cmp(root, child, pass) < 0)
1640 swap(root, child, pass);
1650 void RandomSelection_Init()
1652 RandomSelection_totalweight = 0;
1653 RandomSelection_chosen_ent = world;
1654 RandomSelection_chosen_float = 0;
1655 RandomSelection_chosen_string = string_null;
1656 RandomSelection_best_priority = -1;
1658 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1660 if(priority > RandomSelection_best_priority)
1662 RandomSelection_best_priority = priority;
1663 RandomSelection_chosen_ent = e;
1664 RandomSelection_chosen_float = f;
1665 RandomSelection_chosen_string = s;
1666 RandomSelection_totalweight = weight;
1668 else if(priority == RandomSelection_best_priority)
1670 RandomSelection_totalweight += weight;
1671 if(random() * RandomSelection_totalweight <= weight)
1673 RandomSelection_chosen_ent = e;
1674 RandomSelection_chosen_float = f;
1675 RandomSelection_chosen_string = s;
1680 vector healtharmor_maxdamage(float h, float a, float armorblock)
1682 // NOTE: we'll always choose the SMALLER value...
1683 float healthdamage, armordamage, armorideal;
1685 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1686 armordamage = a + (h - 1); // damage we can take if we could use more armor
1687 armorideal = healthdamage * armorblock;
1689 if(armordamage < healthdamage)
1702 vector healtharmor_applydamage(float a, float armorblock, float damage)
1705 v_y = bound(0, damage * armorblock, a); // save
1706 v_x = bound(0, damage - v_y, damage); // take
1711 string getcurrentmod()
1715 m = cvar_string("fs_gamedir");
1716 n = tokenize_console(m);
1728 v = ReadShort() * 256; // note: this is signed
1729 v += ReadByte(); // note: this is unsigned
1733 void WriteInt24_t(float dest, float val)
1736 WriteShort(dest, (v = floor(val / 256)));
1737 WriteByte(dest, val - v * 256); // 0..255
1742 float float2range11(float f)
1744 // continuous function mapping all reals into -1..1
1745 return f / (fabs(f) + 1);
1748 float float2range01(float f)
1750 // continuous function mapping all reals into 0..1
1751 return 0.5 + 0.5 * float2range11(f);
1754 // from the GNU Scientific Library
1755 float gsl_ran_gaussian_lastvalue;
1756 float gsl_ran_gaussian_lastvalue_set;
1757 float gsl_ran_gaussian(float sigma)
1760 if(gsl_ran_gaussian_lastvalue_set)
1762 gsl_ran_gaussian_lastvalue_set = 0;
1763 return sigma * gsl_ran_gaussian_lastvalue;
1767 a = random() * 2 * M_PI;
1768 b = sqrt(-2 * log(random()));
1769 gsl_ran_gaussian_lastvalue = cos(a) * b;
1770 gsl_ran_gaussian_lastvalue_set = 1;
1771 return sigma * sin(a) * b;
1775 string car(string s)
1778 o = strstrofs(s, " ", 0);
1781 return substring(s, 0, o);
1783 string cdr(string s)
1786 o = strstrofs(s, " ", 0);
1789 return substring(s, o + 1, strlen(s) - (o + 1));
1791 float matchacl(string acl, string str)
1798 t = car(acl); acl = cdr(acl);
1800 if(substring(t, 0, 1) == "-")
1803 t = substring(t, 1, strlen(t) - 1);
1805 else if(substring(t, 0, 1) == "+")
1806 t = substring(t, 1, strlen(t) - 1);
1807 if(substring(t, -1, 1) == "*")
1809 t = substring(t, 0, strlen(t) - 1);
1810 s = substring(s, 0, strlen(t));
1822 float startsWith(string haystack, string needle)
1824 return substring(haystack, 0, strlen(needle)) == needle;
1826 float startsWithNocase(string haystack, string needle)
1828 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1831 string get_model_datafilename(string m, float sk, string fil)
1836 m = "models/player/*_";
1838 m = strcat(m, ftos(sk));
1841 return strcat(m, ".", fil);
1844 float get_model_parameters(string m, float sk)
1849 get_model_parameters_modelname = string_null;
1850 get_model_parameters_modelskin = -1;
1851 get_model_parameters_name = string_null;
1852 get_model_parameters_species = -1;
1853 get_model_parameters_sex = string_null;
1854 get_model_parameters_weight = -1;
1855 get_model_parameters_age = -1;
1856 get_model_parameters_desc = string_null;
1862 if(substring(m, -4, -1) != ".txt")
1864 if(substring(m, -6, 1) != "_")
1866 sk = stof(substring(m, -5, 1));
1867 m = substring(m, 0, -7);
1870 fn = get_model_datafilename(m, sk, "txt");
1871 fh = fopen(fn, FILE_READ);
1875 get_model_parameters_modelname = m;
1876 get_model_parameters_modelskin = sk;
1877 while((s = fgets(fh)))
1880 break; // next lines will be description
1884 get_model_parameters_name = s;
1888 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1889 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1890 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1891 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1892 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1893 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1894 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1897 get_model_parameters_sex = s;
1899 get_model_parameters_weight = stof(s);
1901 get_model_parameters_age = stof(s);
1904 while((s = fgets(fh)))
1906 if(get_model_parameters_desc)
1907 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1909 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1917 vector vec2(vector v)