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 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 float fexists(string f)
242 fh = fopen(f, FILE_READ);
249 // Databases (hash tables)
250 #define DB_BUCKETS 8192
251 void db_save(float db, string pFilename)
254 fh = fopen(pFilename, FILE_WRITE);
257 print(strcat("^1Can't write DB to ", pFilename));
261 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
262 for(i = 0; i < n; ++i)
263 fputs(fh, strcat(bufstr_get(db, i), "\n"));
272 float db_load(string pFilename)
274 float db, fh, i, j, n;
279 fh = fopen(pFilename, FILE_READ);
283 if(stof(l) == DB_BUCKETS)
286 while((l = fgets(fh)))
289 bufstr_set(db, i, l);
295 // different count of buckets, or a dump?
296 // need to reorganize the database then (SLOW)
298 // note: we also parse the first line (l) in case the DB file is
299 // missing the bucket count
302 n = tokenizebyseparator(l, "\\");
303 for(j = 2; j < n; j += 2)
304 db_put(db, argv(j-1), uri_unescape(argv(j)));
306 while((l = fgets(fh)));
312 void db_dump(float db, string pFilename)
314 float fh, i, j, n, m;
315 fh = fopen(pFilename, FILE_WRITE);
317 error(strcat("Can't dump DB to ", pFilename));
320 for(i = 0; i < n; ++i)
322 m = tokenizebyseparator(bufstr_get(db, i), "\\");
323 for(j = 2; j < m; j += 2)
324 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
329 void db_close(float db)
334 string db_get(float db, string pKey)
337 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
338 return uri_unescape(infoget(bufstr_get(db, h), pKey));
341 void db_put(float db, string pKey, string pValue)
344 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
345 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
352 db = db_load("foo.db");
353 print("LOADED. FILL...\n");
354 for(i = 0; i < DB_BUCKETS; ++i)
355 db_put(db, ftos(random()), "X");
356 print("FILLED. SAVE...\n");
357 db_save(db, "foo.db");
358 print("SAVED. CLOSE...\n");
363 // Multiline text file buffers
364 float buf_load(string pFilename)
371 fh = fopen(pFilename, FILE_READ);
378 while((l = fgets(fh)))
380 bufstr_set(buf, i, l);
387 void buf_save(float buf, string pFilename)
390 fh = fopen(pFilename, FILE_WRITE);
392 error(strcat("Can't write buf to ", pFilename));
393 n = buf_getsize(buf);
394 for(i = 0; i < n; ++i)
395 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
399 string mmsss(float tenths)
403 tenths = floor(tenths + 0.5);
404 minutes = floor(tenths / 600);
405 tenths -= minutes * 600;
406 s = ftos(1000 + tenths);
407 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
410 string mmssss(float hundredths)
414 hundredths = floor(hundredths + 0.5);
415 minutes = floor(hundredths / 6000);
416 hundredths -= minutes * 6000;
417 s = ftos(10000 + hundredths);
418 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
421 string ScoreString(float pFlags, float pValue)
426 pValue = floor(pValue + 0.5); // round
428 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
430 else if(pFlags & SFL_RANK)
432 valstr = ftos(pValue);
434 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
435 valstr = strcat(valstr, "th");
436 else if(substring(valstr, l - 1, 1) == "1")
437 valstr = strcat(valstr, "st");
438 else if(substring(valstr, l - 1, 1) == "2")
439 valstr = strcat(valstr, "nd");
440 else if(substring(valstr, l - 1, 1) == "3")
441 valstr = strcat(valstr, "rd");
443 valstr = strcat(valstr, "th");
445 else if(pFlags & SFL_TIME)
446 valstr = TIME_ENCODED_TOSTRING(pValue);
448 valstr = ftos(pValue);
453 vector cross(vector a, vector b)
456 '1 0 0' * (a_y * b_z - a_z * b_y)
457 + '0 1 0' * (a_z * b_x - a_x * b_z)
458 + '0 0 1' * (a_x * b_y - a_y * b_x);
461 // compressed vector format:
462 // like MD3, just even shorter
463 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
464 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
465 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
466 // length = 2^(length_encoded/8) / 8
467 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
468 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
469 // the special value 0 indicates the zero vector
471 float lengthLogTable[128];
473 float invertLengthLog(float x)
475 float l, r, m, lerr, rerr;
477 if(x >= lengthLogTable[127])
479 if(x <= lengthLogTable[0])
487 m = floor((l + r) / 2);
488 if(lengthLogTable[m] < x)
494 // now: r is >=, l is <
495 lerr = (x - lengthLogTable[l]);
496 rerr = (lengthLogTable[r] - x);
502 vector decompressShortVector(float data)
508 p = (data & 0xF000) / 0x1000;
509 y = (data & 0x0F80) / 0x80;
510 len = (data & 0x007F);
512 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
525 y = .19634954084936207740 * y;
526 p = .19634954084936207740 * p - 1.57079632679489661922;
527 out_x = cos(y) * cos(p);
528 out_y = sin(y) * cos(p);
532 //print("decompressed: ", vtos(out), "\n");
534 return out * lengthLogTable[len];
537 float compressShortVector(vector vec)
543 //print("compress: ", vtos(vec), "\n");
544 ang = vectoangles(vec);
548 if(ang_x < -90 && ang_x > +90)
549 error("BOGUS vectoangles");
550 //print("angles: ", vtos(ang), "\n");
552 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
561 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
562 len = invertLengthLog(vlen(vec));
564 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
566 return (p * 0x1000) + (y * 0x80) + len;
569 void compressShortVector_init()
574 for(i = 0; i < 128; ++i)
576 lengthLogTable[i] = l;
580 if(cvar("developer"))
582 print("Verifying vector compression table...\n");
583 for(i = 0x0F00; i < 0xFFFF; ++i)
584 if(i != compressShortVector(decompressShortVector(i)))
586 print("BROKEN vector compression: ", ftos(i));
587 print(" -> ", vtos(decompressShortVector(i)));
588 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
597 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
599 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
600 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
601 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
602 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
603 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
604 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
605 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
606 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
607 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
608 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
609 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
610 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
615 string fixPriorityList(string order, float from, float to, float subtract, float complete)
620 n = tokenize_console(order);
622 for(i = 0; i < n; ++i)
627 if(w >= from && w <= to)
628 neworder = strcat(neworder, ftos(w), " ");
632 if(w >= from && w <= to)
633 neworder = strcat(neworder, ftos(w), " ");
640 n = tokenize_console(neworder);
641 for(w = to; w >= from; --w)
643 for(i = 0; i < n; ++i)
644 if(stof(argv(i)) == w)
646 if(i == n) // not found
647 neworder = strcat(neworder, ftos(w), " ");
651 return substring(neworder, 0, strlen(neworder) - 1);
654 string mapPriorityList(string order, string(string) mapfunc)
659 n = tokenize_console(order);
661 for(i = 0; i < n; ++i)
662 neworder = strcat(neworder, mapfunc(argv(i)), " ");
664 return substring(neworder, 0, strlen(neworder) - 1);
667 string swapInPriorityList(string order, float i, float j)
672 n = tokenize_console(order);
674 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
677 for(w = 0; w < n; ++w)
680 s = strcat(s, argv(j), " ");
682 s = strcat(s, argv(i), " ");
684 s = strcat(s, argv(w), " ");
686 return substring(s, 0, strlen(s) - 1);
692 float cvar_value_issafe(string s)
694 if(strstrofs(s, "\"", 0) >= 0)
696 if(strstrofs(s, "\\", 0) >= 0)
698 if(strstrofs(s, ";", 0) >= 0)
700 if(strstrofs(s, "$", 0) >= 0)
702 if(strstrofs(s, "\r", 0) >= 0)
704 if(strstrofs(s, "\n", 0) >= 0)
710 void get_mi_min_max(float mode)
715 strunzone(mi_shortname);
716 mi_shortname = mapname;
717 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
718 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
719 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
720 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
721 mi_shortname = strzone(mi_shortname);
733 MapInfo_Get_ByName(mi_shortname, 0, 0);
734 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
736 mi_min = MapInfo_Map_mins;
737 mi_max = MapInfo_Map_maxs;
745 tracebox('1 0 0' * mi_x,
746 '0 1 0' * mi_y + '0 0 1' * mi_z,
747 '0 1 0' * ma_y + '0 0 1' * ma_z,
751 if(!trace_startsolid)
752 mi_min_x = trace_endpos_x;
754 tracebox('0 1 0' * mi_y,
755 '1 0 0' * mi_x + '0 0 1' * mi_z,
756 '1 0 0' * ma_x + '0 0 1' * ma_z,
760 if(!trace_startsolid)
761 mi_min_y = trace_endpos_y;
763 tracebox('0 0 1' * mi_z,
764 '1 0 0' * mi_x + '0 1 0' * mi_y,
765 '1 0 0' * ma_x + '0 1 0' * ma_y,
769 if(!trace_startsolid)
770 mi_min_z = trace_endpos_z;
772 tracebox('1 0 0' * ma_x,
773 '0 1 0' * mi_y + '0 0 1' * mi_z,
774 '0 1 0' * ma_y + '0 0 1' * ma_z,
778 if(!trace_startsolid)
779 mi_max_x = trace_endpos_x;
781 tracebox('0 1 0' * ma_y,
782 '1 0 0' * mi_x + '0 0 1' * mi_z,
783 '1 0 0' * ma_x + '0 0 1' * ma_z,
787 if(!trace_startsolid)
788 mi_max_y = trace_endpos_y;
790 tracebox('0 0 1' * ma_z,
791 '1 0 0' * mi_x + '0 1 0' * mi_y,
792 '1 0 0' * ma_x + '0 1 0' * ma_y,
796 if(!trace_startsolid)
797 mi_max_z = trace_endpos_z;
802 void get_mi_min_max_texcoords(float mode)
806 get_mi_min_max(mode);
811 // extend mi_picmax to get a square aspect ratio
812 // center the map in that area
813 extend = mi_picmax - mi_picmin;
814 if(extend_y > extend_x)
816 mi_picmin_x -= (extend_y - extend_x) * 0.5;
817 mi_picmax_x += (extend_y - extend_x) * 0.5;
821 mi_picmin_y -= (extend_x - extend_y) * 0.5;
822 mi_picmax_y += (extend_x - extend_y) * 0.5;
825 // add another some percent
826 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
830 // calculate the texcoords
831 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
832 // first the two corners of the origin
833 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
834 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
835 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
836 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
837 // then the other corners
838 mi_pictexcoord1_x = mi_pictexcoord0_x;
839 mi_pictexcoord1_y = mi_pictexcoord2_y;
840 mi_pictexcoord3_x = mi_pictexcoord2_x;
841 mi_pictexcoord3_y = mi_pictexcoord0_y;
845 float cvar_settemp(string tmp_cvar, string tmp_value)
847 float created_saved_value;
850 if not(tmp_cvar || tmp_value)
852 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
856 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
857 if(e.netname == tmp_cvar)
858 goto saved; // skip creation
860 // creating a new entity to keep track of this cvar
862 e.classname = "saved_cvar_value";
863 e.netname = strzone(tmp_cvar);
864 e.message = strzone(cvar_string(tmp_cvar));
865 created_saved_value = TRUE;
867 // an entity for this cvar already exists
870 // update the cvar to the value given
871 cvar_set(tmp_cvar, tmp_value);
873 return created_saved_value;
876 float cvar_settemp_restore()
880 while((e = find(world, classname, "saved_cvar_value")))
882 cvar_set(e.netname, e.message);
889 float almost_equals(float a, float b)
892 eps = (max(a, -a) + max(b, -b)) * 0.001;
893 if(a - b < eps && b - a < eps)
898 float almost_in_bounds(float a, float b, float c)
901 eps = (max(a, -a) + max(c, -c)) * 0.001;
902 return b == median(a - eps, b, c + eps);
905 float power2of(float e)
909 float log2of(float x)
911 // NOTE: generated code
984 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
991 return (rgb_y - rgb_z) / (ma - mi);
993 return (rgb_y - rgb_z) / (ma - mi) + 6;
996 return (rgb_z - rgb_x) / (ma - mi) + 2;
997 else // if(ma == rgb_z)
998 return (rgb_x - rgb_y) / (ma - mi) + 4;
1001 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1005 hue -= 6 * floor(hue / 6);
1007 //else if(ma == rgb_x)
1008 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1012 rgb_y = hue * (ma - mi) + mi;
1015 //else if(ma == rgb_y)
1016 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1019 rgb_x = (2 - hue) * (ma - mi) + mi;
1027 rgb_z = (hue - 2) * (ma - mi) + mi;
1029 //else // if(ma == rgb_z)
1030 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1034 rgb_y = (4 - hue) * (ma - mi) + mi;
1039 rgb_x = (hue - 4) * (ma - mi) + mi;
1043 //else if(ma == rgb_x)
1044 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1045 else // if(hue <= 6)
1049 rgb_z = (6 - hue) * (ma - mi) + mi;
1055 vector rgb_to_hsv(vector rgb)
1060 mi = min(rgb_x, rgb_y, rgb_z);
1061 ma = max(rgb_x, rgb_y, rgb_z);
1063 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1074 vector hsv_to_rgb(vector hsv)
1076 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1079 vector rgb_to_hsl(vector rgb)
1084 mi = min(rgb_x, rgb_y, rgb_z);
1085 ma = max(rgb_x, rgb_y, rgb_z);
1087 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1089 hsl_z = 0.5 * (mi + ma);
1092 else if(hsl_z <= 0.5)
1093 hsl_y = (ma - mi) / (2*hsl_z);
1094 else // if(hsl_z > 0.5)
1095 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1100 vector hsl_to_rgb(vector hsl)
1102 float mi, ma, maminusmi;
1105 maminusmi = hsl_y * 2 * hsl_z;
1107 maminusmi = hsl_y * (2 - 2 * hsl_z);
1109 // hsl_z = 0.5 * mi + 0.5 * ma
1110 // maminusmi = - mi + ma
1111 mi = hsl_z - 0.5 * maminusmi;
1112 ma = hsl_z + 0.5 * maminusmi;
1114 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1117 string rgb_to_hexcolor(vector rgb)
1122 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1123 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1124 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1128 // requires that m2>m1 in all coordinates, and that m4>m3
1129 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;}
1131 // requires the same, but is a stronger condition
1132 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;}
1137 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1140 // The following function is SLOW.
1141 // For your safety and for the protection of those around you...
1142 // DO NOT CALL THIS AT HOME.
1143 // No really, don't.
1144 if(w(theText, theSize) <= maxWidth)
1145 return strlen(theText); // yeah!
1147 // binary search for right place to cut string
1149 float left, right, middle; // this always works
1151 right = strlen(theText); // this always fails
1154 middle = floor((left + right) / 2);
1155 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1160 while(left < right - 1);
1162 if(w("^7", theSize) == 0) // detect color codes support in the width function
1164 // NOTE: when color codes are involved, this binary search is,
1165 // mathematically, BROKEN. However, it is obviously guaranteed to
1166 // terminate, as the range still halves each time - but nevertheless, it is
1167 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1168 // range, and "right" is outside).
1170 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1171 // and decrease left on the basis of the chars detected of the truncated tag
1172 // Even if the ^xrgb tag is not complete/correct, left is decreased
1173 // (sometimes too much but with a correct result)
1174 // it fixes also ^[0-9]
1175 while(left >= 1 && substring(theText, left-1, 1) == "^")
1178 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1180 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1182 ch = str2chr(theText, left-1);
1183 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1186 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1188 ch = str2chr(theText, left-2);
1189 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1191 ch = str2chr(theText, left-1);
1192 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1201 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1204 // The following function is SLOW.
1205 // For your safety and for the protection of those around you...
1206 // DO NOT CALL THIS AT HOME.
1207 // No really, don't.
1208 if(w(theText) <= maxWidth)
1209 return strlen(theText); // yeah!
1211 // binary search for right place to cut string
1213 float left, right, middle; // this always works
1215 right = strlen(theText); // this always fails
1218 middle = floor((left + right) / 2);
1219 if(w(substring(theText, 0, middle)) <= maxWidth)
1224 while(left < right - 1);
1226 if(w("^7") == 0) // detect color codes support in the width function
1228 // NOTE: when color codes are involved, this binary search is,
1229 // mathematically, BROKEN. However, it is obviously guaranteed to
1230 // terminate, as the range still halves each time - but nevertheless, it is
1231 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1232 // range, and "right" is outside).
1234 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1235 // and decrease left on the basis of the chars detected of the truncated tag
1236 // Even if the ^xrgb tag is not complete/correct, left is decreased
1237 // (sometimes too much but with a correct result)
1238 // it fixes also ^[0-9]
1239 while(left >= 1 && substring(theText, left-1, 1) == "^")
1242 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1244 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1246 ch = str2chr(theText, left-1);
1247 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1250 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1252 ch = str2chr(theText, left-2);
1253 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1255 ch = str2chr(theText, left-1);
1256 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1265 string find_last_color_code(string s)
1267 float start, len, i, carets;
1268 start = strstrofs(s, "^", 0);
1269 if (start == -1) // no caret found
1272 for(i = len; i >= start; --i)
1274 if(substring(s, i, 1) != "^")
1278 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1281 // check if carets aren't all escaped
1282 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1285 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1286 return substring(s, i, 2);
1289 if(substring(s, i+1, 1) == "x")
1290 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1291 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1292 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1293 return substring(s, i, 5);
1295 i -= carets; // this also skips one char before the carets
1301 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1307 s = getWrappedLine_remaining;
1311 getWrappedLine_remaining = string_null;
1312 return s; // the line has no size ANYWAY, nothing would be displayed.
1315 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1316 if(cantake > 0 && cantake < strlen(s))
1319 while(take > 0 && substring(s, take, 1) != " ")
1323 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1324 if(getWrappedLine_remaining == "")
1325 getWrappedLine_remaining = string_null;
1326 else if (tw("^7", theFontSize) == 0)
1327 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1328 return substring(s, 0, cantake);
1332 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1333 if(getWrappedLine_remaining == "")
1334 getWrappedLine_remaining = string_null;
1335 else if (tw("^7", theFontSize) == 0)
1336 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1337 return substring(s, 0, take);
1342 getWrappedLine_remaining = string_null;
1347 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1353 s = getWrappedLine_remaining;
1357 getWrappedLine_remaining = string_null;
1358 return s; // the line has no size ANYWAY, nothing would be displayed.
1361 cantake = textLengthUpToLength(s, w, tw);
1362 if(cantake > 0 && cantake < strlen(s))
1365 while(take > 0 && substring(s, take, 1) != " ")
1369 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1370 if(getWrappedLine_remaining == "")
1371 getWrappedLine_remaining = string_null;
1372 else if (tw("^7") == 0)
1373 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1374 return substring(s, 0, cantake);
1378 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1379 if(getWrappedLine_remaining == "")
1380 getWrappedLine_remaining = string_null;
1381 else if (tw("^7") == 0)
1382 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1383 return substring(s, 0, take);
1388 getWrappedLine_remaining = string_null;
1393 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1395 if(tw(theText, theFontSize) <= maxWidth)
1398 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1401 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1403 if(tw(theText) <= maxWidth)
1406 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1409 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1411 string subpattern, subpattern2, subpattern3, subpattern4;
1412 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1414 subpattern2 = ",teams,";
1416 subpattern2 = ",noteams,";
1418 subpattern3 = ",teamspawns,";
1420 subpattern3 = ",noteamspawns,";
1421 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1422 subpattern4 = ",race,";
1424 subpattern4 = string_null;
1426 if(substring(pattern, 0, 1) == "-")
1428 pattern = substring(pattern, 1, strlen(pattern) - 1);
1429 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1431 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1433 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1435 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1440 if(substring(pattern, 0, 1) == "+")
1441 pattern = substring(pattern, 1, strlen(pattern) - 1);
1442 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1443 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1444 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1445 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1451 void shuffle(float n, swapfunc_t swap, entity pass)
1454 for(i = 1; i < n; ++i)
1456 // swap i-th item at a random position from 0 to i
1457 // proof for even distribution:
1460 // item n+1 gets at any position with chance 1/(n+1)
1461 // all others will get their 1/n chance reduced by factor n/(n+1)
1462 // to be on place n+1, their chance will be 1/(n+1)
1463 // 1/n * n/(n+1) = 1/(n+1)
1465 j = floor(random() * (i + 1));
1471 string substring_range(string s, float b, float e)
1473 return substring(s, b, e - b);
1476 string swapwords(string str, float i, float j)
1479 string s1, s2, s3, s4, s5;
1480 float si, ei, sj, ej, s0, en;
1481 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1482 si = argv_start_index(i);
1483 sj = argv_start_index(j);
1484 ei = argv_end_index(i);
1485 ej = argv_end_index(j);
1486 s0 = argv_start_index(0);
1487 en = argv_end_index(n-1);
1488 s1 = substring_range(str, s0, si);
1489 s2 = substring_range(str, si, ei);
1490 s3 = substring_range(str, ei, sj);
1491 s4 = substring_range(str, sj, ej);
1492 s5 = substring_range(str, ej, en);
1493 return strcat(s1, s4, s3, s2, s5);
1496 string _shufflewords_str;
1497 void _shufflewords_swapfunc(float i, float j, entity pass)
1499 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1501 string shufflewords(string str)
1504 _shufflewords_str = str;
1505 n = tokenizebyseparator(str, " ");
1506 shuffle(n, _shufflewords_swapfunc, world);
1507 str = _shufflewords_str;
1508 _shufflewords_str = string_null;
1512 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1528 // actually, every number solves the equation!
1539 if(a > 0) // put the smaller solution first
1541 v_x = ((-b)-D) / (2*a);
1542 v_y = ((-b)+D) / (2*a);
1546 v_x = (-b+D) / (2*a);
1547 v_y = (-b-D) / (2*a);
1553 // complex solutions!
1566 void check_unacceptable_compiler_bugs()
1568 if(cvar("_allow_unacceptable_compiler_bugs"))
1570 tokenize_console("foo bar");
1571 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1572 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.");
1575 float compressShotOrigin(vector v)
1579 y = rint(v_y * 4) + 128;
1580 z = rint(v_z * 4) + 128;
1581 if(x > 255 || x < 0)
1583 print("shot origin ", vtos(v), " x out of bounds\n");
1584 x = bound(0, x, 255);
1586 if(y > 255 || y < 0)
1588 print("shot origin ", vtos(v), " y out of bounds\n");
1589 y = bound(0, y, 255);
1591 if(z > 255 || z < 0)
1593 print("shot origin ", vtos(v), " z out of bounds\n");
1594 z = bound(0, z, 255);
1596 return x * 0x10000 + y * 0x100 + z;
1598 vector decompressShotOrigin(float f)
1601 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1602 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1603 v_z = ((f & 0xFF) - 128) / 4;
1607 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1609 float start, end, root, child;
1612 start = floor((n - 2) / 2);
1615 // siftdown(start, count-1);
1617 while(root * 2 + 1 <= n-1)
1619 child = root * 2 + 1;
1621 if(cmp(child, child+1, pass) < 0)
1623 if(cmp(root, child, pass) < 0)
1625 swap(root, child, pass);
1641 // siftdown(0, end);
1643 while(root * 2 + 1 <= end)
1645 child = root * 2 + 1;
1646 if(child < end && cmp(child, child+1, pass) < 0)
1648 if(cmp(root, child, pass) < 0)
1650 swap(root, child, pass);
1660 void RandomSelection_Init()
1662 RandomSelection_totalweight = 0;
1663 RandomSelection_chosen_ent = world;
1664 RandomSelection_chosen_float = 0;
1665 RandomSelection_chosen_string = string_null;
1666 RandomSelection_best_priority = -1;
1668 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1670 if(priority > RandomSelection_best_priority)
1672 RandomSelection_best_priority = priority;
1673 RandomSelection_chosen_ent = e;
1674 RandomSelection_chosen_float = f;
1675 RandomSelection_chosen_string = s;
1676 RandomSelection_totalweight = weight;
1678 else if(priority == RandomSelection_best_priority)
1680 RandomSelection_totalweight += weight;
1681 if(random() * RandomSelection_totalweight <= weight)
1683 RandomSelection_chosen_ent = e;
1684 RandomSelection_chosen_float = f;
1685 RandomSelection_chosen_string = s;
1690 vector healtharmor_maxdamage(float h, float a, float armorblock)
1692 // NOTE: we'll always choose the SMALLER value...
1693 float healthdamage, armordamage, armorideal;
1695 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1696 armordamage = a + (h - 1); // damage we can take if we could use more armor
1697 armorideal = healthdamage * armorblock;
1699 if(armordamage < healthdamage)
1712 vector healtharmor_applydamage(float a, float armorblock, float damage)
1715 v_y = bound(0, damage * armorblock, a); // save
1716 v_x = bound(0, damage - v_y, damage); // take
1721 string getcurrentmod()
1725 m = cvar_string("fs_gamedir");
1726 n = tokenize_console(m);
1738 v = ReadShort() * 256; // note: this is signed
1739 v += ReadByte(); // note: this is unsigned
1743 void WriteInt24_t(float dst, float val)
1746 WriteShort(dst, (v = floor(val / 256)));
1747 WriteByte(dst, val - v * 256); // 0..255
1752 float float2range11(float f)
1754 // continuous function mapping all reals into -1..1
1755 return f / (fabs(f) + 1);
1758 float float2range01(float f)
1760 // continuous function mapping all reals into 0..1
1761 return 0.5 + 0.5 * float2range11(f);
1764 // from the GNU Scientific Library
1765 float gsl_ran_gaussian_lastvalue;
1766 float gsl_ran_gaussian_lastvalue_set;
1767 float gsl_ran_gaussian(float sigma)
1770 if(gsl_ran_gaussian_lastvalue_set)
1772 gsl_ran_gaussian_lastvalue_set = 0;
1773 return sigma * gsl_ran_gaussian_lastvalue;
1777 a = random() * 2 * M_PI;
1778 b = sqrt(-2 * log(random()));
1779 gsl_ran_gaussian_lastvalue = cos(a) * b;
1780 gsl_ran_gaussian_lastvalue_set = 1;
1781 return sigma * sin(a) * b;
1785 string car(string s)
1788 o = strstrofs(s, " ", 0);
1791 return substring(s, 0, o);
1793 string cdr(string s)
1796 o = strstrofs(s, " ", 0);
1799 return substring(s, o + 1, strlen(s) - (o + 1));
1801 float matchacl(string acl, string str)
1808 t = car(acl); acl = cdr(acl);
1810 if(substring(t, 0, 1) == "-")
1813 t = substring(t, 1, strlen(t) - 1);
1815 else if(substring(t, 0, 1) == "+")
1816 t = substring(t, 1, strlen(t) - 1);
1817 if(substring(t, -1, 1) == "*")
1819 t = substring(t, 0, strlen(t) - 1);
1820 s = substring(s, 0, strlen(t));
1832 float startsWith(string haystack, string needle)
1834 return substring(haystack, 0, strlen(needle)) == needle;
1836 float startsWithNocase(string haystack, string needle)
1838 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1841 string get_model_datafilename(string m, float sk, string fil)
1846 m = "models/player/*_";
1848 m = strcat(m, ftos(sk));
1851 return strcat(m, ".", fil);
1854 float get_model_parameters(string m, float sk)
1859 get_model_parameters_modelname = string_null;
1860 get_model_parameters_modelskin = -1;
1861 get_model_parameters_name = string_null;
1862 get_model_parameters_species = -1;
1863 get_model_parameters_sex = string_null;
1864 get_model_parameters_weight = -1;
1865 get_model_parameters_age = -1;
1866 get_model_parameters_desc = string_null;
1872 if(substring(m, -4, -1) != ".txt")
1874 if(substring(m, -6, 1) != "_")
1876 sk = stof(substring(m, -5, 1));
1877 m = substring(m, 0, -7);
1880 fn = get_model_datafilename(m, sk, "txt");
1881 fh = fopen(fn, FILE_READ);
1885 fn = get_model_datafilename(m, sk, "txt");
1886 fh = fopen(fn, FILE_READ);
1891 get_model_parameters_modelname = m;
1892 get_model_parameters_modelskin = sk;
1893 while((s = fgets(fh)))
1896 break; // next lines will be description
1900 get_model_parameters_name = s;
1904 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1905 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1906 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1907 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1908 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1909 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1910 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1913 get_model_parameters_sex = s;
1915 get_model_parameters_weight = stof(s);
1917 get_model_parameters_age = stof(s);
1920 while((s = fgets(fh)))
1922 if(get_model_parameters_desc)
1923 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1925 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1933 vector vec2(vector v)
1940 vector NearestPointOnBox(entity box, vector org)
1942 vector m1, m2, nearest;
1944 m1 = box.mins + box.origin;
1945 m2 = box.maxs + box.origin;
1947 nearest_x = bound(m1_x, org_x, m2_x);
1948 nearest_y = bound(m1_y, org_y, m2_y);
1949 nearest_z = bound(m1_z, org_z, m2_z);
1955 float vercmp_recursive(string v1, string v2)
1961 dot1 = strstrofs(v1, ".", 0);
1962 dot2 = strstrofs(v2, ".", 0);
1966 s1 = substring(v1, 0, dot1);
1970 s2 = substring(v2, 0, dot2);
1972 r = stof(s1) - stof(s2);
1976 r = strcasecmp(s1, s2);
1989 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1992 float vercmp(string v1, string v2)
1994 if(strcasecmp(v1, v2) == 0) // early out check
2003 return vercmp_recursive(v1, v2);
2006 float u8_strsize(string s)
2026 // translation helpers
2027 string language_filename(string s)
2032 if(fn == "" || fn == "dump")
2034 fn = strcat(s, ".", fn);
2035 if((fh = fopen(fn, FILE_READ)) >= 0)
2042 string CTX(string s)
2044 float p = strstrofs(s, "^", 0);
2047 return substring(s, p+1, -1);
2050 // x-encoding (encoding as zero length invisible string)
2051 const string XENCODE_2 = "xX";
2052 const string XENCODE_22 = "0123456789abcdefABCDEF";
2053 string xencode(float f)
2056 d = mod(f, 22); f = floor(f / 22);
2057 c = mod(f, 22); f = floor(f / 22);
2058 b = mod(f, 22); f = floor(f / 22);
2059 a = mod(f, 2); // f = floor(f / 2);
2062 substring(XENCODE_2, a, 1),
2063 substring(XENCODE_22, b, 1),
2064 substring(XENCODE_22, c, 1),
2065 substring(XENCODE_22, d, 1)
2068 float xdecode(string s)
2071 if(substring(s, 0, 1) != "^")
2075 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2076 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2077 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2078 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2079 if(a < 0 || b < 0 || c < 0 || d < 0)
2081 return ((a * 22 + b) * 22 + c) * 22 + d;
2084 float lowestbit(float f)
2095 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2097 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2100 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2103 // escape the string to make it safe for consoles
2104 string MakeConsoleSafe(string input)
2106 input = strreplace("\n", "", input);
2107 input = strreplace("\\", "\\\\", input);
2108 input = strreplace("$", "$$", input);
2109 input = strreplace("\"", "\\\"", input);
2114 // get true/false value of a string with multiple different inputs
2115 float InterpretBoolean(string input)
2117 switch(strtolower(input))
2129 default: return stof(input);
2135 entity ReadCSQCEntity()
2141 return findfloat(world, entnum, f);
2145 float shutdown_running;
2150 void CSQC_Shutdown()
2156 if(shutdown_running)
2158 print("Recursive shutdown detected! Only restoring cvars...\n");
2162 shutdown_running = 1;
2165 cvar_settemp_restore(); // this must be done LAST, but in any case