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 void cvar_settemp(string cv, string val)
848 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
852 e.classname = "saved_cvar_value";
853 e.netname = strzone(cv);
854 e.message = strzone(cvar_string(cv));
859 void cvar_settemp_restore()
862 while((e = find(world, classname, "saved_cvar_value")))
864 cvar_set(e.netname, e.message);
869 float almost_equals(float a, float b)
872 eps = (max(a, -a) + max(b, -b)) * 0.001;
873 if(a - b < eps && b - a < eps)
878 float almost_in_bounds(float a, float b, float c)
881 eps = (max(a, -a) + max(c, -c)) * 0.001;
882 return b == median(a - eps, b, c + eps);
885 float power2of(float e)
889 float log2of(float x)
891 // NOTE: generated code
964 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
971 return (rgb_y - rgb_z) / (ma - mi);
973 return (rgb_y - rgb_z) / (ma - mi) + 6;
976 return (rgb_z - rgb_x) / (ma - mi) + 2;
977 else // if(ma == rgb_z)
978 return (rgb_x - rgb_y) / (ma - mi) + 4;
981 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
985 hue -= 6 * floor(hue / 6);
987 //else if(ma == rgb_x)
988 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
992 rgb_y = hue * (ma - mi) + mi;
995 //else if(ma == rgb_y)
996 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
999 rgb_x = (2 - hue) * (ma - mi) + mi;
1007 rgb_z = (hue - 2) * (ma - mi) + mi;
1009 //else // if(ma == rgb_z)
1010 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1014 rgb_y = (4 - hue) * (ma - mi) + mi;
1019 rgb_x = (hue - 4) * (ma - mi) + mi;
1023 //else if(ma == rgb_x)
1024 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1025 else // if(hue <= 6)
1029 rgb_z = (6 - hue) * (ma - mi) + mi;
1035 vector rgb_to_hsv(vector rgb)
1040 mi = min(rgb_x, rgb_y, rgb_z);
1041 ma = max(rgb_x, rgb_y, rgb_z);
1043 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1054 vector hsv_to_rgb(vector hsv)
1056 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1059 vector rgb_to_hsl(vector rgb)
1064 mi = min(rgb_x, rgb_y, rgb_z);
1065 ma = max(rgb_x, rgb_y, rgb_z);
1067 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1069 hsl_z = 0.5 * (mi + ma);
1072 else if(hsl_z <= 0.5)
1073 hsl_y = (ma - mi) / (2*hsl_z);
1074 else // if(hsl_z > 0.5)
1075 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1080 vector hsl_to_rgb(vector hsl)
1082 float mi, ma, maminusmi;
1085 maminusmi = hsl_y * 2 * hsl_z;
1087 maminusmi = hsl_y * (2 - 2 * hsl_z);
1089 // hsl_z = 0.5 * mi + 0.5 * ma
1090 // maminusmi = - mi + ma
1091 mi = hsl_z - 0.5 * maminusmi;
1092 ma = hsl_z + 0.5 * maminusmi;
1094 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1097 string rgb_to_hexcolor(vector rgb)
1102 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1103 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1104 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1108 // requires that m2>m1 in all coordinates, and that m4>m3
1109 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;}
1111 // requires the same, but is a stronger condition
1112 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;}
1117 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1120 // The following function is SLOW.
1121 // For your safety and for the protection of those around you...
1122 // DO NOT CALL THIS AT HOME.
1123 // No really, don't.
1124 if(w(theText, theSize) <= maxWidth)
1125 return strlen(theText); // yeah!
1127 // binary search for right place to cut string
1129 float left, right, middle; // this always works
1131 right = strlen(theText); // this always fails
1134 middle = floor((left + right) / 2);
1135 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1140 while(left < right - 1);
1142 if(w("^7", theSize) == 0) // detect color codes support in the width function
1144 // NOTE: when color codes are involved, this binary search is,
1145 // mathematically, BROKEN. However, it is obviously guaranteed to
1146 // terminate, as the range still halves each time - but nevertheless, it is
1147 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1148 // range, and "right" is outside).
1150 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1151 // and decrease left on the basis of the chars detected of the truncated tag
1152 // Even if the ^xrgb tag is not complete/correct, left is decreased
1153 // (sometimes too much but with a correct result)
1154 // it fixes also ^[0-9]
1155 while(left >= 1 && substring(theText, left-1, 1) == "^")
1158 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1160 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1162 ch = str2chr(theText, left-1);
1163 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1166 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1168 ch = str2chr(theText, left-2);
1169 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1171 ch = str2chr(theText, left-1);
1172 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1181 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1184 // The following function is SLOW.
1185 // For your safety and for the protection of those around you...
1186 // DO NOT CALL THIS AT HOME.
1187 // No really, don't.
1188 if(w(theText) <= maxWidth)
1189 return strlen(theText); // yeah!
1191 // binary search for right place to cut string
1193 float left, right, middle; // this always works
1195 right = strlen(theText); // this always fails
1198 middle = floor((left + right) / 2);
1199 if(w(substring(theText, 0, middle)) <= maxWidth)
1204 while(left < right - 1);
1206 if(w("^7") == 0) // detect color codes support in the width function
1208 // NOTE: when color codes are involved, this binary search is,
1209 // mathematically, BROKEN. However, it is obviously guaranteed to
1210 // terminate, as the range still halves each time - but nevertheless, it is
1211 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1212 // range, and "right" is outside).
1214 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1215 // and decrease left on the basis of the chars detected of the truncated tag
1216 // Even if the ^xrgb tag is not complete/correct, left is decreased
1217 // (sometimes too much but with a correct result)
1218 // it fixes also ^[0-9]
1219 while(left >= 1 && substring(theText, left-1, 1) == "^")
1222 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1224 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1226 ch = str2chr(theText, left-1);
1227 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1230 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1232 ch = str2chr(theText, left-2);
1233 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1235 ch = str2chr(theText, left-1);
1236 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1245 string find_last_color_code(string s)
1247 float start, len, i, carets;
1248 start = strstrofs(s, "^", 0);
1249 if (start == -1) // no caret found
1252 for(i = len; i >= start; --i)
1254 if(substring(s, i, 1) != "^")
1258 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1261 // check if carets aren't all escaped
1262 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1265 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1266 return substring(s, i, 2);
1269 if(substring(s, i+1, 1) == "x")
1270 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1271 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1272 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1273 return substring(s, i, 5);
1275 i -= carets; // this also skips one char before the carets
1281 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1287 s = getWrappedLine_remaining;
1291 getWrappedLine_remaining = string_null;
1292 return s; // the line has no size ANYWAY, nothing would be displayed.
1295 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1296 if(cantake > 0 && cantake < strlen(s))
1299 while(take > 0 && substring(s, take, 1) != " ")
1303 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1304 if(getWrappedLine_remaining == "")
1305 getWrappedLine_remaining = string_null;
1306 else if (tw("^7", theFontSize) == 0)
1307 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1308 return substring(s, 0, cantake);
1312 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1313 if(getWrappedLine_remaining == "")
1314 getWrappedLine_remaining = string_null;
1315 else if (tw("^7", theFontSize) == 0)
1316 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1317 return substring(s, 0, take);
1322 getWrappedLine_remaining = string_null;
1327 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1333 s = getWrappedLine_remaining;
1337 getWrappedLine_remaining = string_null;
1338 return s; // the line has no size ANYWAY, nothing would be displayed.
1341 cantake = textLengthUpToLength(s, w, tw);
1342 if(cantake > 0 && cantake < strlen(s))
1345 while(take > 0 && substring(s, take, 1) != " ")
1349 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1350 if(getWrappedLine_remaining == "")
1351 getWrappedLine_remaining = string_null;
1352 else if (tw("^7") == 0)
1353 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1354 return substring(s, 0, cantake);
1358 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1359 if(getWrappedLine_remaining == "")
1360 getWrappedLine_remaining = string_null;
1361 else if (tw("^7") == 0)
1362 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1363 return substring(s, 0, take);
1368 getWrappedLine_remaining = string_null;
1373 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1375 if(tw(theText, theFontSize) <= maxWidth)
1378 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1381 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1383 if(tw(theText) <= maxWidth)
1386 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1389 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1391 string subpattern, subpattern2, subpattern3, subpattern4;
1392 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1394 subpattern2 = ",teams,";
1396 subpattern2 = ",noteams,";
1398 subpattern3 = ",teamspawns,";
1400 subpattern3 = ",noteamspawns,";
1401 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1402 subpattern4 = ",race,";
1404 subpattern4 = string_null;
1406 if(substring(pattern, 0, 1) == "-")
1408 pattern = substring(pattern, 1, strlen(pattern) - 1);
1409 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1411 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1413 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1415 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1420 if(substring(pattern, 0, 1) == "+")
1421 pattern = substring(pattern, 1, strlen(pattern) - 1);
1422 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1423 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1424 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1425 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1431 void shuffle(float n, swapfunc_t swap, entity pass)
1434 for(i = 1; i < n; ++i)
1436 // swap i-th item at a random position from 0 to i
1437 // proof for even distribution:
1440 // item n+1 gets at any position with chance 1/(n+1)
1441 // all others will get their 1/n chance reduced by factor n/(n+1)
1442 // to be on place n+1, their chance will be 1/(n+1)
1443 // 1/n * n/(n+1) = 1/(n+1)
1445 j = floor(random() * (i + 1));
1451 string substring_range(string s, float b, float e)
1453 return substring(s, b, e - b);
1456 string swapwords(string str, float i, float j)
1459 string s1, s2, s3, s4, s5;
1460 float si, ei, sj, ej, s0, en;
1461 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1462 si = argv_start_index(i);
1463 sj = argv_start_index(j);
1464 ei = argv_end_index(i);
1465 ej = argv_end_index(j);
1466 s0 = argv_start_index(0);
1467 en = argv_end_index(n-1);
1468 s1 = substring_range(str, s0, si);
1469 s2 = substring_range(str, si, ei);
1470 s3 = substring_range(str, ei, sj);
1471 s4 = substring_range(str, sj, ej);
1472 s5 = substring_range(str, ej, en);
1473 return strcat(s1, s4, s3, s2, s5);
1476 string _shufflewords_str;
1477 void _shufflewords_swapfunc(float i, float j, entity pass)
1479 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1481 string shufflewords(string str)
1484 _shufflewords_str = str;
1485 n = tokenizebyseparator(str, " ");
1486 shuffle(n, _shufflewords_swapfunc, world);
1487 str = _shufflewords_str;
1488 _shufflewords_str = string_null;
1492 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1508 // actually, every number solves the equation!
1519 if(a > 0) // put the smaller solution first
1521 v_x = ((-b)-D) / (2*a);
1522 v_y = ((-b)+D) / (2*a);
1526 v_x = (-b+D) / (2*a);
1527 v_y = (-b-D) / (2*a);
1533 // complex solutions!
1546 void check_unacceptable_compiler_bugs()
1548 if(cvar("_allow_unacceptable_compiler_bugs"))
1550 tokenize_console("foo bar");
1551 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1552 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.");
1555 float compressShotOrigin(vector v)
1559 y = rint(v_y * 4) + 128;
1560 z = rint(v_z * 4) + 128;
1561 if(x > 255 || x < 0)
1563 print("shot origin ", vtos(v), " x out of bounds\n");
1564 x = bound(0, x, 255);
1566 if(y > 255 || y < 0)
1568 print("shot origin ", vtos(v), " y out of bounds\n");
1569 y = bound(0, y, 255);
1571 if(z > 255 || z < 0)
1573 print("shot origin ", vtos(v), " z out of bounds\n");
1574 z = bound(0, z, 255);
1576 return x * 0x10000 + y * 0x100 + z;
1578 vector decompressShotOrigin(float f)
1581 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1582 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1583 v_z = ((f & 0xFF) - 128) / 4;
1587 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1589 float start, end, root, child;
1592 start = floor((n - 2) / 2);
1595 // siftdown(start, count-1);
1597 while(root * 2 + 1 <= n-1)
1599 child = root * 2 + 1;
1601 if(cmp(child, child+1, pass) < 0)
1603 if(cmp(root, child, pass) < 0)
1605 swap(root, child, pass);
1621 // siftdown(0, end);
1623 while(root * 2 + 1 <= end)
1625 child = root * 2 + 1;
1626 if(child < end && cmp(child, child+1, pass) < 0)
1628 if(cmp(root, child, pass) < 0)
1630 swap(root, child, pass);
1640 void RandomSelection_Init()
1642 RandomSelection_totalweight = 0;
1643 RandomSelection_chosen_ent = world;
1644 RandomSelection_chosen_float = 0;
1645 RandomSelection_chosen_string = string_null;
1646 RandomSelection_best_priority = -1;
1648 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1650 if(priority > RandomSelection_best_priority)
1652 RandomSelection_best_priority = priority;
1653 RandomSelection_chosen_ent = e;
1654 RandomSelection_chosen_float = f;
1655 RandomSelection_chosen_string = s;
1656 RandomSelection_totalweight = weight;
1658 else if(priority == RandomSelection_best_priority)
1660 RandomSelection_totalweight += weight;
1661 if(random() * RandomSelection_totalweight <= weight)
1663 RandomSelection_chosen_ent = e;
1664 RandomSelection_chosen_float = f;
1665 RandomSelection_chosen_string = s;
1670 vector healtharmor_maxdamage(float h, float a, float armorblock)
1672 // NOTE: we'll always choose the SMALLER value...
1673 float healthdamage, armordamage, armorideal;
1675 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1676 armordamage = a + (h - 1); // damage we can take if we could use more armor
1677 armorideal = healthdamage * armorblock;
1679 if(armordamage < healthdamage)
1692 vector healtharmor_applydamage(float a, float armorblock, float damage)
1695 v_y = bound(0, damage * armorblock, a); // save
1696 v_x = bound(0, damage - v_y, damage); // take
1701 string getcurrentmod()
1705 m = cvar_string("fs_gamedir");
1706 n = tokenize_console(m);
1718 v = ReadShort() * 256; // note: this is signed
1719 v += ReadByte(); // note: this is unsigned
1723 void WriteInt24_t(float dst, float val)
1726 WriteShort(dst, (v = floor(val / 256)));
1727 WriteByte(dst, val - v * 256); // 0..255
1732 float float2range11(float f)
1734 // continuous function mapping all reals into -1..1
1735 return f / (fabs(f) + 1);
1738 float float2range01(float f)
1740 // continuous function mapping all reals into 0..1
1741 return 0.5 + 0.5 * float2range11(f);
1744 // from the GNU Scientific Library
1745 float gsl_ran_gaussian_lastvalue;
1746 float gsl_ran_gaussian_lastvalue_set;
1747 float gsl_ran_gaussian(float sigma)
1750 if(gsl_ran_gaussian_lastvalue_set)
1752 gsl_ran_gaussian_lastvalue_set = 0;
1753 return sigma * gsl_ran_gaussian_lastvalue;
1757 a = random() * 2 * M_PI;
1758 b = sqrt(-2 * log(random()));
1759 gsl_ran_gaussian_lastvalue = cos(a) * b;
1760 gsl_ran_gaussian_lastvalue_set = 1;
1761 return sigma * sin(a) * b;
1765 string car(string s)
1768 o = strstrofs(s, " ", 0);
1771 return substring(s, 0, o);
1773 string cdr(string s)
1776 o = strstrofs(s, " ", 0);
1779 return substring(s, o + 1, strlen(s) - (o + 1));
1781 float matchacl(string acl, string str)
1788 t = car(acl); acl = cdr(acl);
1790 if(substring(t, 0, 1) == "-")
1793 t = substring(t, 1, strlen(t) - 1);
1795 else if(substring(t, 0, 1) == "+")
1796 t = substring(t, 1, strlen(t) - 1);
1797 if(substring(t, -1, 1) == "*")
1799 t = substring(t, 0, strlen(t) - 1);
1800 s = substring(s, 0, strlen(t));
1812 float startsWith(string haystack, string needle)
1814 return substring(haystack, 0, strlen(needle)) == needle;
1816 float startsWithNocase(string haystack, string needle)
1818 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1821 string get_model_datafilename(string m, float sk, string fil)
1826 m = "models/player/*_";
1828 m = strcat(m, ftos(sk));
1831 return strcat(m, ".", fil);
1834 float get_model_parameters(string m, float sk)
1839 get_model_parameters_modelname = string_null;
1840 get_model_parameters_modelskin = -1;
1841 get_model_parameters_name = string_null;
1842 get_model_parameters_species = -1;
1843 get_model_parameters_sex = string_null;
1844 get_model_parameters_weight = -1;
1845 get_model_parameters_age = -1;
1846 get_model_parameters_desc = string_null;
1852 if(substring(m, -4, -1) != ".txt")
1854 if(substring(m, -6, 1) != "_")
1856 sk = stof(substring(m, -5, 1));
1857 m = substring(m, 0, -7);
1860 fn = get_model_datafilename(m, sk, "txt");
1861 fh = fopen(fn, FILE_READ);
1865 fn = get_model_datafilename(m, sk, "txt");
1866 fh = fopen(fn, FILE_READ);
1871 get_model_parameters_modelname = m;
1872 get_model_parameters_modelskin = sk;
1873 while((s = fgets(fh)))
1876 break; // next lines will be description
1880 get_model_parameters_name = s;
1884 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1885 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1886 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1887 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1888 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1889 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1890 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1893 get_model_parameters_sex = s;
1895 get_model_parameters_weight = stof(s);
1897 get_model_parameters_age = stof(s);
1900 while((s = fgets(fh)))
1902 if(get_model_parameters_desc)
1903 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1905 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1913 vector vec2(vector v)
1920 vector NearestPointOnBox(entity box, vector org)
1922 vector m1, m2, nearest;
1924 m1 = box.mins + box.origin;
1925 m2 = box.maxs + box.origin;
1927 nearest_x = bound(m1_x, org_x, m2_x);
1928 nearest_y = bound(m1_y, org_y, m2_y);
1929 nearest_z = bound(m1_z, org_z, m2_z);
1935 float vercmp_recursive(string v1, string v2)
1941 dot1 = strstrofs(v1, ".", 0);
1942 dot2 = strstrofs(v2, ".", 0);
1946 s1 = substring(v1, 0, dot1);
1950 s2 = substring(v2, 0, dot2);
1952 r = stof(s1) - stof(s2);
1956 r = strcasecmp(s1, s2);
1969 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1972 float vercmp(string v1, string v2)
1974 if(strcasecmp(v1, v2) == 0) // early out check
1983 return vercmp_recursive(v1, v2);
1986 float u8_strsize(string s)
2006 // translation helpers
2007 string language_filename(string s)
2012 if(fn == "" || fn == "dump")
2014 fn = strcat(s, ".", fn);
2015 if((fh = fopen(fn, FILE_READ)) >= 0)
2022 string CTX(string s)
2024 float p = strstrofs(s, "^", 0);
2027 return substring(s, p+1, -1);
2030 // x-encoding (encoding as zero length invisible string)
2031 const string XENCODE_2 = "xX";
2032 const string XENCODE_22 = "0123456789abcdefABCDEF";
2033 string xencode(float f)
2036 d = mod(f, 22); f = floor(f / 22);
2037 c = mod(f, 22); f = floor(f / 22);
2038 b = mod(f, 22); f = floor(f / 22);
2039 a = mod(f, 2); // f = floor(f / 2);
2042 substring(XENCODE_2, a, 1),
2043 substring(XENCODE_22, b, 1),
2044 substring(XENCODE_22, c, 1),
2045 substring(XENCODE_22, d, 1)
2048 float xdecode(string s)
2051 if(substring(s, 0, 1) != "^")
2055 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2056 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2057 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2058 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2059 if(a < 0 || b < 0 || c < 0 || d < 0)
2061 return ((a * 22 + b) * 22 + c) * 22 + d;
2064 float lowestbit(float f)
2075 entity ReadCSQCEntity()
2081 return findfloat(world, entnum, f);
2085 float shutdown_running;
2090 void CSQC_Shutdown()
2096 if(shutdown_running)
2098 print("Recursive shutdown detected! Only restoring cvars...\n");
2102 shutdown_running = 1;
2105 cvar_settemp_restore(); // this must be done LAST, but in any case