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"));
43 string draw_UseSkinFor(string pic)
45 if(substring(pic, 0, 1) == "/")
46 return substring(pic, 1, strlen(pic)-1);
48 return strcat(draw_currentSkin, "/", pic);
52 string unescape(string in)
57 // but it doesn't seem to be necessary in my tests at least
62 for(i = 0; i < len; ++i)
64 s = substring(in, i, 1);
67 s = substring(in, i+1, 1);
69 str = strcat(str, "\n");
71 str = strcat(str, "\\");
73 str = strcat(str, substring(in, i, 2));
83 void wordwrap_cb(string s, float l, void(string) callback)
86 float lleft, i, j, wlen;
90 for (i = 0;i < strlen(s);++i)
92 if (substring(s, i, 2) == "\\n")
98 else if (substring(s, i, 1) == "\n")
103 else if (substring(s, i, 1) == " ")
113 for (j = i+1;j < strlen(s);++j)
114 // ^^ this skips over the first character of a word, which
115 // is ALWAYS part of the word
116 // this is safe since if i+1 == strlen(s), i will become
117 // strlen(s)-1 at the end of this block and the function
118 // will terminate. A space can't be the first character we
119 // read here, and neither can a \n be the start, since these
120 // two cases have been handled above.
122 c = substring(s, j, 1);
129 // we need to keep this tempstring alive even if substring is
130 // called repeatedly, so call strcat even though we're not
140 callback(substring(s, i, wlen));
141 lleft = lleft - wlen;
148 float dist_point_line(vector p, vector l0, vector ldir)
150 ldir = normalize(ldir);
152 // remove the component in line direction
153 p = p - (p * ldir) * ldir;
155 // vlen of the remaining vector
159 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
188 float median(float a, float b, float c)
191 return bound(a, b, c);
192 return bound(c, b, a);
195 // converts a number to a string with the indicated number of decimals
196 // works for up to 10 decimals!
197 string ftos_decimals(float number, float decimals)
199 // we have sprintf...
200 return sprintf("%.*f", decimals, number);
204 vector colormapPaletteColor(float c, float isPants)
208 case 0: return '1.000000 1.000000 1.000000';
209 case 1: return '1.000000 0.333333 0.000000';
210 case 2: return '0.000000 1.000000 0.501961';
211 case 3: return '0.000000 1.000000 0.000000';
212 case 4: return '1.000000 0.000000 0.000000';
213 case 5: return '0.000000 0.666667 1.000000';
214 case 6: return '0.000000 1.000000 1.000000';
215 case 7: return '0.501961 1.000000 0.000000';
216 case 8: return '0.501961 0.000000 1.000000';
217 case 9: return '1.000000 0.000000 1.000000';
218 case 10: return '1.000000 0.000000 0.501961';
219 case 11: return '0.000000 0.000000 1.000000';
220 case 12: return '1.000000 1.000000 0.000000';
221 case 13: return '0.000000 0.333333 1.000000';
222 case 14: return '1.000000 0.666667 0.000000';
226 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
227 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
228 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
231 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
232 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
233 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
234 default: return '0.000 0.000 0.000';
238 // unzone the string, and return it as tempstring. Safe to be called on string_null
239 string fstrunzone(string s)
249 float fexists(string f)
252 fh = fopen(f, FILE_READ);
259 // Databases (hash tables)
260 #define DB_BUCKETS 8192
261 void db_save(float db, string pFilename)
264 fh = fopen(pFilename, FILE_WRITE);
267 print(strcat("^1Can't write DB to ", pFilename));
271 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
272 for(i = 0; i < n; ++i)
273 fputs(fh, strcat(bufstr_get(db, i), "\n"));
282 float db_load(string pFilename)
284 float db, fh, i, j, n;
289 fh = fopen(pFilename, FILE_READ);
293 if(stof(l) == DB_BUCKETS)
296 while((l = fgets(fh)))
299 bufstr_set(db, i, l);
305 // different count of buckets, or a dump?
306 // need to reorganize the database then (SLOW)
308 // note: we also parse the first line (l) in case the DB file is
309 // missing the bucket count
312 n = tokenizebyseparator(l, "\\");
313 for(j = 2; j < n; j += 2)
314 db_put(db, argv(j-1), uri_unescape(argv(j)));
316 while((l = fgets(fh)));
322 void db_dump(float db, string pFilename)
324 float fh, i, j, n, m;
325 fh = fopen(pFilename, FILE_WRITE);
327 error(strcat("Can't dump DB to ", pFilename));
330 for(i = 0; i < n; ++i)
332 m = tokenizebyseparator(bufstr_get(db, i), "\\");
333 for(j = 2; j < m; j += 2)
334 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
339 void db_close(float db)
344 string db_get(float db, string pKey)
347 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
348 return uri_unescape(infoget(bufstr_get(db, h), pKey));
351 void db_put(float db, string pKey, string pValue)
354 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
355 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
362 db = db_load("foo.db");
363 print("LOADED. FILL...\n");
364 for(i = 0; i < DB_BUCKETS; ++i)
365 db_put(db, ftos(random()), "X");
366 print("FILLED. SAVE...\n");
367 db_save(db, "foo.db");
368 print("SAVED. CLOSE...\n");
373 // Multiline text file buffers
374 float buf_load(string pFilename)
381 fh = fopen(pFilename, FILE_READ);
388 while((l = fgets(fh)))
390 bufstr_set(buf, i, l);
397 void buf_save(float buf, string pFilename)
400 fh = fopen(pFilename, FILE_WRITE);
402 error(strcat("Can't write buf to ", pFilename));
403 n = buf_getsize(buf);
404 for(i = 0; i < n; ++i)
405 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
409 string mmsss(float tenths)
413 tenths = floor(tenths + 0.5);
414 minutes = floor(tenths / 600);
415 tenths -= minutes * 600;
416 s = ftos(1000 + tenths);
417 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
420 string mmssss(float hundredths)
424 hundredths = floor(hundredths + 0.5);
425 minutes = floor(hundredths / 6000);
426 hundredths -= minutes * 6000;
427 s = ftos(10000 + hundredths);
428 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
431 string ScoreString(float pFlags, float pValue)
436 pValue = floor(pValue + 0.5); // round
438 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
440 else if(pFlags & SFL_RANK)
442 valstr = ftos(pValue);
444 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
445 valstr = strcat(valstr, "th");
446 else if(substring(valstr, l - 1, 1) == "1")
447 valstr = strcat(valstr, "st");
448 else if(substring(valstr, l - 1, 1) == "2")
449 valstr = strcat(valstr, "nd");
450 else if(substring(valstr, l - 1, 1) == "3")
451 valstr = strcat(valstr, "rd");
453 valstr = strcat(valstr, "th");
455 else if(pFlags & SFL_TIME)
456 valstr = TIME_ENCODED_TOSTRING(pValue);
458 valstr = ftos(pValue);
463 vector cross(vector a, vector b)
466 '1 0 0' * (a_y * b_z - a_z * b_y)
467 + '0 1 0' * (a_z * b_x - a_x * b_z)
468 + '0 0 1' * (a_x * b_y - a_y * b_x);
471 // compressed vector format:
472 // like MD3, just even shorter
473 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
474 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
475 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
476 // length = 2^(length_encoded/8) / 8
477 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
478 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
479 // the special value 0 indicates the zero vector
481 float lengthLogTable[128];
483 float invertLengthLog(float x)
485 float l, r, m, lerr, rerr;
487 if(x >= lengthLogTable[127])
489 if(x <= lengthLogTable[0])
497 m = floor((l + r) / 2);
498 if(lengthLogTable[m] < x)
504 // now: r is >=, l is <
505 lerr = (x - lengthLogTable[l]);
506 rerr = (lengthLogTable[r] - x);
512 vector decompressShortVector(float data)
518 p = (data & 0xF000) / 0x1000;
519 y = (data & 0x0F80) / 0x80;
520 len = (data & 0x007F);
522 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
535 y = .19634954084936207740 * y;
536 p = .19634954084936207740 * p - 1.57079632679489661922;
537 out_x = cos(y) * cos(p);
538 out_y = sin(y) * cos(p);
542 //print("decompressed: ", vtos(out), "\n");
544 return out * lengthLogTable[len];
547 float compressShortVector(vector vec)
553 //print("compress: ", vtos(vec), "\n");
554 ang = vectoangles(vec);
558 if(ang_x < -90 && ang_x > +90)
559 error("BOGUS vectoangles");
560 //print("angles: ", vtos(ang), "\n");
562 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
571 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
572 len = invertLengthLog(vlen(vec));
574 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
576 return (p * 0x1000) + (y * 0x80) + len;
579 void compressShortVector_init()
584 for(i = 0; i < 128; ++i)
586 lengthLogTable[i] = l;
590 if(cvar("developer"))
592 print("Verifying vector compression table...\n");
593 for(i = 0x0F00; i < 0xFFFF; ++i)
594 if(i != compressShortVector(decompressShortVector(i)))
596 print("BROKEN vector compression: ", ftos(i));
597 print(" -> ", vtos(decompressShortVector(i)));
598 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
607 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
609 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
610 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
611 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
612 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
619 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
620 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
625 string fixPriorityList(string order, float from, float to, float subtract, float complete)
630 n = tokenize_console(order);
632 for(i = 0; i < n; ++i)
637 if(w >= from && w <= to)
638 neworder = strcat(neworder, ftos(w), " ");
642 if(w >= from && w <= to)
643 neworder = strcat(neworder, ftos(w), " ");
650 n = tokenize_console(neworder);
651 for(w = to; w >= from; --w)
653 for(i = 0; i < n; ++i)
654 if(stof(argv(i)) == w)
656 if(i == n) // not found
657 neworder = strcat(neworder, ftos(w), " ");
661 return substring(neworder, 0, strlen(neworder) - 1);
664 string mapPriorityList(string order, string(string) mapfunc)
669 n = tokenize_console(order);
671 for(i = 0; i < n; ++i)
672 neworder = strcat(neworder, mapfunc(argv(i)), " ");
674 return substring(neworder, 0, strlen(neworder) - 1);
677 string swapInPriorityList(string order, float i, float j)
682 n = tokenize_console(order);
684 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
687 for(w = 0; w < n; ++w)
690 s = strcat(s, argv(j), " ");
692 s = strcat(s, argv(i), " ");
694 s = strcat(s, argv(w), " ");
696 return substring(s, 0, strlen(s) - 1);
702 float cvar_value_issafe(string s)
704 if(strstrofs(s, "\"", 0) >= 0)
706 if(strstrofs(s, "\\", 0) >= 0)
708 if(strstrofs(s, ";", 0) >= 0)
710 if(strstrofs(s, "$", 0) >= 0)
712 if(strstrofs(s, "\r", 0) >= 0)
714 if(strstrofs(s, "\n", 0) >= 0)
720 void get_mi_min_max(float mode)
725 strunzone(mi_shortname);
726 mi_shortname = mapname;
727 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
728 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
729 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
730 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
731 mi_shortname = strzone(mi_shortname);
743 MapInfo_Get_ByName(mi_shortname, 0, 0);
744 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
746 mi_min = MapInfo_Map_mins;
747 mi_max = MapInfo_Map_maxs;
755 tracebox('1 0 0' * mi_x,
756 '0 1 0' * mi_y + '0 0 1' * mi_z,
757 '0 1 0' * ma_y + '0 0 1' * ma_z,
761 if(!trace_startsolid)
762 mi_min_x = trace_endpos_x;
764 tracebox('0 1 0' * mi_y,
765 '1 0 0' * mi_x + '0 0 1' * mi_z,
766 '1 0 0' * ma_x + '0 0 1' * ma_z,
770 if(!trace_startsolid)
771 mi_min_y = trace_endpos_y;
773 tracebox('0 0 1' * mi_z,
774 '1 0 0' * mi_x + '0 1 0' * mi_y,
775 '1 0 0' * ma_x + '0 1 0' * ma_y,
779 if(!trace_startsolid)
780 mi_min_z = trace_endpos_z;
782 tracebox('1 0 0' * ma_x,
783 '0 1 0' * mi_y + '0 0 1' * mi_z,
784 '0 1 0' * ma_y + '0 0 1' * ma_z,
788 if(!trace_startsolid)
789 mi_max_x = trace_endpos_x;
791 tracebox('0 1 0' * ma_y,
792 '1 0 0' * mi_x + '0 0 1' * mi_z,
793 '1 0 0' * ma_x + '0 0 1' * ma_z,
797 if(!trace_startsolid)
798 mi_max_y = trace_endpos_y;
800 tracebox('0 0 1' * ma_z,
801 '1 0 0' * mi_x + '0 1 0' * mi_y,
802 '1 0 0' * ma_x + '0 1 0' * ma_y,
806 if(!trace_startsolid)
807 mi_max_z = trace_endpos_z;
812 void get_mi_min_max_texcoords(float mode)
816 get_mi_min_max(mode);
821 // extend mi_picmax to get a square aspect ratio
822 // center the map in that area
823 extend = mi_picmax - mi_picmin;
824 if(extend_y > extend_x)
826 mi_picmin_x -= (extend_y - extend_x) * 0.5;
827 mi_picmax_x += (extend_y - extend_x) * 0.5;
831 mi_picmin_y -= (extend_x - extend_y) * 0.5;
832 mi_picmax_y += (extend_x - extend_y) * 0.5;
835 // add another some percent
836 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
840 // calculate the texcoords
841 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
842 // first the two corners of the origin
843 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
844 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
845 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
846 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
847 // then the other corners
848 mi_pictexcoord1_x = mi_pictexcoord0_x;
849 mi_pictexcoord1_y = mi_pictexcoord2_y;
850 mi_pictexcoord3_x = mi_pictexcoord2_x;
851 mi_pictexcoord3_y = mi_pictexcoord0_y;
855 void cvar_settemp(string cv, string val)
858 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
862 e.classname = "saved_cvar_value";
863 e.netname = strzone(cv);
864 e.message = strzone(cvar_string(cv));
869 void cvar_settemp_restore()
872 while((e = find(world, classname, "saved_cvar_value")))
874 cvar_set(e.netname, e.message);
879 float almost_equals(float a, float b)
882 eps = (max(a, -a) + max(b, -b)) * 0.001;
883 if(a - b < eps && b - a < eps)
888 float almost_in_bounds(float a, float b, float c)
891 eps = (max(a, -a) + max(c, -c)) * 0.001;
892 return b == median(a - eps, b, c + eps);
895 float power2of(float e)
899 float log2of(float x)
901 // NOTE: generated code
974 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
981 return (rgb_y - rgb_z) / (ma - mi);
983 return (rgb_y - rgb_z) / (ma - mi) + 6;
986 return (rgb_z - rgb_x) / (ma - mi) + 2;
987 else // if(ma == rgb_z)
988 return (rgb_x - rgb_y) / (ma - mi) + 4;
991 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
995 hue -= 6 * floor(hue / 6);
997 //else if(ma == rgb_x)
998 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1002 rgb_y = hue * (ma - mi) + mi;
1005 //else if(ma == rgb_y)
1006 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1009 rgb_x = (2 - hue) * (ma - mi) + mi;
1017 rgb_z = (hue - 2) * (ma - mi) + mi;
1019 //else // if(ma == rgb_z)
1020 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1024 rgb_y = (4 - hue) * (ma - mi) + mi;
1029 rgb_x = (hue - 4) * (ma - mi) + mi;
1033 //else if(ma == rgb_x)
1034 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1035 else // if(hue <= 6)
1039 rgb_z = (6 - hue) * (ma - mi) + mi;
1045 vector rgb_to_hsv(vector rgb)
1050 mi = min(rgb_x, rgb_y, rgb_z);
1051 ma = max(rgb_x, rgb_y, rgb_z);
1053 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1064 vector hsv_to_rgb(vector hsv)
1066 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1069 vector rgb_to_hsl(vector rgb)
1074 mi = min(rgb_x, rgb_y, rgb_z);
1075 ma = max(rgb_x, rgb_y, rgb_z);
1077 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1079 hsl_z = 0.5 * (mi + ma);
1082 else if(hsl_z <= 0.5)
1083 hsl_y = (ma - mi) / (2*hsl_z);
1084 else // if(hsl_z > 0.5)
1085 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1090 vector hsl_to_rgb(vector hsl)
1092 float mi, ma, maminusmi;
1095 maminusmi = hsl_y * 2 * hsl_z;
1097 maminusmi = hsl_y * (2 - 2 * hsl_z);
1099 // hsl_z = 0.5 * mi + 0.5 * ma
1100 // maminusmi = - mi + ma
1101 mi = hsl_z - 0.5 * maminusmi;
1102 ma = hsl_z + 0.5 * maminusmi;
1104 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1107 string rgb_to_hexcolor(vector rgb)
1112 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1113 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1114 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1118 // requires that m2>m1 in all coordinates, and that m4>m3
1119 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;}
1121 // requires the same, but is a stronger condition
1122 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;}
1127 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1130 // The following function is SLOW.
1131 // For your safety and for the protection of those around you...
1132 // DO NOT CALL THIS AT HOME.
1133 // No really, don't.
1134 if(w(theText, theSize) <= maxWidth)
1135 return strlen(theText); // yeah!
1137 // binary search for right place to cut string
1139 float left, right, middle; // this always works
1141 right = strlen(theText); // this always fails
1144 middle = floor((left + right) / 2);
1145 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1150 while(left < right - 1);
1152 if(w("^7", theSize) == 0) // detect color codes support in the width function
1154 // NOTE: when color codes are involved, this binary search is,
1155 // mathematically, BROKEN. However, it is obviously guaranteed to
1156 // terminate, as the range still halves each time - but nevertheless, it is
1157 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1158 // range, and "right" is outside).
1160 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1161 // and decrease left on the basis of the chars detected of the truncated tag
1162 // Even if the ^xrgb tag is not complete/correct, left is decreased
1163 // (sometimes too much but with a correct result)
1164 // it fixes also ^[0-9]
1165 while(left >= 1 && substring(theText, left-1, 1) == "^")
1168 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1170 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1172 ch = str2chr(theText, left-1);
1173 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1176 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1178 ch = str2chr(theText, left-2);
1179 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1181 ch = str2chr(theText, left-1);
1182 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1191 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1194 // The following function is SLOW.
1195 // For your safety and for the protection of those around you...
1196 // DO NOT CALL THIS AT HOME.
1197 // No really, don't.
1198 if(w(theText) <= maxWidth)
1199 return strlen(theText); // yeah!
1201 // binary search for right place to cut string
1203 float left, right, middle; // this always works
1205 right = strlen(theText); // this always fails
1208 middle = floor((left + right) / 2);
1209 if(w(substring(theText, 0, middle)) <= maxWidth)
1214 while(left < right - 1);
1216 if(w("^7") == 0) // detect color codes support in the width function
1218 // NOTE: when color codes are involved, this binary search is,
1219 // mathematically, BROKEN. However, it is obviously guaranteed to
1220 // terminate, as the range still halves each time - but nevertheless, it is
1221 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1222 // range, and "right" is outside).
1224 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1225 // and decrease left on the basis of the chars detected of the truncated tag
1226 // Even if the ^xrgb tag is not complete/correct, left is decreased
1227 // (sometimes too much but with a correct result)
1228 // it fixes also ^[0-9]
1229 while(left >= 1 && substring(theText, left-1, 1) == "^")
1232 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1234 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1236 ch = str2chr(theText, left-1);
1237 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1240 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1242 ch = str2chr(theText, left-2);
1243 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1245 ch = str2chr(theText, left-1);
1246 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1255 string find_last_color_code(string s)
1257 float start, len, i, carets;
1258 start = strstrofs(s, "^", 0);
1259 if (start == -1) // no caret found
1262 for(i = len; i >= start; --i)
1264 if(substring(s, i, 1) != "^")
1268 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1271 // check if carets aren't all escaped
1272 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1275 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1276 return substring(s, i, 2);
1279 if(substring(s, i+1, 1) == "x")
1280 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1281 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1282 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1283 return substring(s, i, 5);
1285 i -= carets; // this also skips one char before the carets
1291 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1297 s = getWrappedLine_remaining;
1301 getWrappedLine_remaining = string_null;
1302 return s; // the line has no size ANYWAY, nothing would be displayed.
1305 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1306 if(cantake > 0 && cantake < strlen(s))
1309 while(take > 0 && substring(s, take, 1) != " ")
1313 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1314 if(getWrappedLine_remaining == "")
1315 getWrappedLine_remaining = string_null;
1316 else if (tw("^7", theFontSize) == 0)
1317 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1318 return substring(s, 0, cantake);
1322 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1323 if(getWrappedLine_remaining == "")
1324 getWrappedLine_remaining = string_null;
1325 else if (tw("^7", theFontSize) == 0)
1326 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1327 return substring(s, 0, take);
1332 getWrappedLine_remaining = string_null;
1337 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1343 s = getWrappedLine_remaining;
1347 getWrappedLine_remaining = string_null;
1348 return s; // the line has no size ANYWAY, nothing would be displayed.
1351 cantake = textLengthUpToLength(s, w, tw);
1352 if(cantake > 0 && cantake < strlen(s))
1355 while(take > 0 && substring(s, take, 1) != " ")
1359 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1360 if(getWrappedLine_remaining == "")
1361 getWrappedLine_remaining = string_null;
1362 else if (tw("^7") == 0)
1363 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1364 return substring(s, 0, cantake);
1368 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1369 if(getWrappedLine_remaining == "")
1370 getWrappedLine_remaining = string_null;
1371 else if (tw("^7") == 0)
1372 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1373 return substring(s, 0, take);
1378 getWrappedLine_remaining = string_null;
1383 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1385 if(tw(theText, theFontSize) <= maxWidth)
1388 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1391 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1393 if(tw(theText) <= maxWidth)
1396 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1399 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1401 string subpattern, subpattern2, subpattern3, subpattern4;
1402 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1404 subpattern2 = ",teams,";
1406 subpattern2 = ",noteams,";
1408 subpattern3 = ",teamspawns,";
1410 subpattern3 = ",noteamspawns,";
1411 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1412 subpattern4 = ",race,";
1414 subpattern4 = string_null;
1416 if(substring(pattern, 0, 1) == "-")
1418 pattern = substring(pattern, 1, strlen(pattern) - 1);
1419 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1421 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1423 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1425 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1430 if(substring(pattern, 0, 1) == "+")
1431 pattern = substring(pattern, 1, strlen(pattern) - 1);
1432 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1433 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1434 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1435 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 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!
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 dst, float val)
1736 WriteShort(dst, (v = floor(val / 256)));
1737 WriteByte(dst, 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 fn = get_model_datafilename(m, sk, "txt");
1876 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);
1996 float u8_strsize(string s)
2016 // translation helpers
2017 string language_filename(string s)
2022 if(fn == "" || fn == "dump")
2024 fn = strcat(s, ".", fn);
2025 if((fh = fopen(fn, FILE_READ)) >= 0)
2032 string CTX(string s)
2034 float p = strstrofs(s, "^", 0);
2037 return substring(s, p+1, -1);
2040 // x-encoding (encoding as zero length invisible string)
2041 const string XENCODE_2 = "xX";
2042 const string XENCODE_22 = "0123456789abcdefABCDEF";
2043 string xencode(float f)
2046 d = mod(f, 22); f = floor(f / 22);
2047 c = mod(f, 22); f = floor(f / 22);
2048 b = mod(f, 22); f = floor(f / 22);
2049 a = mod(f, 2); // f = floor(f / 2);
2052 substring(XENCODE_2, a, 1),
2053 substring(XENCODE_22, b, 1),
2054 substring(XENCODE_22, c, 1),
2055 substring(XENCODE_22, d, 1)
2058 float xdecode(string s)
2061 if(substring(s, 0, 1) != "^")
2065 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2066 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2067 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2068 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2069 if(a < 0 || b < 0 || c < 0 || d < 0)
2071 return ((a * 22 + b) * 22 + c) * 22 + d;
2074 float lowestbit(float f)
2085 entity ReadCSQCEntity()
2091 return findfloat(world, entnum, f);
2095 float shutdown_running;
2100 void CSQC_Shutdown()
2106 if(shutdown_running)
2108 print("Recursive shutdown detected! Only restoring cvars...\n");
2112 shutdown_running = 1;
2115 cvar_settemp_restore(); // this must be done LAST, but in any case