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 // inhibit stupid negative zero
202 // we have sprintf...
203 return sprintf("%.*f", decimals, number);
207 vector colormapPaletteColor(float c, float isPants)
211 case 0: return '1.000000 1.000000 1.000000';
212 case 1: return '1.000000 0.333333 0.000000';
213 case 2: return '0.000000 1.000000 0.501961';
214 case 3: return '0.000000 1.000000 0.000000';
215 case 4: return '1.000000 0.000000 0.000000';
216 case 5: return '0.000000 0.666667 1.000000';
217 case 6: return '0.000000 1.000000 1.000000';
218 case 7: return '0.501961 1.000000 0.000000';
219 case 8: return '0.501961 0.000000 1.000000';
220 case 9: return '1.000000 0.000000 1.000000';
221 case 10: return '1.000000 0.000000 0.501961';
222 case 11: return '0.000000 0.000000 1.000000';
223 case 12: return '1.000000 1.000000 0.000000';
224 case 13: return '0.000000 0.333333 1.000000';
225 case 14: return '1.000000 0.666667 0.000000';
229 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
230 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
231 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
234 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
235 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
236 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
237 default: return '0.000 0.000 0.000';
241 // unzone the string, and return it as tempstring. Safe to be called on string_null
242 string fstrunzone(string s)
252 float fexists(string f)
255 fh = fopen(f, FILE_READ);
262 // Databases (hash tables)
263 #define DB_BUCKETS 8192
264 void db_save(float db, string pFilename)
267 fh = fopen(pFilename, FILE_WRITE);
270 print(strcat("^1Can't write DB to ", pFilename));
274 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
275 for(i = 0; i < n; ++i)
276 fputs(fh, strcat(bufstr_get(db, i), "\n"));
285 float db_load(string pFilename)
287 float db, fh, i, j, n;
292 fh = fopen(pFilename, FILE_READ);
296 if(stof(l) == DB_BUCKETS)
299 while((l = fgets(fh)))
302 bufstr_set(db, i, l);
308 // different count of buckets, or a dump?
309 // need to reorganize the database then (SLOW)
311 // note: we also parse the first line (l) in case the DB file is
312 // missing the bucket count
315 n = tokenizebyseparator(l, "\\");
316 for(j = 2; j < n; j += 2)
317 db_put(db, argv(j-1), uri_unescape(argv(j)));
319 while((l = fgets(fh)));
325 void db_dump(float db, string pFilename)
327 float fh, i, j, n, m;
328 fh = fopen(pFilename, FILE_WRITE);
330 error(strcat("Can't dump DB to ", pFilename));
333 for(i = 0; i < n; ++i)
335 m = tokenizebyseparator(bufstr_get(db, i), "\\");
336 for(j = 2; j < m; j += 2)
337 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
342 void db_close(float db)
347 string db_get(float db, string pKey)
350 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
351 return uri_unescape(infoget(bufstr_get(db, h), pKey));
354 void db_put(float db, string pKey, string pValue)
357 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
358 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
365 db = db_load("foo.db");
366 print("LOADED. FILL...\n");
367 for(i = 0; i < DB_BUCKETS; ++i)
368 db_put(db, ftos(random()), "X");
369 print("FILLED. SAVE...\n");
370 db_save(db, "foo.db");
371 print("SAVED. CLOSE...\n");
376 // Multiline text file buffers
377 float buf_load(string pFilename)
384 fh = fopen(pFilename, FILE_READ);
391 while((l = fgets(fh)))
393 bufstr_set(buf, i, l);
400 void buf_save(float buf, string pFilename)
403 fh = fopen(pFilename, FILE_WRITE);
405 error(strcat("Can't write buf to ", pFilename));
406 n = buf_getsize(buf);
407 for(i = 0; i < n; ++i)
408 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
412 string mmsss(float tenths)
416 tenths = floor(tenths + 0.5);
417 minutes = floor(tenths / 600);
418 tenths -= minutes * 600;
419 s = ftos(1000 + tenths);
420 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
423 string mmssss(float hundredths)
427 hundredths = floor(hundredths + 0.5);
428 minutes = floor(hundredths / 6000);
429 hundredths -= minutes * 6000;
430 s = ftos(10000 + hundredths);
431 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
434 string ScoreString(float pFlags, float pValue)
439 pValue = floor(pValue + 0.5); // round
441 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
443 else if(pFlags & SFL_RANK)
445 valstr = ftos(pValue);
447 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
448 valstr = strcat(valstr, "th");
449 else if(substring(valstr, l - 1, 1) == "1")
450 valstr = strcat(valstr, "st");
451 else if(substring(valstr, l - 1, 1) == "2")
452 valstr = strcat(valstr, "nd");
453 else if(substring(valstr, l - 1, 1) == "3")
454 valstr = strcat(valstr, "rd");
456 valstr = strcat(valstr, "th");
458 else if(pFlags & SFL_TIME)
459 valstr = TIME_ENCODED_TOSTRING(pValue);
461 valstr = ftos(pValue);
466 float dotproduct(vector a, vector b)
468 return a_x * b_x + a_y * b_y + a_z * b_z;
471 vector cross(vector a, vector b)
474 '1 0 0' * (a_y * b_z - a_z * b_y)
475 + '0 1 0' * (a_z * b_x - a_x * b_z)
476 + '0 0 1' * (a_x * b_y - a_y * b_x);
479 // compressed vector format:
480 // like MD3, just even shorter
481 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
482 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
483 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
484 // length = 2^(length_encoded/8) / 8
485 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
486 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
487 // the special value 0 indicates the zero vector
489 float lengthLogTable[128];
491 float invertLengthLog(float x)
493 float l, r, m, lerr, rerr;
495 if(x >= lengthLogTable[127])
497 if(x <= lengthLogTable[0])
505 m = floor((l + r) / 2);
506 if(lengthLogTable[m] < x)
512 // now: r is >=, l is <
513 lerr = (x - lengthLogTable[l]);
514 rerr = (lengthLogTable[r] - x);
520 vector decompressShortVector(float data)
526 p = (data & 0xF000) / 0x1000;
527 y = (data & 0x0F80) / 0x80;
528 len = (data & 0x007F);
530 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
543 y = .19634954084936207740 * y;
544 p = .19634954084936207740 * p - 1.57079632679489661922;
545 out_x = cos(y) * cos(p);
546 out_y = sin(y) * cos(p);
550 //print("decompressed: ", vtos(out), "\n");
552 return out * lengthLogTable[len];
555 float compressShortVector(vector vec)
561 //print("compress: ", vtos(vec), "\n");
562 ang = vectoangles(vec);
566 if(ang_x < -90 && ang_x > +90)
567 error("BOGUS vectoangles");
568 //print("angles: ", vtos(ang), "\n");
570 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
579 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
580 len = invertLengthLog(vlen(vec));
582 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
584 return (p * 0x1000) + (y * 0x80) + len;
587 void compressShortVector_init()
592 for(i = 0; i < 128; ++i)
594 lengthLogTable[i] = l;
598 if(cvar("developer"))
600 print("Verifying vector compression table...\n");
601 for(i = 0x0F00; i < 0xFFFF; ++i)
602 if(i != compressShortVector(decompressShortVector(i)))
604 print("BROKEN vector compression: ", ftos(i));
605 print(" -> ", vtos(decompressShortVector(i)));
606 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
615 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
617 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
619 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
620 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
628 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
633 string fixPriorityList(string order, float from, float to, float subtract, float complete)
638 n = tokenize_console(order);
640 for(i = 0; i < n; ++i)
645 if(w >= from && w <= to)
646 neworder = strcat(neworder, ftos(w), " ");
650 if(w >= from && w <= to)
651 neworder = strcat(neworder, ftos(w), " ");
658 n = tokenize_console(neworder);
659 for(w = to; w >= from; --w)
661 for(i = 0; i < n; ++i)
662 if(stof(argv(i)) == w)
664 if(i == n) // not found
665 neworder = strcat(neworder, ftos(w), " ");
669 return substring(neworder, 0, strlen(neworder) - 1);
672 string mapPriorityList(string order, string(string) mapfunc)
677 n = tokenize_console(order);
679 for(i = 0; i < n; ++i)
680 neworder = strcat(neworder, mapfunc(argv(i)), " ");
682 return substring(neworder, 0, strlen(neworder) - 1);
685 string swapInPriorityList(string order, float i, float j)
690 n = tokenize_console(order);
692 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
695 for(w = 0; w < n; ++w)
698 s = strcat(s, argv(j), " ");
700 s = strcat(s, argv(i), " ");
702 s = strcat(s, argv(w), " ");
704 return substring(s, 0, strlen(s) - 1);
710 float cvar_value_issafe(string s)
712 if(strstrofs(s, "\"", 0) >= 0)
714 if(strstrofs(s, "\\", 0) >= 0)
716 if(strstrofs(s, ";", 0) >= 0)
718 if(strstrofs(s, "$", 0) >= 0)
720 if(strstrofs(s, "\r", 0) >= 0)
722 if(strstrofs(s, "\n", 0) >= 0)
728 void get_mi_min_max(float mode)
733 strunzone(mi_shortname);
734 mi_shortname = mapname;
735 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
736 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
737 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
738 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
739 mi_shortname = strzone(mi_shortname);
751 MapInfo_Get_ByName(mi_shortname, 0, 0);
752 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
754 mi_min = MapInfo_Map_mins;
755 mi_max = MapInfo_Map_maxs;
763 tracebox('1 0 0' * mi_x,
764 '0 1 0' * mi_y + '0 0 1' * mi_z,
765 '0 1 0' * ma_y + '0 0 1' * ma_z,
769 if(!trace_startsolid)
770 mi_min_x = trace_endpos_x;
772 tracebox('0 1 0' * mi_y,
773 '1 0 0' * mi_x + '0 0 1' * mi_z,
774 '1 0 0' * ma_x + '0 0 1' * ma_z,
778 if(!trace_startsolid)
779 mi_min_y = trace_endpos_y;
781 tracebox('0 0 1' * mi_z,
782 '1 0 0' * mi_x + '0 1 0' * mi_y,
783 '1 0 0' * ma_x + '0 1 0' * ma_y,
787 if(!trace_startsolid)
788 mi_min_z = trace_endpos_z;
790 tracebox('1 0 0' * ma_x,
791 '0 1 0' * mi_y + '0 0 1' * mi_z,
792 '0 1 0' * ma_y + '0 0 1' * ma_z,
796 if(!trace_startsolid)
797 mi_max_x = trace_endpos_x;
799 tracebox('0 1 0' * ma_y,
800 '1 0 0' * mi_x + '0 0 1' * mi_z,
801 '1 0 0' * ma_x + '0 0 1' * ma_z,
805 if(!trace_startsolid)
806 mi_max_y = trace_endpos_y;
808 tracebox('0 0 1' * ma_z,
809 '1 0 0' * mi_x + '0 1 0' * mi_y,
810 '1 0 0' * ma_x + '0 1 0' * ma_y,
814 if(!trace_startsolid)
815 mi_max_z = trace_endpos_z;
820 void get_mi_min_max_texcoords(float mode)
824 get_mi_min_max(mode);
829 // extend mi_picmax to get a square aspect ratio
830 // center the map in that area
831 extend = mi_picmax - mi_picmin;
832 if(extend_y > extend_x)
834 mi_picmin_x -= (extend_y - extend_x) * 0.5;
835 mi_picmax_x += (extend_y - extend_x) * 0.5;
839 mi_picmin_y -= (extend_x - extend_y) * 0.5;
840 mi_picmax_y += (extend_x - extend_y) * 0.5;
843 // add another some percent
844 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
848 // calculate the texcoords
849 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
850 // first the two corners of the origin
851 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
852 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
853 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
854 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
855 // then the other corners
856 mi_pictexcoord1_x = mi_pictexcoord0_x;
857 mi_pictexcoord1_y = mi_pictexcoord2_y;
858 mi_pictexcoord3_x = mi_pictexcoord2_x;
859 mi_pictexcoord3_y = mi_pictexcoord0_y;
863 float cvar_settemp(string tmp_cvar, string tmp_value)
865 float created_saved_value;
868 created_saved_value = 0;
870 if not(tmp_cvar || tmp_value)
872 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
876 if(!cvar_type(tmp_cvar))
878 print(sprintf("Error: cvar %s doesn't exist!\n", tmp_cvar));
882 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
883 if(e.netname == tmp_cvar)
884 created_saved_value = -1; // skip creation
886 if(created_saved_value != -1)
888 // creating a new entity to keep track of this cvar
890 e.classname = "saved_cvar_value";
891 e.netname = strzone(tmp_cvar);
892 e.message = strzone(cvar_string(tmp_cvar));
893 created_saved_value = 1;
896 // update the cvar to the value given
897 cvar_set(tmp_cvar, tmp_value);
899 return created_saved_value;
902 float cvar_settemp_restore()
906 while((e = find(e, classname, "saved_cvar_value")))
908 if(cvar_type(e.netname))
910 cvar_set(e.netname, e.message);
915 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname));
921 float almost_equals(float a, float b)
924 eps = (max(a, -a) + max(b, -b)) * 0.001;
925 if(a - b < eps && b - a < eps)
930 float almost_in_bounds(float a, float b, float c)
933 eps = (max(a, -a) + max(c, -c)) * 0.001;
936 return b == median(a - eps, b, c + eps);
939 float power2of(float e)
943 float log2of(float x)
945 // NOTE: generated code
1018 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1022 else if(ma == rgb_x)
1025 return (rgb_y - rgb_z) / (ma - mi);
1027 return (rgb_y - rgb_z) / (ma - mi) + 6;
1029 else if(ma == rgb_y)
1030 return (rgb_z - rgb_x) / (ma - mi) + 2;
1031 else // if(ma == rgb_z)
1032 return (rgb_x - rgb_y) / (ma - mi) + 4;
1035 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1039 hue -= 6 * floor(hue / 6);
1041 //else if(ma == rgb_x)
1042 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1046 rgb_y = hue * (ma - mi) + mi;
1049 //else if(ma == rgb_y)
1050 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1053 rgb_x = (2 - hue) * (ma - mi) + mi;
1061 rgb_z = (hue - 2) * (ma - mi) + mi;
1063 //else // if(ma == rgb_z)
1064 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1068 rgb_y = (4 - hue) * (ma - mi) + mi;
1073 rgb_x = (hue - 4) * (ma - mi) + mi;
1077 //else if(ma == rgb_x)
1078 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1079 else // if(hue <= 6)
1083 rgb_z = (6 - hue) * (ma - mi) + mi;
1089 vector rgb_to_hsv(vector rgb)
1094 mi = min(rgb_x, rgb_y, rgb_z);
1095 ma = max(rgb_x, rgb_y, rgb_z);
1097 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1108 vector hsv_to_rgb(vector hsv)
1110 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1113 vector rgb_to_hsl(vector rgb)
1118 mi = min(rgb_x, rgb_y, rgb_z);
1119 ma = max(rgb_x, rgb_y, rgb_z);
1121 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1123 hsl_z = 0.5 * (mi + ma);
1126 else if(hsl_z <= 0.5)
1127 hsl_y = (ma - mi) / (2*hsl_z);
1128 else // if(hsl_z > 0.5)
1129 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1134 vector hsl_to_rgb(vector hsl)
1136 float mi, ma, maminusmi;
1139 maminusmi = hsl_y * 2 * hsl_z;
1141 maminusmi = hsl_y * (2 - 2 * hsl_z);
1143 // hsl_z = 0.5 * mi + 0.5 * ma
1144 // maminusmi = - mi + ma
1145 mi = hsl_z - 0.5 * maminusmi;
1146 ma = hsl_z + 0.5 * maminusmi;
1148 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1151 string rgb_to_hexcolor(vector rgb)
1156 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1157 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1158 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1162 // requires that m2>m1 in all coordinates, and that m4>m3
1163 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;}
1165 // requires the same, but is a stronger condition
1166 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;}
1171 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1174 // The following function is SLOW.
1175 // For your safety and for the protection of those around you...
1176 // DO NOT CALL THIS AT HOME.
1177 // No really, don't.
1178 if(w(theText, theSize) <= maxWidth)
1179 return strlen(theText); // yeah!
1181 // binary search for right place to cut string
1183 float left, right, middle; // this always works
1185 right = strlen(theText); // this always fails
1188 middle = floor((left + right) / 2);
1189 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1194 while(left < right - 1);
1196 if(w("^7", theSize) == 0) // detect color codes support in the width function
1198 // NOTE: when color codes are involved, this binary search is,
1199 // mathematically, BROKEN. However, it is obviously guaranteed to
1200 // terminate, as the range still halves each time - but nevertheless, it is
1201 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1202 // range, and "right" is outside).
1204 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1205 // and decrease left on the basis of the chars detected of the truncated tag
1206 // Even if the ^xrgb tag is not complete/correct, left is decreased
1207 // (sometimes too much but with a correct result)
1208 // it fixes also ^[0-9]
1209 while(left >= 1 && substring(theText, left-1, 1) == "^")
1212 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1214 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1216 ch = str2chr(theText, left-1);
1217 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1220 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1222 ch = str2chr(theText, left-2);
1223 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1225 ch = str2chr(theText, left-1);
1226 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1235 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1238 // The following function is SLOW.
1239 // For your safety and for the protection of those around you...
1240 // DO NOT CALL THIS AT HOME.
1241 // No really, don't.
1242 if(w(theText) <= maxWidth)
1243 return strlen(theText); // yeah!
1245 // binary search for right place to cut string
1247 float left, right, middle; // this always works
1249 right = strlen(theText); // this always fails
1252 middle = floor((left + right) / 2);
1253 if(w(substring(theText, 0, middle)) <= maxWidth)
1258 while(left < right - 1);
1260 if(w("^7") == 0) // detect color codes support in the width function
1262 // NOTE: when color codes are involved, this binary search is,
1263 // mathematically, BROKEN. However, it is obviously guaranteed to
1264 // terminate, as the range still halves each time - but nevertheless, it is
1265 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1266 // range, and "right" is outside).
1268 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1269 // and decrease left on the basis of the chars detected of the truncated tag
1270 // Even if the ^xrgb tag is not complete/correct, left is decreased
1271 // (sometimes too much but with a correct result)
1272 // it fixes also ^[0-9]
1273 while(left >= 1 && substring(theText, left-1, 1) == "^")
1276 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1278 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1280 ch = str2chr(theText, left-1);
1281 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1284 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1286 ch = str2chr(theText, left-2);
1287 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1289 ch = str2chr(theText, left-1);
1290 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1299 string find_last_color_code(string s)
1301 float start, len, i, carets;
1302 start = strstrofs(s, "^", 0);
1303 if (start == -1) // no caret found
1306 for(i = len; i >= start; --i)
1308 if(substring(s, i, 1) != "^")
1312 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1315 // check if carets aren't all escaped
1316 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1319 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1320 return substring(s, i, 2);
1323 if(substring(s, i+1, 1) == "x")
1324 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1325 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1326 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1327 return substring(s, i, 5);
1329 i -= carets; // this also skips one char before the carets
1335 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1341 s = getWrappedLine_remaining;
1345 getWrappedLine_remaining = string_null;
1346 return s; // the line has no size ANYWAY, nothing would be displayed.
1349 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1350 if(cantake > 0 && cantake < strlen(s))
1353 while(take > 0 && substring(s, take, 1) != " ")
1357 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1358 if(getWrappedLine_remaining == "")
1359 getWrappedLine_remaining = string_null;
1360 else if (tw("^7", theFontSize) == 0)
1361 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1362 return substring(s, 0, cantake);
1366 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1367 if(getWrappedLine_remaining == "")
1368 getWrappedLine_remaining = string_null;
1369 else if (tw("^7", theFontSize) == 0)
1370 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1371 return substring(s, 0, take);
1376 getWrappedLine_remaining = string_null;
1381 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1387 s = getWrappedLine_remaining;
1391 getWrappedLine_remaining = string_null;
1392 return s; // the line has no size ANYWAY, nothing would be displayed.
1395 cantake = textLengthUpToLength(s, w, tw);
1396 if(cantake > 0 && cantake < strlen(s))
1399 while(take > 0 && substring(s, take, 1) != " ")
1403 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1404 if(getWrappedLine_remaining == "")
1405 getWrappedLine_remaining = string_null;
1406 else if (tw("^7") == 0)
1407 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1408 return substring(s, 0, cantake);
1412 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1413 if(getWrappedLine_remaining == "")
1414 getWrappedLine_remaining = string_null;
1415 else if (tw("^7") == 0)
1416 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1417 return substring(s, 0, take);
1422 getWrappedLine_remaining = string_null;
1427 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1429 if(tw(theText, theFontSize) <= maxWidth)
1432 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1435 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1437 if(tw(theText) <= maxWidth)
1440 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1443 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1445 string subpattern, subpattern2, subpattern3, subpattern4;
1446 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1448 subpattern2 = ",teams,";
1450 subpattern2 = ",noteams,";
1452 subpattern3 = ",teamspawns,";
1454 subpattern3 = ",noteamspawns,";
1455 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1456 subpattern4 = ",race,";
1458 subpattern4 = string_null;
1460 if(substring(pattern, 0, 1) == "-")
1462 pattern = substring(pattern, 1, strlen(pattern) - 1);
1463 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1465 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1467 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1469 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1474 if(substring(pattern, 0, 1) == "+")
1475 pattern = substring(pattern, 1, strlen(pattern) - 1);
1476 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1477 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1478 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1479 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1485 void shuffle(float n, swapfunc_t swap, entity pass)
1488 for(i = 1; i < n; ++i)
1490 // swap i-th item at a random position from 0 to i
1491 // proof for even distribution:
1494 // item n+1 gets at any position with chance 1/(n+1)
1495 // all others will get their 1/n chance reduced by factor n/(n+1)
1496 // to be on place n+1, their chance will be 1/(n+1)
1497 // 1/n * n/(n+1) = 1/(n+1)
1499 j = floor(random() * (i + 1));
1505 string substring_range(string s, float b, float e)
1507 return substring(s, b, e - b);
1510 string swapwords(string str, float i, float j)
1513 string s1, s2, s3, s4, s5;
1514 float si, ei, sj, ej, s0, en;
1515 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1516 si = argv_start_index(i);
1517 sj = argv_start_index(j);
1518 ei = argv_end_index(i);
1519 ej = argv_end_index(j);
1520 s0 = argv_start_index(0);
1521 en = argv_end_index(n-1);
1522 s1 = substring_range(str, s0, si);
1523 s2 = substring_range(str, si, ei);
1524 s3 = substring_range(str, ei, sj);
1525 s4 = substring_range(str, sj, ej);
1526 s5 = substring_range(str, ej, en);
1527 return strcat(s1, s4, s3, s2, s5);
1530 string _shufflewords_str;
1531 void _shufflewords_swapfunc(float i, float j, entity pass)
1533 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1535 string shufflewords(string str)
1538 _shufflewords_str = str;
1539 n = tokenizebyseparator(str, " ");
1540 shuffle(n, _shufflewords_swapfunc, world);
1541 str = _shufflewords_str;
1542 _shufflewords_str = string_null;
1546 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1562 // actually, every number solves the equation!
1573 if(a > 0) // put the smaller solution first
1575 v_x = ((-b)-D) / (2*a);
1576 v_y = ((-b)+D) / (2*a);
1580 v_x = (-b+D) / (2*a);
1581 v_y = (-b-D) / (2*a);
1587 // complex solutions!
1600 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1604 // make origin and speed relative
1609 // now solve for ret, ret normalized:
1610 // eorg + t * evel == t * ret * spd
1611 // or, rather, solve for t:
1612 // |eorg + t * evel| == t * spd
1613 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1614 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1615 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1616 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1617 // q = (eorg * eorg) / (evel * evel - spd * spd)
1618 if(!solution_z) // no real solution
1621 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1622 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1623 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1624 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1625 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1626 // spd < |evel| * sin angle(evel, eorg)
1629 else if(solution_x > 0)
1631 // both solutions > 0: take the smaller one
1632 // happens if p < 0 and q > 0
1633 ret = normalize(eorg + solution_x * evel);
1635 else if(solution_y > 0)
1637 // one solution > 0: take the larger one
1638 // happens if q < 0 or q == 0 and p < 0
1639 ret = normalize(eorg + solution_y * evel);
1643 // no solution > 0: reject
1644 // happens if p > 0 and q >= 0
1645 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1646 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1651 // "Enemy is moving away from me at more than spd"
1655 // NOTE: we always got a solution if spd > |evel|
1657 if(newton_style == 2)
1658 ret = normalize(ret * spd + myvel);
1663 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1668 if(newton_style == 2)
1670 // true Newtonian projectiles with automatic aim adjustment
1672 // solve: |outspeed * mydir - myvel| = spd
1673 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1674 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1678 // myvel^2 - (mydir * myvel)^2 > spd^2
1679 // velocity without mydir component > spd
1680 // fire at smallest possible spd that works?
1681 // |(mydir * myvel) * myvel - myvel| = spd
1683 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1687 outspeed = solution_y; // the larger one
1690 //outspeed = 0; // slowest possible shot
1691 outspeed = solution_x; // the real part (that is, the average!)
1692 //dprint("impossible shot, adjusting\n");
1695 outspeed = bound(spd * mi, outspeed, spd * ma);
1696 return mydir * outspeed;
1700 return myvel + spd * mydir;
1703 void check_unacceptable_compiler_bugs()
1705 if(cvar("_allow_unacceptable_compiler_bugs"))
1707 tokenize_console("foo bar");
1708 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1709 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.");
1713 error("The empty string counts as false. We do not want that!");
1716 float compressShotOrigin(vector v)
1720 y = rint(v_y * 4) + 128;
1721 z = rint(v_z * 4) + 128;
1722 if(x > 255 || x < 0)
1724 print("shot origin ", vtos(v), " x out of bounds\n");
1725 x = bound(0, x, 255);
1727 if(y > 255 || y < 0)
1729 print("shot origin ", vtos(v), " y out of bounds\n");
1730 y = bound(0, y, 255);
1732 if(z > 255 || z < 0)
1734 print("shot origin ", vtos(v), " z out of bounds\n");
1735 z = bound(0, z, 255);
1737 return x * 0x10000 + y * 0x100 + z;
1739 vector decompressShotOrigin(float f)
1742 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1743 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1744 v_z = ((f & 0xFF) - 128) / 4;
1748 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1750 float start, end, root, child;
1753 start = floor((n - 2) / 2);
1756 // siftdown(start, count-1);
1758 while(root * 2 + 1 <= n-1)
1760 child = root * 2 + 1;
1762 if(cmp(child, child+1, pass) < 0)
1764 if(cmp(root, child, pass) < 0)
1766 swap(root, child, pass);
1782 // siftdown(0, end);
1784 while(root * 2 + 1 <= end)
1786 child = root * 2 + 1;
1787 if(child < end && cmp(child, child+1, pass) < 0)
1789 if(cmp(root, child, pass) < 0)
1791 swap(root, child, pass);
1801 void RandomSelection_Init()
1803 RandomSelection_totalweight = 0;
1804 RandomSelection_chosen_ent = world;
1805 RandomSelection_chosen_float = 0;
1806 RandomSelection_chosen_string = string_null;
1807 RandomSelection_best_priority = -1;
1809 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1811 if(priority > RandomSelection_best_priority)
1813 RandomSelection_best_priority = priority;
1814 RandomSelection_chosen_ent = e;
1815 RandomSelection_chosen_float = f;
1816 RandomSelection_chosen_string = s;
1817 RandomSelection_totalweight = weight;
1819 else if(priority == RandomSelection_best_priority)
1821 RandomSelection_totalweight += weight;
1822 if(random() * RandomSelection_totalweight <= weight)
1824 RandomSelection_chosen_ent = e;
1825 RandomSelection_chosen_float = f;
1826 RandomSelection_chosen_string = s;
1831 vector healtharmor_maxdamage(float h, float a, float armorblock)
1833 // NOTE: we'll always choose the SMALLER value...
1834 float healthdamage, armordamage, armorideal;
1836 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1837 armordamage = a + (h - 1); // damage we can take if we could use more armor
1838 armorideal = healthdamage * armorblock;
1840 if(armordamage < healthdamage)
1853 vector healtharmor_applydamage(float a, float armorblock, float damage)
1856 v_y = bound(0, damage * armorblock, a); // save
1857 v_x = bound(0, damage - v_y, damage); // take
1862 string getcurrentmod()
1866 m = cvar_string("fs_gamedir");
1867 n = tokenize_console(m);
1879 v = ReadShort() * 256; // note: this is signed
1880 v += ReadByte(); // note: this is unsigned
1884 void WriteInt24_t(float dst, float val)
1887 WriteShort(dst, (v = floor(val / 256)));
1888 WriteByte(dst, val - v * 256); // 0..255
1893 float float2range11(float f)
1895 // continuous function mapping all reals into -1..1
1896 return f / (fabs(f) + 1);
1899 float float2range01(float f)
1901 // continuous function mapping all reals into 0..1
1902 return 0.5 + 0.5 * float2range11(f);
1905 // from the GNU Scientific Library
1906 float gsl_ran_gaussian_lastvalue;
1907 float gsl_ran_gaussian_lastvalue_set;
1908 float gsl_ran_gaussian(float sigma)
1911 if(gsl_ran_gaussian_lastvalue_set)
1913 gsl_ran_gaussian_lastvalue_set = 0;
1914 return sigma * gsl_ran_gaussian_lastvalue;
1918 a = random() * 2 * M_PI;
1919 b = sqrt(-2 * log(random()));
1920 gsl_ran_gaussian_lastvalue = cos(a) * b;
1921 gsl_ran_gaussian_lastvalue_set = 1;
1922 return sigma * sin(a) * b;
1926 string car(string s)
1929 o = strstrofs(s, " ", 0);
1932 return substring(s, 0, o);
1934 string cdr(string s)
1937 o = strstrofs(s, " ", 0);
1940 return substring(s, o + 1, strlen(s) - (o + 1));
1942 float matchacl(string acl, string str)
1949 t = car(acl); acl = cdr(acl);
1952 if(substring(t, 0, 1) == "-")
1955 t = substring(t, 1, strlen(t) - 1);
1957 else if(substring(t, 0, 1) == "+")
1958 t = substring(t, 1, strlen(t) - 1);
1960 if(substring(t, -1, 1) == "*")
1962 t = substring(t, 0, strlen(t) - 1);
1963 s = substring(str, 0, strlen(t));
1975 float startsWith(string haystack, string needle)
1977 return substring(haystack, 0, strlen(needle)) == needle;
1979 float startsWithNocase(string haystack, string needle)
1981 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1984 string get_model_datafilename(string m, float sk, string fil)
1989 m = "models/player/*_";
1991 m = strcat(m, ftos(sk));
1994 return strcat(m, ".", fil);
1997 float get_model_parameters(string m, float sk)
2002 get_model_parameters_modelname = string_null;
2003 get_model_parameters_modelskin = -1;
2004 get_model_parameters_name = string_null;
2005 get_model_parameters_species = -1;
2006 get_model_parameters_sex = string_null;
2007 get_model_parameters_weight = -1;
2008 get_model_parameters_age = -1;
2009 get_model_parameters_desc = string_null;
2015 if(substring(m, -4, -1) != ".txt")
2017 if(substring(m, -6, 1) != "_")
2019 sk = stof(substring(m, -5, 1));
2020 m = substring(m, 0, -7);
2023 fn = get_model_datafilename(m, sk, "txt");
2024 fh = fopen(fn, FILE_READ);
2028 fn = get_model_datafilename(m, sk, "txt");
2029 fh = fopen(fn, FILE_READ);
2034 get_model_parameters_modelname = m;
2035 get_model_parameters_modelskin = sk;
2036 while((s = fgets(fh)))
2039 break; // next lines will be description
2043 get_model_parameters_name = s;
2047 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2048 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2049 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2050 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2051 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2052 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2053 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2056 get_model_parameters_sex = s;
2058 get_model_parameters_weight = stof(s);
2060 get_model_parameters_age = stof(s);
2063 while((s = fgets(fh)))
2065 if(get_model_parameters_desc)
2066 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2068 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2076 vector vec2(vector v)
2083 vector NearestPointOnBox(entity box, vector org)
2085 vector m1, m2, nearest;
2087 m1 = box.mins + box.origin;
2088 m2 = box.maxs + box.origin;
2090 nearest_x = bound(m1_x, org_x, m2_x);
2091 nearest_y = bound(m1_y, org_y, m2_y);
2092 nearest_z = bound(m1_z, org_z, m2_z);
2098 float vercmp_recursive(string v1, string v2)
2104 dot1 = strstrofs(v1, ".", 0);
2105 dot2 = strstrofs(v2, ".", 0);
2109 s1 = substring(v1, 0, dot1);
2113 s2 = substring(v2, 0, dot2);
2115 r = stof(s1) - stof(s2);
2119 r = strcasecmp(s1, s2);
2132 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2135 float vercmp(string v1, string v2)
2137 if(strcasecmp(v1, v2) == 0) // early out check
2146 return vercmp_recursive(v1, v2);
2149 float u8_strsize(string s)
2169 // translation helpers
2170 string language_filename(string s)
2175 if(fn == "" || fn == "dump")
2177 fn = strcat(s, ".", fn);
2178 if((fh = fopen(fn, FILE_READ)) >= 0)
2185 string CTX(string s)
2187 float p = strstrofs(s, "^", 0);
2190 return substring(s, p+1, -1);
2193 // x-encoding (encoding as zero length invisible string)
2194 const string XENCODE_2 = "xX";
2195 const string XENCODE_22 = "0123456789abcdefABCDEF";
2196 string xencode(float f)
2199 d = mod(f, 22); f = floor(f / 22);
2200 c = mod(f, 22); f = floor(f / 22);
2201 b = mod(f, 22); f = floor(f / 22);
2202 a = mod(f, 2); // f = floor(f / 2);
2205 substring(XENCODE_2, a, 1),
2206 substring(XENCODE_22, b, 1),
2207 substring(XENCODE_22, c, 1),
2208 substring(XENCODE_22, d, 1)
2211 float xdecode(string s)
2214 if(substring(s, 0, 1) != "^")
2218 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2219 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2220 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2221 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2222 if(a < 0 || b < 0 || c < 0 || d < 0)
2224 return ((a * 22 + b) * 22 + c) * 22 + d;
2227 float lowestbit(float f)
2238 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2240 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2243 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2246 // escape the string to make it safe for consoles
2247 string MakeConsoleSafe(string input)
2249 input = strreplace("\n", "", input);
2250 input = strreplace("\\", "\\\\", input);
2251 input = strreplace("$", "$$", input);
2252 input = strreplace("\"", "\\\"", input);
2257 // get true/false value of a string with multiple different inputs
2258 float InterpretBoolean(string input)
2260 switch(strtolower(input))
2272 default: return stof(input);
2278 entity ReadCSQCEntity()
2284 return findfloat(world, entnum, f);
2288 float shutdown_running;
2293 void CSQC_Shutdown()
2299 if(shutdown_running)
2301 print("Recursive shutdown detected! Only restoring cvars...\n");
2305 shutdown_running = 1;
2308 cvar_settemp_restore(); // this must be done LAST, but in any case
2311 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2312 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2313 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2314 // this will use the value:
2316 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2317 // accuracy at x is 1/derivative, i.e.
2318 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2320 void WriteApproxPastTime(float dst, float t)
2322 float dt = time - t;
2324 // warning: this is approximate; do not resend when you don't have to!
2325 // be careful with sendflags here!
2326 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2329 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2332 dt = rint(bound(0, dt, 255));
2338 float ReadApproxPastTime()
2340 float dt = ReadByte();
2342 // map from range...PPROXPASTTIME_MAX / 256
2343 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2345 return servertime - dt;
2350 .float skeleton_bones_index;
2351 void Skeleton_SetBones(entity e)
2353 // set skeleton_bones to the total number of bones on the model
2354 if(e.skeleton_bones_index == e.modelindex)
2355 return; // same model, nothing to update
2358 skelindex = skel_create(e.modelindex);
2359 e.skeleton_bones = skel_get_numbones(skelindex);
2360 skel_delete(skelindex);
2361 e.skeleton_bones_index = e.modelindex;
2365 string to_execute_next_frame;
2366 void execute_next_frame()
2368 if(to_execute_next_frame)
2370 localcmd("\n", to_execute_next_frame, "\n");
2371 strunzone(to_execute_next_frame);
2372 to_execute_next_frame = string_null;
2375 void queue_to_execute_next_frame(string s)
2377 if(to_execute_next_frame)
2379 s = strcat(s, "\n", to_execute_next_frame);
2380 strunzone(to_execute_next_frame);
2382 to_execute_next_frame = strzone(s);
2385 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2388 ((( startspeedfactor + endspeedfactor - 2
2389 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2390 ) * x + startspeedfactor
2394 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2396 if(startspeedfactor < 0 || endspeedfactor < 0)
2400 // if this is the case, the possible zeros of the first derivative are outside
2402 We can calculate this condition as condition
2407 // better, see below:
2408 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2411 // if this is the case, the first derivative has no zeros at all
2412 float se = startspeedfactor + endspeedfactor;
2413 float s_e = startspeedfactor - endspeedfactor;
2414 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2417 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2418 // we also get s_e <= 6 - se
2419 // 3 * (se - 4)^2 + (6 - se)^2
2420 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2421 // Therefore, above "better" check works!
2425 // known good cases:
2433 // (3.5, [0.2..2.3])
2437 .float FindConnectedComponent_processing;
2438 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2440 entity queue_start, queue_end;
2442 // we build a queue of to-be-processed entities.
2443 // queue_start is the next entity to be checked for neighbors
2444 // queue_end is the last entity added
2446 if(e.FindConnectedComponent_processing)
2447 error("recursion or broken cleanup");
2449 // start with a 1-element queue
2450 queue_start = queue_end = e;
2451 queue_end.fld = world;
2452 queue_end.FindConnectedComponent_processing = 1;
2454 // for each queued item:
2455 for(; queue_start; queue_start = queue_start.fld)
2457 // find all neighbors of queue_start
2459 for(t = world; (t = nxt(t, queue_start, pass)); )
2461 if(t.FindConnectedComponent_processing)
2463 if(iscon(t, queue_start, pass))
2465 // it is connected? ADD IT. It will look for neighbors soon too.
2468 queue_end.fld = world;
2469 queue_end.FindConnectedComponent_processing = 1;
2475 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2476 queue_start.FindConnectedComponent_processing = 0;