1 string wordwrap_buffer;
3 void wordwrap_buffer_put(string s)
5 wordwrap_buffer = strcat(wordwrap_buffer, s);
8 string wordwrap(string s, float l)
12 wordwrap_cb(s, l, wordwrap_buffer_put);
20 void wordwrap_buffer_sprint(string s)
22 wordwrap_buffer = strcat(wordwrap_buffer, s);
25 sprint(self, wordwrap_buffer);
30 void wordwrap_sprint(string s, float l)
33 wordwrap_cb(s, l, wordwrap_buffer_sprint);
34 if(wordwrap_buffer != "")
35 sprint(self, strcat(wordwrap_buffer, "\n"));
42 string unescape(string in)
47 // but it doesn't seem to be necessary in my tests at least
52 for(i = 0; i < len; ++i)
54 s = substring(in, i, 1);
57 s = substring(in, i+1, 1);
59 str = strcat(str, "\n");
61 str = strcat(str, "\\");
63 str = strcat(str, substring(in, i, 2));
73 void wordwrap_cb(string s, float l, void(string) callback)
76 local float lleft, i, j, wlen;
80 for (i = 0;i < strlen(s);++i)
82 if (substring(s, i, 2) == "\\n")
88 else if (substring(s, i, 1) == "\n")
93 else if (substring(s, i, 1) == " ")
103 for (j = i+1;j < strlen(s);++j)
104 // ^^ this skips over the first character of a word, which
105 // is ALWAYS part of the word
106 // this is safe since if i+1 == strlen(s), i will become
107 // strlen(s)-1 at the end of this block and the function
108 // will terminate. A space can't be the first character we
109 // read here, and neither can a \n be the start, since these
110 // two cases have been handled above.
112 c = substring(s, j, 1);
119 // we need to keep this tempstring alive even if substring is
120 // called repeatedly, so call strcat even though we're not
130 callback(substring(s, i, wlen));
131 lleft = lleft - wlen;
138 float dist_point_line(vector p, vector l0, vector ldir)
140 ldir = normalize(ldir);
142 // remove the component in line direction
143 p = p - (p * ldir) * ldir;
145 // vlen of the remaining vector
149 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
178 float median(float a, float b, float c)
181 return bound(a, b, c);
182 return bound(c, b, a);
185 // converts a number to a string with the indicated number of decimals
186 // works for up to 10 decimals!
187 string ftos_decimals(float number, float decimals)
189 // we have sprintf...
190 return sprintf("%.*f", decimals, number);
194 vector colormapPaletteColor(float c, float isPants)
198 case 0: return '0.800000 0.800000 0.800000';
199 case 1: return '0.600000 0.400000 0.000000';
200 case 2: return '0.000000 1.000000 0.501961';
201 case 3: return '0.000000 1.000000 0.000000';
202 case 4: return '1.000000 0.000000 0.000000';
203 case 5: return '0.000000 0.658824 1.000000';
204 case 6: return '0.000000 1.000000 1.000000';
205 case 7: return '0.501961 1.000000 0.000000';
206 case 8: return '0.501961 0.000000 1.000000';
207 case 9: return '1.000000 0.000000 1.000000';
208 case 10: return '1.000000 0.000000 0.501961';
209 case 11: return '0.600000 0.600000 0.600000';
210 case 12: return '1.000000 1.000000 0.000000';
211 case 13: return '0.000000 0.313725 1.000000';
212 case 14: return '1.000000 0.501961 0.000000';
216 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
217 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
218 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
221 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
222 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
223 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
224 default: return '0.000 0.000 0.000';
228 // unzone the string, and return it as tempstring. Safe to be called on string_null
229 string fstrunzone(string s)
239 // Databases (hash tables)
240 #define DB_BUCKETS 8192
241 void db_save(float db, string pFilename)
244 fh = fopen(pFilename, FILE_WRITE);
247 print(strcat("^1Can't write DB to ", pFilename));
251 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
252 for(i = 0; i < n; ++i)
253 fputs(fh, strcat(bufstr_get(db, i), "\n"));
262 float db_load(string pFilename)
264 float db, fh, i, j, n;
269 fh = fopen(pFilename, FILE_READ);
273 if(stof(l) == DB_BUCKETS)
276 while((l = fgets(fh)))
279 bufstr_set(db, i, l);
285 // different count of buckets, or a dump?
286 // need to reorganize the database then (SLOW)
288 // note: we also parse the first line (l) in case the DB file is
289 // missing the bucket count
292 n = tokenizebyseparator(l, "\\");
293 for(j = 2; j < n; j += 2)
294 db_put(db, argv(j-1), uri_unescape(argv(j)));
296 while((l = fgets(fh)));
302 void db_dump(float db, string pFilename)
304 float fh, i, j, n, m;
305 fh = fopen(pFilename, FILE_WRITE);
307 error(strcat("Can't dump DB to ", pFilename));
310 for(i = 0; i < n; ++i)
312 m = tokenizebyseparator(bufstr_get(db, i), "\\");
313 for(j = 2; j < m; j += 2)
314 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
319 void db_close(float db)
324 string db_get(float db, string pKey)
327 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
328 return uri_unescape(infoget(bufstr_get(db, h), pKey));
331 void db_put(float db, string pKey, string pValue)
334 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
335 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
342 db = db_load("foo.db");
343 print("LOADED. FILL...\n");
344 for(i = 0; i < DB_BUCKETS; ++i)
345 db_put(db, ftos(random()), "X");
346 print("FILLED. SAVE...\n");
347 db_save(db, "foo.db");
348 print("SAVED. CLOSE...\n");
353 // Multiline text file buffers
354 float buf_load(string pFilename)
361 fh = fopen(pFilename, FILE_READ);
365 while((l = fgets(fh)))
367 bufstr_set(buf, i, l);
374 void buf_save(float buf, string pFilename)
377 fh = fopen(pFilename, FILE_WRITE);
379 error(strcat("Can't write buf to ", pFilename));
380 n = buf_getsize(buf);
381 for(i = 0; i < n; ++i)
382 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
386 string GametypeNameFromType(float g)
388 if (g == GAME_DEATHMATCH) return "dm";
389 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
390 else if (g == GAME_DOMINATION) return "dom";
391 else if (g == GAME_CTF) return "ctf";
392 else if (g == GAME_RUNEMATCH) return "rune";
393 else if (g == GAME_LMS) return "lms";
394 else if (g == GAME_ARENA) return "arena";
395 else if (g == GAME_CA) return "ca";
396 else if (g == GAME_KEYHUNT) return "kh";
397 else if (g == GAME_ONSLAUGHT) return "ons";
398 else if (g == GAME_ASSAULT) return "as";
399 else if (g == GAME_RACE) return "rc";
400 else if (g == GAME_NEXBALL) return "nexball";
401 else if (g == GAME_CTS) return "cts";
402 else if (g == GAME_FREEZETAG) return "freezetag";
406 string mmsss(float tenths)
410 tenths = floor(tenths + 0.5);
411 minutes = floor(tenths / 600);
412 tenths -= minutes * 600;
413 s = ftos(1000 + tenths);
414 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
417 string mmssss(float hundredths)
421 hundredths = floor(hundredths + 0.5);
422 minutes = floor(hundredths / 6000);
423 hundredths -= minutes * 6000;
424 s = ftos(10000 + hundredths);
425 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
428 string ScoreString(float pFlags, float pValue)
433 pValue = floor(pValue + 0.5); // round
435 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
437 else if(pFlags & SFL_RANK)
439 valstr = ftos(pValue);
441 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
442 valstr = strcat(valstr, "th");
443 else if(substring(valstr, l - 1, 1) == "1")
444 valstr = strcat(valstr, "st");
445 else if(substring(valstr, l - 1, 1) == "2")
446 valstr = strcat(valstr, "nd");
447 else if(substring(valstr, l - 1, 1) == "3")
448 valstr = strcat(valstr, "rd");
450 valstr = strcat(valstr, "th");
452 else if(pFlags & SFL_TIME)
453 valstr = TIME_ENCODED_TOSTRING(pValue);
455 valstr = ftos(pValue);
460 vector cross(vector a, vector b)
463 '1 0 0' * (a_y * b_z - a_z * b_y)
464 + '0 1 0' * (a_z * b_x - a_x * b_z)
465 + '0 0 1' * (a_x * b_y - a_y * b_x);
468 // compressed vector format:
469 // like MD3, just even shorter
470 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
471 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
472 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
473 // length = 2^(length_encoded/8) / 8
474 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
475 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
476 // the special value 0 indicates the zero vector
478 float lengthLogTable[128];
480 float invertLengthLog(float x)
482 float l, r, m, lerr, rerr;
484 if(x >= lengthLogTable[127])
486 if(x <= lengthLogTable[0])
494 m = floor((l + r) / 2);
495 if(lengthLogTable[m] < x)
501 // now: r is >=, l is <
502 lerr = (x - lengthLogTable[l]);
503 rerr = (lengthLogTable[r] - x);
509 vector decompressShortVector(float data)
512 float pitch, yaw, len;
515 pitch = (data & 0xF000) / 0x1000;
516 yaw = (data & 0x0F80) / 0x80;
517 len = (data & 0x007F);
519 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
532 yaw = .19634954084936207740 * yaw;
533 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
534 out_x = cos(yaw) * cos(pitch);
535 out_y = sin(yaw) * cos(pitch);
539 //print("decompressed: ", vtos(out), "\n");
541 return out * lengthLogTable[len];
544 float compressShortVector(vector vec)
547 float pitch, yaw, len;
550 //print("compress: ", vtos(vec), "\n");
551 ang = vectoangles(vec);
555 if(ang_x < -90 && ang_x > +90)
556 error("BOGUS vectoangles");
557 //print("angles: ", vtos(ang), "\n");
559 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
568 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
569 len = invertLengthLog(vlen(vec));
571 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
573 return (pitch * 0x1000) + (yaw * 0x80) + len;
576 void compressShortVector_init()
581 for(i = 0; i < 128; ++i)
583 lengthLogTable[i] = l;
587 if(cvar("developer"))
589 print("Verifying vector compression table...\n");
590 for(i = 0x0F00; i < 0xFFFF; ++i)
591 if(i != compressShortVector(decompressShortVector(i)))
593 print("BROKEN vector compression: ", ftos(i));
594 print(" -> ", vtos(decompressShortVector(i)));
595 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
604 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
606 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
607 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
608 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
609 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
610 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
611 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
612 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
622 string fixPriorityList(string order, float from, float to, float subtract, float complete)
627 n = tokenize_console(order);
629 for(i = 0; i < n; ++i)
634 if(w >= from && w <= to)
635 neworder = strcat(neworder, ftos(w), " ");
639 if(w >= from && w <= to)
640 neworder = strcat(neworder, ftos(w), " ");
647 n = tokenize_console(neworder);
648 for(w = to; w >= from; --w)
650 for(i = 0; i < n; ++i)
651 if(stof(argv(i)) == w)
653 if(i == n) // not found
654 neworder = strcat(neworder, ftos(w), " ");
658 return substring(neworder, 0, strlen(neworder) - 1);
661 string mapPriorityList(string order, string(string) mapfunc)
666 n = tokenize_console(order);
668 for(i = 0; i < n; ++i)
669 neworder = strcat(neworder, mapfunc(argv(i)), " ");
671 return substring(neworder, 0, strlen(neworder) - 1);
674 string swapInPriorityList(string order, float i, float j)
679 n = tokenize_console(order);
681 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
684 for(w = 0; w < n; ++w)
687 s = strcat(s, argv(j), " ");
689 s = strcat(s, argv(i), " ");
691 s = strcat(s, argv(w), " ");
693 return substring(s, 0, strlen(s) - 1);
699 float cvar_value_issafe(string s)
701 if(strstrofs(s, "\"", 0) >= 0)
703 if(strstrofs(s, "\\", 0) >= 0)
705 if(strstrofs(s, ";", 0) >= 0)
707 if(strstrofs(s, "$", 0) >= 0)
709 if(strstrofs(s, "\r", 0) >= 0)
711 if(strstrofs(s, "\n", 0) >= 0)
717 void get_mi_min_max(float mode)
722 strunzone(mi_shortname);
723 mi_shortname = mapname;
724 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
725 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
726 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
727 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
728 mi_shortname = strzone(mi_shortname);
740 MapInfo_Get_ByName(mi_shortname, 0, 0);
741 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
743 mi_min = MapInfo_Map_mins;
744 mi_max = MapInfo_Map_maxs;
752 tracebox('1 0 0' * mi_x,
753 '0 1 0' * mi_y + '0 0 1' * mi_z,
754 '0 1 0' * ma_y + '0 0 1' * ma_z,
758 if(!trace_startsolid)
759 mi_min_x = trace_endpos_x;
761 tracebox('0 1 0' * mi_y,
762 '1 0 0' * mi_x + '0 0 1' * mi_z,
763 '1 0 0' * ma_x + '0 0 1' * ma_z,
767 if(!trace_startsolid)
768 mi_min_y = trace_endpos_y;
770 tracebox('0 0 1' * mi_z,
771 '1 0 0' * mi_x + '0 1 0' * mi_y,
772 '1 0 0' * ma_x + '0 1 0' * ma_y,
776 if(!trace_startsolid)
777 mi_min_z = trace_endpos_z;
779 tracebox('1 0 0' * ma_x,
780 '0 1 0' * mi_y + '0 0 1' * mi_z,
781 '0 1 0' * ma_y + '0 0 1' * ma_z,
785 if(!trace_startsolid)
786 mi_max_x = trace_endpos_x;
788 tracebox('0 1 0' * ma_y,
789 '1 0 0' * mi_x + '0 0 1' * mi_z,
790 '1 0 0' * ma_x + '0 0 1' * ma_z,
794 if(!trace_startsolid)
795 mi_max_y = trace_endpos_y;
797 tracebox('0 0 1' * ma_z,
798 '1 0 0' * mi_x + '0 1 0' * mi_y,
799 '1 0 0' * ma_x + '0 1 0' * ma_y,
803 if(!trace_startsolid)
804 mi_max_z = trace_endpos_z;
809 void get_mi_min_max_texcoords(float mode)
813 get_mi_min_max(mode);
818 // extend mi_picmax to get a square aspect ratio
819 // center the map in that area
820 extend = mi_picmax - mi_picmin;
821 if(extend_y > extend_x)
823 mi_picmin_x -= (extend_y - extend_x) * 0.5;
824 mi_picmax_x += (extend_y - extend_x) * 0.5;
828 mi_picmin_y -= (extend_x - extend_y) * 0.5;
829 mi_picmax_y += (extend_x - extend_y) * 0.5;
832 // add another some percent
833 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
837 // calculate the texcoords
838 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
839 // first the two corners of the origin
840 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
841 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
842 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
843 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
844 // then the other corners
845 mi_pictexcoord1_x = mi_pictexcoord0_x;
846 mi_pictexcoord1_y = mi_pictexcoord2_y;
847 mi_pictexcoord3_x = mi_pictexcoord2_x;
848 mi_pictexcoord3_y = mi_pictexcoord0_y;
853 void cvar_settemp(string pKey, string pValue)
855 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
857 void cvar_settemp_restore()
859 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
862 void cvar_settemp(string pKey, string pValue)
866 if(cvar_string(pKey) == pValue)
868 i = cvar("settemp_idx");
869 cvar_set("settemp_idx", ftos(i+1));
870 settemp_var = strcat("_settemp_x", ftos(i));
872 registercvar(settemp_var, "", 0);
874 registercvar(settemp_var, "");
876 cvar_set("settemp_list", strcat("1 ", pKey, " ", settemp_var, " ", cvar_string("settemp_list")));
877 cvar_set(settemp_var, cvar_string(pKey));
878 cvar_set(pKey, pValue);
881 void cvar_settemp_restore()
883 // undo what cvar_settemp did
885 n = tokenize_console(cvar_string("settemp_list"));
886 for(i = 0; i < n - 3; i += 3)
887 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
888 cvar_set("settemp_list", "0");
892 float almost_equals(float a, float b)
895 eps = (max(a, -a) + max(b, -b)) * 0.001;
896 if(a - b < eps && b - a < eps)
901 float almost_in_bounds(float a, float b, float c)
904 eps = (max(a, -a) + max(c, -c)) * 0.001;
905 return b == median(a - eps, b, c + eps);
908 float power2of(float e)
912 float log2of(float x)
914 // NOTE: generated code
987 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
994 return (rgb_y - rgb_z) / (ma - mi);
996 return (rgb_y - rgb_z) / (ma - mi) + 6;
999 return (rgb_z - rgb_x) / (ma - mi) + 2;
1000 else // if(ma == rgb_z)
1001 return (rgb_x - rgb_y) / (ma - mi) + 4;
1004 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1008 hue -= 6 * floor(hue / 6);
1010 //else if(ma == rgb_x)
1011 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1015 rgb_y = hue * (ma - mi) + mi;
1018 //else if(ma == rgb_y)
1019 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1022 rgb_x = (2 - hue) * (ma - mi) + mi;
1030 rgb_z = (hue - 2) * (ma - mi) + mi;
1032 //else // if(ma == rgb_z)
1033 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1037 rgb_y = (4 - hue) * (ma - mi) + mi;
1042 rgb_x = (hue - 4) * (ma - mi) + mi;
1046 //else if(ma == rgb_x)
1047 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1048 else // if(hue <= 6)
1052 rgb_z = (6 - hue) * (ma - mi) + mi;
1058 vector rgb_to_hsv(vector rgb)
1063 mi = min3(rgb_x, rgb_y, rgb_z);
1064 ma = max3(rgb_x, rgb_y, rgb_z);
1066 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1077 vector hsv_to_rgb(vector hsv)
1079 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1082 vector rgb_to_hsl(vector rgb)
1087 mi = min3(rgb_x, rgb_y, rgb_z);
1088 ma = max3(rgb_x, rgb_y, rgb_z);
1090 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1092 hsl_z = 0.5 * (mi + ma);
1095 else if(hsl_z <= 0.5)
1096 hsl_y = (ma - mi) / (2*hsl_z);
1097 else // if(hsl_z > 0.5)
1098 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1103 vector hsl_to_rgb(vector hsl)
1105 float mi, ma, maminusmi;
1108 maminusmi = hsl_y * 2 * hsl_z;
1110 maminusmi = hsl_y * (2 - 2 * hsl_z);
1112 // hsl_z = 0.5 * mi + 0.5 * ma
1113 // maminusmi = - mi + ma
1114 mi = hsl_z - 0.5 * maminusmi;
1115 ma = hsl_z + 0.5 * maminusmi;
1117 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1120 string rgb_to_hexcolor(vector rgb)
1125 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1126 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1127 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1131 // requires that m2>m1 in all coordinates, and that m4>m3
1132 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;};
1134 // requires the same, but is a stronger condition
1135 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;};
1140 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1142 float ICanHasKallerz;
1144 // detect color codes support in the width function
1145 ICanHasKallerz = (w("^7", theSize) == 0);
1148 // The following function is SLOW.
1149 // For your safety and for the protection of those around you...
1150 // DO NOT CALL THIS AT HOME.
1151 // No really, don't.
1152 if(w(theText, theSize) <= maxWidth)
1153 return strlen(theText); // yeah!
1155 // binary search for right place to cut string
1157 float left, right, middle; // this always works
1159 right = strlen(theText); // this always fails
1162 middle = floor((left + right) / 2);
1163 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1168 while(left < right - 1);
1172 // NOTE: when color codes are involved, this binary search is,
1173 // mathematically, BROKEN. However, it is obviously guaranteed to
1174 // terminate, as the range still halves each time - but nevertheless, it is
1175 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1176 // range, and "right" is outside).
1178 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1179 // and decrease left on the basis of the chars detected of the truncated tag
1180 // Even if the ^xrgb tag is not complete/correct, left is decreased
1181 // (sometimes too much but with a correct result)
1182 // it fixes also ^[0-9]
1183 while(left >= 1 && substring(theText, left-1, 1) == "^")
1186 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1188 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1190 ch = str2chr(theText, left-1);
1191 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1194 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1196 ch = str2chr(theText, left-2);
1197 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1199 ch = str2chr(theText, left-1);
1200 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1209 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1211 float ICanHasKallerz;
1213 // detect color codes support in the width function
1214 ICanHasKallerz = (w("^7") == 0);
1217 // The following function is SLOW.
1218 // For your safety and for the protection of those around you...
1219 // DO NOT CALL THIS AT HOME.
1220 // No really, don't.
1221 if(w(theText) <= maxWidth)
1222 return strlen(theText); // yeah!
1224 // binary search for right place to cut string
1226 float left, right, middle; // this always works
1228 right = strlen(theText); // this always fails
1231 middle = floor((left + right) / 2);
1232 if(w(substring(theText, 0, middle)) <= maxWidth)
1237 while(left < right - 1);
1241 // NOTE: when color codes are involved, this binary search is,
1242 // mathematically, BROKEN. However, it is obviously guaranteed to
1243 // terminate, as the range still halves each time - but nevertheless, it is
1244 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1245 // range, and "right" is outside).
1247 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1248 // and decrease left on the basis of the chars detected of the truncated tag
1249 // Even if the ^xrgb tag is not complete/correct, left is decreased
1250 // (sometimes too much but with a correct result)
1251 // it fixes also ^[0-9]
1252 while(left >= 1 && substring(theText, left-1, 1) == "^")
1255 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1257 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1259 ch = str2chr(theText, left-1);
1260 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1263 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1265 ch = str2chr(theText, left-2);
1266 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1268 ch = str2chr(theText, left-1);
1269 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1278 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1284 s = getWrappedLine_remaining;
1286 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1287 if(cantake > 0 && cantake < strlen(s))
1290 while(take > 0 && substring(s, take, 1) != " ")
1294 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1295 if(getWrappedLine_remaining == "")
1296 getWrappedLine_remaining = string_null;
1297 return substring(s, 0, cantake);
1301 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1302 if(getWrappedLine_remaining == "")
1303 getWrappedLine_remaining = string_null;
1304 return substring(s, 0, take);
1309 getWrappedLine_remaining = string_null;
1314 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1320 s = getWrappedLine_remaining;
1322 cantake = textLengthUpToLength(s, w, tw);
1323 if(cantake > 0 && cantake < strlen(s))
1326 while(take > 0 && substring(s, take, 1) != " ")
1330 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1331 if(getWrappedLine_remaining == "")
1332 getWrappedLine_remaining = string_null;
1333 return substring(s, 0, cantake);
1337 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1338 if(getWrappedLine_remaining == "")
1339 getWrappedLine_remaining = string_null;
1340 return substring(s, 0, take);
1345 getWrappedLine_remaining = string_null;
1350 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1352 if(tw(theText, theFontSize) <= maxWidth)
1355 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1358 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1360 if(tw(theText) <= maxWidth)
1363 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1366 float isGametypeInFilter(float gt, float tp, string pattern)
1368 string subpattern, subpattern2, subpattern3;
1369 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1371 subpattern2 = ",teams,";
1373 subpattern2 = ",noteams,";
1374 if(gt == GAME_RACE || gt == GAME_CTS)
1375 subpattern3 = ",race,";
1377 subpattern3 = string_null;
1379 if(substring(pattern, 0, 1) == "-")
1381 pattern = substring(pattern, 1, strlen(pattern) - 1);
1382 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1384 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1386 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1391 if(substring(pattern, 0, 1) == "+")
1392 pattern = substring(pattern, 1, strlen(pattern) - 1);
1393 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1394 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1395 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1401 void shuffle(float n, swapfunc_t swap, entity pass)
1404 for(i = 1; i < n; ++i)
1406 // swap i-th item at a random position from 0 to i
1407 // proof for even distribution:
1410 // item n+1 gets at any position with chance 1/(n+1)
1411 // all others will get their 1/n chance reduced by factor n/(n+1)
1412 // to be on place n+1, their chance will be 1/(n+1)
1413 // 1/n * n/(n+1) = 1/(n+1)
1415 j = floor(random() * (i + 1));
1421 string substring_range(string s, float b, float e)
1423 return substring(s, b, e - b);
1426 string swapwords(string str, float i, float j)
1429 string s1, s2, s3, s4, s5;
1430 float si, ei, sj, ej, s0, en;
1431 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1432 si = argv_start_index(i);
1433 sj = argv_start_index(j);
1434 ei = argv_end_index(i);
1435 ej = argv_end_index(j);
1436 s0 = argv_start_index(0);
1437 en = argv_end_index(n-1);
1438 s1 = substring_range(str, s0, si);
1439 s2 = substring_range(str, si, ei);
1440 s3 = substring_range(str, ei, sj);
1441 s4 = substring_range(str, sj, ej);
1442 s5 = substring_range(str, ej, en);
1443 return strcat(s1, s4, s3, s2, s5);
1446 string _shufflewords_str;
1447 void _shufflewords_swapfunc(float i, float j, entity pass)
1449 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1451 string shufflewords(string str)
1454 _shufflewords_str = str;
1455 n = tokenizebyseparator(str, " ");
1456 shuffle(n, _shufflewords_swapfunc, world);
1457 str = _shufflewords_str;
1458 _shufflewords_str = string_null;
1462 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1478 // actually, every number solves the equation!
1489 if(a > 0) // put the smaller solution first
1491 v_x = ((-b)-D) / (2*a);
1492 v_y = ((-b)+D) / (2*a);
1496 v_x = (-b+D) / (2*a);
1497 v_y = (-b-D) / (2*a);
1503 // complex solutions!
1517 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1518 float _unacceptable_compiler_bug_1_b() { return 1; }
1519 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1520 float _unacceptable_compiler_bug_1_d() { return 1; }
1522 void check_unacceptable_compiler_bugs()
1524 if(cvar("_allow_unacceptable_compiler_bugs"))
1526 tokenize_console("foo bar");
1527 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1528 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.");
1531 float compressShotOrigin(vector v)
1535 y = rint(v_y * 4) + 128;
1536 z = rint(v_z * 4) + 128;
1537 if(x > 255 || x < 0)
1539 print("shot origin ", vtos(v), " x out of bounds\n");
1540 x = bound(0, x, 255);
1542 if(y > 255 || y < 0)
1544 print("shot origin ", vtos(v), " y out of bounds\n");
1545 y = bound(0, y, 255);
1547 if(z > 255 || z < 0)
1549 print("shot origin ", vtos(v), " z out of bounds\n");
1550 z = bound(0, z, 255);
1552 return x * 0x10000 + y * 0x100 + z;
1554 vector decompressShotOrigin(float f)
1557 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1558 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1559 v_z = ((f & 0xFF) - 128) / 4;
1563 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1565 float start, end, root, child;
1568 start = floor((n - 2) / 2);
1571 // siftdown(start, count-1);
1573 while(root * 2 + 1 <= n-1)
1575 child = root * 2 + 1;
1577 if(cmp(child, child+1, pass) < 0)
1579 if(cmp(root, child, pass) < 0)
1581 swap(root, child, pass);
1597 // siftdown(0, end);
1599 while(root * 2 + 1 <= end)
1601 child = root * 2 + 1;
1602 if(child < end && cmp(child, child+1, pass) < 0)
1604 if(cmp(root, child, pass) < 0)
1606 swap(root, child, pass);
1616 void RandomSelection_Init()
1618 RandomSelection_totalweight = 0;
1619 RandomSelection_chosen_ent = world;
1620 RandomSelection_chosen_float = 0;
1621 RandomSelection_chosen_string = string_null;
1622 RandomSelection_best_priority = -1;
1624 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1626 if(priority > RandomSelection_best_priority)
1628 RandomSelection_best_priority = priority;
1629 RandomSelection_chosen_ent = e;
1630 RandomSelection_chosen_float = f;
1631 RandomSelection_chosen_string = s;
1632 RandomSelection_totalweight = weight;
1634 else if(priority == RandomSelection_best_priority)
1636 RandomSelection_totalweight += weight;
1637 if(random() * RandomSelection_totalweight <= weight)
1639 RandomSelection_chosen_ent = e;
1640 RandomSelection_chosen_float = f;
1641 RandomSelection_chosen_string = s;
1646 vector healtharmor_maxdamage(float h, float a, float armorblock)
1648 // NOTE: we'll always choose the SMALLER value...
1649 float healthdamage, armordamage, armorideal;
1651 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1652 armordamage = a + (h - 1); // damage we can take if we could use more armor
1653 armorideal = healthdamage * armorblock;
1655 if(armordamage < healthdamage)
1668 vector healtharmor_applydamage(float a, float armorblock, float damage)
1671 v_y = bound(0, damage * armorblock, a); // save
1672 v_x = bound(0, damage - v_y, damage); // take
1677 string getcurrentmod()
1681 m = cvar_string("fs_gamedir");
1682 n = tokenize_console(m);
1694 v = ReadShort() * 256; // note: this is signed
1695 v += ReadByte(); // note: this is unsigned
1699 void WriteInt24_t(float dest, float val)
1702 WriteShort(dest, (v = floor(val / 256)));
1703 WriteByte(dest, val - v * 256); // 0..255
1708 float float2range11(float f)
1710 // continuous function mapping all reals into -1..1
1711 return f / (fabs(f) + 1);
1714 float float2range01(float f)
1716 // continuous function mapping all reals into 0..1
1717 return 0.5 + 0.5 * float2range11(f);
1720 // from the GNU Scientific Library
1721 float gsl_ran_gaussian_lastvalue;
1722 float gsl_ran_gaussian_lastvalue_set;
1723 float gsl_ran_gaussian(float sigma)
1726 if(gsl_ran_gaussian_lastvalue_set)
1728 gsl_ran_gaussian_lastvalue_set = 0;
1729 return sigma * gsl_ran_gaussian_lastvalue;
1733 a = random() * 2 * M_PI;
1734 b = sqrt(-2 * log(random()));
1735 gsl_ran_gaussian_lastvalue = cos(a) * b;
1736 gsl_ran_gaussian_lastvalue_set = 1;
1737 return sigma * sin(a) * b;
1741 string car(string s)
1744 o = strstrofs(s, " ", 0);
1747 return substring(s, 0, o);
1749 string cdr(string s)
1752 o = strstrofs(s, " ", 0);
1755 return substring(s, o + 1, strlen(s) - (o + 1));
1757 float matchacl(string acl, string str)
1764 t = car(acl); acl = cdr(acl);
1766 if(substring(t, 0, 1) == "-")
1769 t = substring(t, 1, strlen(t) - 1);
1771 else if(substring(t, 0, 1) == "+")
1772 t = substring(t, 1, strlen(t) - 1);
1773 if(substring(t, -1, 1) == "*")
1775 t = substring(t, 0, strlen(t) - 1);
1776 s = substring(s, 0, strlen(t));
1788 float startsWith(string haystack, string needle)
1790 return substring(haystack, 0, strlen(needle)) == needle;
1792 float startsWithNocase(string haystack, string needle)
1794 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1797 string get_model_datafilename(string m, float sk, string fil)
1802 m = "models/player/*_";
1804 m = strcat(m, ftos(sk));
1807 return strcat(m, ".", fil);
1810 float get_model_parameters(string m, float sk)
1815 get_model_parameters_modelname = string_null;
1816 get_model_parameters_modelskin = -1;
1817 get_model_parameters_name = string_null;
1818 get_model_parameters_species = -1;
1819 get_model_parameters_sex = string_null;
1820 get_model_parameters_weight = -1;
1821 get_model_parameters_age = -1;
1822 get_model_parameters_desc = string_null;
1828 if(substring(m, -4, -1) != ".txt")
1830 if(substring(m, -6, 1) != "_")
1832 sk = stof(substring(m, -5, 1));
1833 m = substring(m, 0, -7);
1836 fn = get_model_datafilename(m, sk, "txt");
1837 fh = fopen(fn, FILE_READ);
1841 get_model_parameters_modelname = m;
1842 get_model_parameters_modelskin = sk;
1843 while((s = fgets(fh)))
1846 break; // next lines will be description
1850 get_model_parameters_name = s;
1854 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1855 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1856 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1857 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1858 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1859 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1860 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1863 get_model_parameters_sex = s;
1865 get_model_parameters_weight = stof(s);
1867 get_model_parameters_age = stof(s);
1870 while((s = fgets(fh)))
1872 if(get_model_parameters_desc)
1873 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1875 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1883 vector vec2(vector v)
1890 vector NearestPointOnBox(entity box, vector org)
1892 vector m1, m2, nearest;
1894 m1 = box.mins + box.origin;
1895 m2 = box.maxs + box.origin;
1897 nearest_x = bound(m1_x, org_x, m2_x);
1898 nearest_y = bound(m1_y, org_y, m2_y);
1899 nearest_z = bound(m1_z, org_z, m2_z);
1905 float vercmp_recursive(string v1, string v2)
1911 dot1 = strstrofs(v1, ".", 0);
1912 dot2 = strstrofs(v2, ".", 0);
1916 s1 = substring(v1, 0, dot1);
1920 s2 = substring(v2, 0, dot2);
1922 r = stof(s1) - stof(s2);
1926 r = strcasecmp(s1, s2);
1939 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1942 float vercmp(string v1, string v2)
1944 if(strcasecmp(v1, v2) == 0) // early out check
1953 return vercmp_recursive(v1, v2);