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 = FALSE;
870 if not(tmp_cvar || tmp_value)
872 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
876 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
877 if(e.netname == tmp_cvar)
878 goto saved; // skip creation
880 // creating a new entity to keep track of this cvar
882 e.classname = "saved_cvar_value";
883 e.netname = strzone(tmp_cvar);
884 e.message = strzone(cvar_string(tmp_cvar));
885 created_saved_value = TRUE;
887 // an entity for this cvar already exists
890 // update the cvar to the value given
891 cvar_set(tmp_cvar, tmp_value);
893 return created_saved_value;
896 float cvar_settemp_restore()
900 while((e = find(world, classname, "saved_cvar_value")))
902 cvar_set(e.netname, e.message);
909 float almost_equals(float a, float b)
912 eps = (max(a, -a) + max(b, -b)) * 0.001;
913 if(a - b < eps && b - a < eps)
918 float almost_in_bounds(float a, float b, float c)
921 eps = (max(a, -a) + max(c, -c)) * 0.001;
924 return b == median(a - eps, b, c + eps);
927 float power2of(float e)
931 float log2of(float x)
933 // NOTE: generated code
1006 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1010 else if(ma == rgb_x)
1013 return (rgb_y - rgb_z) / (ma - mi);
1015 return (rgb_y - rgb_z) / (ma - mi) + 6;
1017 else if(ma == rgb_y)
1018 return (rgb_z - rgb_x) / (ma - mi) + 2;
1019 else // if(ma == rgb_z)
1020 return (rgb_x - rgb_y) / (ma - mi) + 4;
1023 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1027 hue -= 6 * floor(hue / 6);
1029 //else if(ma == rgb_x)
1030 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1034 rgb_y = hue * (ma - mi) + mi;
1037 //else if(ma == rgb_y)
1038 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1041 rgb_x = (2 - hue) * (ma - mi) + mi;
1049 rgb_z = (hue - 2) * (ma - mi) + mi;
1051 //else // if(ma == rgb_z)
1052 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1056 rgb_y = (4 - hue) * (ma - mi) + mi;
1061 rgb_x = (hue - 4) * (ma - mi) + mi;
1065 //else if(ma == rgb_x)
1066 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1067 else // if(hue <= 6)
1071 rgb_z = (6 - hue) * (ma - mi) + mi;
1077 vector rgb_to_hsv(vector rgb)
1082 mi = min(rgb_x, rgb_y, rgb_z);
1083 ma = max(rgb_x, rgb_y, rgb_z);
1085 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1096 vector hsv_to_rgb(vector hsv)
1098 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1101 vector rgb_to_hsl(vector rgb)
1106 mi = min(rgb_x, rgb_y, rgb_z);
1107 ma = max(rgb_x, rgb_y, rgb_z);
1109 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1111 hsl_z = 0.5 * (mi + ma);
1114 else if(hsl_z <= 0.5)
1115 hsl_y = (ma - mi) / (2*hsl_z);
1116 else // if(hsl_z > 0.5)
1117 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1122 vector hsl_to_rgb(vector hsl)
1124 float mi, ma, maminusmi;
1127 maminusmi = hsl_y * 2 * hsl_z;
1129 maminusmi = hsl_y * (2 - 2 * hsl_z);
1131 // hsl_z = 0.5 * mi + 0.5 * ma
1132 // maminusmi = - mi + ma
1133 mi = hsl_z - 0.5 * maminusmi;
1134 ma = hsl_z + 0.5 * maminusmi;
1136 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1139 string rgb_to_hexcolor(vector rgb)
1144 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1145 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1146 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1150 // requires that m2>m1 in all coordinates, and that m4>m3
1151 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;}
1153 // requires the same, but is a stronger condition
1154 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;}
1159 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1162 // The following function is SLOW.
1163 // For your safety and for the protection of those around you...
1164 // DO NOT CALL THIS AT HOME.
1165 // No really, don't.
1166 if(w(theText, theSize) <= maxWidth)
1167 return strlen(theText); // yeah!
1169 // binary search for right place to cut string
1171 float left, right, middle; // this always works
1173 right = strlen(theText); // this always fails
1176 middle = floor((left + right) / 2);
1177 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1182 while(left < right - 1);
1184 if(w("^7", theSize) == 0) // detect color codes support in the width function
1186 // NOTE: when color codes are involved, this binary search is,
1187 // mathematically, BROKEN. However, it is obviously guaranteed to
1188 // terminate, as the range still halves each time - but nevertheless, it is
1189 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1190 // range, and "right" is outside).
1192 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1193 // and decrease left on the basis of the chars detected of the truncated tag
1194 // Even if the ^xrgb tag is not complete/correct, left is decreased
1195 // (sometimes too much but with a correct result)
1196 // it fixes also ^[0-9]
1197 while(left >= 1 && substring(theText, left-1, 1) == "^")
1200 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1202 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1204 ch = str2chr(theText, left-1);
1205 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1208 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1210 ch = str2chr(theText, left-2);
1211 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1213 ch = str2chr(theText, left-1);
1214 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1223 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1226 // The following function is SLOW.
1227 // For your safety and for the protection of those around you...
1228 // DO NOT CALL THIS AT HOME.
1229 // No really, don't.
1230 if(w(theText) <= maxWidth)
1231 return strlen(theText); // yeah!
1233 // binary search for right place to cut string
1235 float left, right, middle; // this always works
1237 right = strlen(theText); // this always fails
1240 middle = floor((left + right) / 2);
1241 if(w(substring(theText, 0, middle)) <= maxWidth)
1246 while(left < right - 1);
1248 if(w("^7") == 0) // detect color codes support in the width function
1250 // NOTE: when color codes are involved, this binary search is,
1251 // mathematically, BROKEN. However, it is obviously guaranteed to
1252 // terminate, as the range still halves each time - but nevertheless, it is
1253 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1254 // range, and "right" is outside).
1256 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1257 // and decrease left on the basis of the chars detected of the truncated tag
1258 // Even if the ^xrgb tag is not complete/correct, left is decreased
1259 // (sometimes too much but with a correct result)
1260 // it fixes also ^[0-9]
1261 while(left >= 1 && substring(theText, left-1, 1) == "^")
1264 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1266 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1268 ch = str2chr(theText, left-1);
1269 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1272 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1274 ch = str2chr(theText, left-2);
1275 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1277 ch = str2chr(theText, left-1);
1278 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1287 string find_last_color_code(string s)
1289 float start, len, i, carets;
1290 start = strstrofs(s, "^", 0);
1291 if (start == -1) // no caret found
1294 for(i = len; i >= start; --i)
1296 if(substring(s, i, 1) != "^")
1300 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1303 // check if carets aren't all escaped
1304 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1307 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1308 return substring(s, i, 2);
1311 if(substring(s, i+1, 1) == "x")
1312 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1313 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1314 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1315 return substring(s, i, 5);
1317 i -= carets; // this also skips one char before the carets
1323 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1329 s = getWrappedLine_remaining;
1333 getWrappedLine_remaining = string_null;
1334 return s; // the line has no size ANYWAY, nothing would be displayed.
1337 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1338 if(cantake > 0 && cantake < strlen(s))
1341 while(take > 0 && substring(s, take, 1) != " ")
1345 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1346 if(getWrappedLine_remaining == "")
1347 getWrappedLine_remaining = string_null;
1348 else if (tw("^7", theFontSize) == 0)
1349 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1350 return substring(s, 0, cantake);
1354 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1355 if(getWrappedLine_remaining == "")
1356 getWrappedLine_remaining = string_null;
1357 else if (tw("^7", theFontSize) == 0)
1358 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1359 return substring(s, 0, take);
1364 getWrappedLine_remaining = string_null;
1369 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1375 s = getWrappedLine_remaining;
1379 getWrappedLine_remaining = string_null;
1380 return s; // the line has no size ANYWAY, nothing would be displayed.
1383 cantake = textLengthUpToLength(s, w, tw);
1384 if(cantake > 0 && cantake < strlen(s))
1387 while(take > 0 && substring(s, take, 1) != " ")
1391 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1392 if(getWrappedLine_remaining == "")
1393 getWrappedLine_remaining = string_null;
1394 else if (tw("^7") == 0)
1395 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1396 return substring(s, 0, cantake);
1400 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1401 if(getWrappedLine_remaining == "")
1402 getWrappedLine_remaining = string_null;
1403 else if (tw("^7") == 0)
1404 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1405 return substring(s, 0, take);
1410 getWrappedLine_remaining = string_null;
1415 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1417 if(tw(theText, theFontSize) <= maxWidth)
1420 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1423 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1425 if(tw(theText) <= maxWidth)
1428 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1431 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1433 string subpattern, subpattern2, subpattern3, subpattern4;
1434 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1436 subpattern2 = ",teams,";
1438 subpattern2 = ",noteams,";
1440 subpattern3 = ",teamspawns,";
1442 subpattern3 = ",noteamspawns,";
1443 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1444 subpattern4 = ",race,";
1446 subpattern4 = string_null;
1448 if(substring(pattern, 0, 1) == "-")
1450 pattern = substring(pattern, 1, strlen(pattern) - 1);
1451 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1453 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1455 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1457 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1462 if(substring(pattern, 0, 1) == "+")
1463 pattern = substring(pattern, 1, strlen(pattern) - 1);
1464 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1465 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1466 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1467 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1473 void shuffle(float n, swapfunc_t swap, entity pass)
1476 for(i = 1; i < n; ++i)
1478 // swap i-th item at a random position from 0 to i
1479 // proof for even distribution:
1482 // item n+1 gets at any position with chance 1/(n+1)
1483 // all others will get their 1/n chance reduced by factor n/(n+1)
1484 // to be on place n+1, their chance will be 1/(n+1)
1485 // 1/n * n/(n+1) = 1/(n+1)
1487 j = floor(random() * (i + 1));
1493 string substring_range(string s, float b, float e)
1495 return substring(s, b, e - b);
1498 string swapwords(string str, float i, float j)
1501 string s1, s2, s3, s4, s5;
1502 float si, ei, sj, ej, s0, en;
1503 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1504 si = argv_start_index(i);
1505 sj = argv_start_index(j);
1506 ei = argv_end_index(i);
1507 ej = argv_end_index(j);
1508 s0 = argv_start_index(0);
1509 en = argv_end_index(n-1);
1510 s1 = substring_range(str, s0, si);
1511 s2 = substring_range(str, si, ei);
1512 s3 = substring_range(str, ei, sj);
1513 s4 = substring_range(str, sj, ej);
1514 s5 = substring_range(str, ej, en);
1515 return strcat(s1, s4, s3, s2, s5);
1518 string _shufflewords_str;
1519 void _shufflewords_swapfunc(float i, float j, entity pass)
1521 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1523 string shufflewords(string str)
1526 _shufflewords_str = str;
1527 n = tokenizebyseparator(str, " ");
1528 shuffle(n, _shufflewords_swapfunc, world);
1529 str = _shufflewords_str;
1530 _shufflewords_str = string_null;
1534 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1550 // actually, every number solves the equation!
1561 if(a > 0) // put the smaller solution first
1563 v_x = ((-b)-D) / (2*a);
1564 v_y = ((-b)+D) / (2*a);
1568 v_x = (-b+D) / (2*a);
1569 v_y = (-b-D) / (2*a);
1575 // complex solutions!
1588 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1592 // make origin and speed relative
1597 // now solve for ret, ret normalized:
1598 // eorg + t * evel == t * ret * spd
1599 // or, rather, solve for t:
1600 // |eorg + t * evel| == t * spd
1601 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1602 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1603 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1604 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1605 // q = (eorg * eorg) / (evel * evel - spd * spd)
1606 if(!solution_z) // no real solution
1609 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1610 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1611 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1612 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1613 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1614 // spd < |evel| * sin angle(evel, eorg)
1617 else if(solution_x > 0)
1619 // both solutions > 0: take the smaller one
1620 // happens if p < 0 and q > 0
1621 ret = normalize(eorg + solution_x * evel);
1623 else if(solution_y > 0)
1625 // one solution > 0: take the larger one
1626 // happens if q < 0 or q == 0 and p < 0
1627 ret = normalize(eorg + solution_y * evel);
1631 // no solution > 0: reject
1632 // happens if p > 0 and q >= 0
1633 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1634 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1639 // "Enemy is moving away from me at more than spd"
1643 // NOTE: we always got a solution if spd > |evel|
1645 if(newton_style == 2)
1646 ret = normalize(ret * spd + myvel);
1651 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1656 if(newton_style == 2)
1658 // true Newtonian projectiles with automatic aim adjustment
1660 // solve: |outspeed * mydir - myvel| = spd
1661 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1662 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1666 // myvel^2 - (mydir * myvel)^2 > spd^2
1667 // velocity without mydir component > spd
1668 // fire at smallest possible spd that works?
1669 // |(mydir * myvel) * myvel - myvel| = spd
1671 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1675 outspeed = solution_y; // the larger one
1678 //outspeed = 0; // slowest possible shot
1679 outspeed = solution_x; // the real part (that is, the average!)
1680 //dprint("impossible shot, adjusting\n");
1683 outspeed = bound(spd * mi, outspeed, spd * ma);
1684 return mydir * outspeed;
1688 return myvel + spd * mydir;
1691 void check_unacceptable_compiler_bugs()
1693 if(cvar("_allow_unacceptable_compiler_bugs"))
1695 tokenize_console("foo bar");
1696 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1697 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.");
1701 error("The empty string counts as false. We do not want that!");
1704 float compressShotOrigin(vector v)
1708 y = rint(v_y * 4) + 128;
1709 z = rint(v_z * 4) + 128;
1710 if(x > 255 || x < 0)
1712 print("shot origin ", vtos(v), " x out of bounds\n");
1713 x = bound(0, x, 255);
1715 if(y > 255 || y < 0)
1717 print("shot origin ", vtos(v), " y out of bounds\n");
1718 y = bound(0, y, 255);
1720 if(z > 255 || z < 0)
1722 print("shot origin ", vtos(v), " z out of bounds\n");
1723 z = bound(0, z, 255);
1725 return x * 0x10000 + y * 0x100 + z;
1727 vector decompressShotOrigin(float f)
1730 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1731 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1732 v_z = ((f & 0xFF) - 128) / 4;
1736 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1738 float start, end, root, child;
1741 start = floor((n - 2) / 2);
1744 // siftdown(start, count-1);
1746 while(root * 2 + 1 <= n-1)
1748 child = root * 2 + 1;
1750 if(cmp(child, child+1, pass) < 0)
1752 if(cmp(root, child, pass) < 0)
1754 swap(root, child, pass);
1770 // siftdown(0, end);
1772 while(root * 2 + 1 <= end)
1774 child = root * 2 + 1;
1775 if(child < end && cmp(child, child+1, pass) < 0)
1777 if(cmp(root, child, pass) < 0)
1779 swap(root, child, pass);
1789 void RandomSelection_Init()
1791 RandomSelection_totalweight = 0;
1792 RandomSelection_chosen_ent = world;
1793 RandomSelection_chosen_float = 0;
1794 RandomSelection_chosen_string = string_null;
1795 RandomSelection_best_priority = -1;
1797 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1799 if(priority > RandomSelection_best_priority)
1801 RandomSelection_best_priority = priority;
1802 RandomSelection_chosen_ent = e;
1803 RandomSelection_chosen_float = f;
1804 RandomSelection_chosen_string = s;
1805 RandomSelection_totalweight = weight;
1807 else if(priority == RandomSelection_best_priority)
1809 RandomSelection_totalweight += weight;
1810 if(random() * RandomSelection_totalweight <= weight)
1812 RandomSelection_chosen_ent = e;
1813 RandomSelection_chosen_float = f;
1814 RandomSelection_chosen_string = s;
1819 vector healtharmor_maxdamage(float h, float a, float armorblock)
1821 // NOTE: we'll always choose the SMALLER value...
1822 float healthdamage, armordamage, armorideal;
1824 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1825 armordamage = a + (h - 1); // damage we can take if we could use more armor
1826 armorideal = healthdamage * armorblock;
1828 if(armordamage < healthdamage)
1841 vector healtharmor_applydamage(float a, float armorblock, float damage)
1844 v_y = bound(0, damage * armorblock, a); // save
1845 v_x = bound(0, damage - v_y, damage); // take
1850 string getcurrentmod()
1854 m = cvar_string("fs_gamedir");
1855 n = tokenize_console(m);
1867 v = ReadShort() * 256; // note: this is signed
1868 v += ReadByte(); // note: this is unsigned
1872 void WriteInt24_t(float dst, float val)
1875 WriteShort(dst, (v = floor(val / 256)));
1876 WriteByte(dst, val - v * 256); // 0..255
1881 float float2range11(float f)
1883 // continuous function mapping all reals into -1..1
1884 return f / (fabs(f) + 1);
1887 float float2range01(float f)
1889 // continuous function mapping all reals into 0..1
1890 return 0.5 + 0.5 * float2range11(f);
1893 // from the GNU Scientific Library
1894 float gsl_ran_gaussian_lastvalue;
1895 float gsl_ran_gaussian_lastvalue_set;
1896 float gsl_ran_gaussian(float sigma)
1899 if(gsl_ran_gaussian_lastvalue_set)
1901 gsl_ran_gaussian_lastvalue_set = 0;
1902 return sigma * gsl_ran_gaussian_lastvalue;
1906 a = random() * 2 * M_PI;
1907 b = sqrt(-2 * log(random()));
1908 gsl_ran_gaussian_lastvalue = cos(a) * b;
1909 gsl_ran_gaussian_lastvalue_set = 1;
1910 return sigma * sin(a) * b;
1914 string car(string s)
1917 o = strstrofs(s, " ", 0);
1920 return substring(s, 0, o);
1922 string cdr(string s)
1925 o = strstrofs(s, " ", 0);
1928 return substring(s, o + 1, strlen(s) - (o + 1));
1930 float matchacl(string acl, string str)
1937 t = car(acl); acl = cdr(acl);
1940 if(substring(t, 0, 1) == "-")
1943 t = substring(t, 1, strlen(t) - 1);
1945 else if(substring(t, 0, 1) == "+")
1946 t = substring(t, 1, strlen(t) - 1);
1948 if(substring(t, -1, 1) == "*")
1950 t = substring(t, 0, strlen(t) - 1);
1951 s = substring(str, 0, strlen(t));
1963 float startsWith(string haystack, string needle)
1965 return substring(haystack, 0, strlen(needle)) == needle;
1967 float startsWithNocase(string haystack, string needle)
1969 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1972 string get_model_datafilename(string m, float sk, string fil)
1977 m = "models/player/*_";
1979 m = strcat(m, ftos(sk));
1982 return strcat(m, ".", fil);
1985 float get_model_parameters(string m, float sk)
1990 get_model_parameters_modelname = string_null;
1991 get_model_parameters_modelskin = -1;
1992 get_model_parameters_name = string_null;
1993 get_model_parameters_species = -1;
1994 get_model_parameters_sex = string_null;
1995 get_model_parameters_weight = -1;
1996 get_model_parameters_age = -1;
1997 get_model_parameters_desc = string_null;
2003 if(substring(m, -4, -1) != ".txt")
2005 if(substring(m, -6, 1) != "_")
2007 sk = stof(substring(m, -5, 1));
2008 m = substring(m, 0, -7);
2011 fn = get_model_datafilename(m, sk, "txt");
2012 fh = fopen(fn, FILE_READ);
2016 fn = get_model_datafilename(m, sk, "txt");
2017 fh = fopen(fn, FILE_READ);
2022 get_model_parameters_modelname = m;
2023 get_model_parameters_modelskin = sk;
2024 while((s = fgets(fh)))
2027 break; // next lines will be description
2031 get_model_parameters_name = s;
2035 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2036 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2037 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2038 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2039 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2040 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2041 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2044 get_model_parameters_sex = s;
2046 get_model_parameters_weight = stof(s);
2048 get_model_parameters_age = stof(s);
2051 while((s = fgets(fh)))
2053 if(get_model_parameters_desc)
2054 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2056 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2064 vector vec2(vector v)
2071 vector NearestPointOnBox(entity box, vector org)
2073 vector m1, m2, nearest;
2075 m1 = box.mins + box.origin;
2076 m2 = box.maxs + box.origin;
2078 nearest_x = bound(m1_x, org_x, m2_x);
2079 nearest_y = bound(m1_y, org_y, m2_y);
2080 nearest_z = bound(m1_z, org_z, m2_z);
2086 float vercmp_recursive(string v1, string v2)
2092 dot1 = strstrofs(v1, ".", 0);
2093 dot2 = strstrofs(v2, ".", 0);
2097 s1 = substring(v1, 0, dot1);
2101 s2 = substring(v2, 0, dot2);
2103 r = stof(s1) - stof(s2);
2107 r = strcasecmp(s1, s2);
2120 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2123 float vercmp(string v1, string v2)
2125 if(strcasecmp(v1, v2) == 0) // early out check
2134 return vercmp_recursive(v1, v2);
2137 float u8_strsize(string s)
2157 // translation helpers
2158 string language_filename(string s)
2163 if(fn == "" || fn == "dump")
2165 fn = strcat(s, ".", fn);
2166 if((fh = fopen(fn, FILE_READ)) >= 0)
2173 string CTX(string s)
2175 float p = strstrofs(s, "^", 0);
2178 return substring(s, p+1, -1);
2181 // x-encoding (encoding as zero length invisible string)
2182 const string XENCODE_2 = "xX";
2183 const string XENCODE_22 = "0123456789abcdefABCDEF";
2184 string xencode(float f)
2187 d = mod(f, 22); f = floor(f / 22);
2188 c = mod(f, 22); f = floor(f / 22);
2189 b = mod(f, 22); f = floor(f / 22);
2190 a = mod(f, 2); // f = floor(f / 2);
2193 substring(XENCODE_2, a, 1),
2194 substring(XENCODE_22, b, 1),
2195 substring(XENCODE_22, c, 1),
2196 substring(XENCODE_22, d, 1)
2199 float xdecode(string s)
2202 if(substring(s, 0, 1) != "^")
2206 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2207 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2208 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2209 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2210 if(a < 0 || b < 0 || c < 0 || d < 0)
2212 return ((a * 22 + b) * 22 + c) * 22 + d;
2215 float lowestbit(float f)
2226 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2228 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2231 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2234 // escape the string to make it safe for consoles
2235 string MakeConsoleSafe(string input)
2237 input = strreplace("\n", "", input);
2238 input = strreplace("\\", "\\\\", input);
2239 input = strreplace("$", "$$", input);
2240 input = strreplace("\"", "\\\"", input);
2245 // get true/false value of a string with multiple different inputs
2246 float InterpretBoolean(string input)
2248 switch(strtolower(input))
2260 default: return stof(input);
2266 entity ReadCSQCEntity()
2272 return findfloat(world, entnum, f);
2276 float shutdown_running;
2281 void CSQC_Shutdown()
2287 if(shutdown_running)
2289 print("Recursive shutdown detected! Only restoring cvars...\n");
2293 shutdown_running = 1;
2296 cvar_settemp_restore(); // this must be done LAST, but in any case
2299 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2300 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2301 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2302 // this will use the value:
2304 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2305 // accuracy at x is 1/derivative, i.e.
2306 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2308 void WriteApproxPastTime(float dst, float t)
2310 float dt = time - t;
2312 // warning: this is approximate; do not resend when you don't have to!
2313 // be careful with sendflags here!
2314 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2317 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2320 dt = rint(bound(0, dt, 255));
2326 float ReadApproxPastTime()
2328 float dt = ReadByte();
2330 // map from range...PPROXPASTTIME_MAX / 256
2331 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2333 return servertime - dt;
2338 .float skeleton_bones_index;
2339 void Skeleton_SetBones(entity e)
2341 // set skeleton_bones to the total number of bones on the model
2342 if(e.skeleton_bones_index == e.modelindex)
2343 return; // same model, nothing to update
2346 skelindex = skel_create(e.modelindex);
2347 e.skeleton_bones = skel_get_numbones(skelindex);
2348 skel_delete(skelindex);
2349 e.skeleton_bones_index = e.modelindex;
2353 string to_execute_next_frame;
2354 void execute_next_frame()
2356 if(to_execute_next_frame)
2358 localcmd("\n", to_execute_next_frame, "\n");
2359 strunzone(to_execute_next_frame);
2360 to_execute_next_frame = string_null;
2363 void queue_to_execute_next_frame(string s)
2365 if(to_execute_next_frame)
2367 s = strcat(s, "\n", to_execute_next_frame);
2368 strunzone(to_execute_next_frame);
2370 to_execute_next_frame = strzone(s);
2373 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2376 ((( startspeedfactor + endspeedfactor - 2
2377 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2378 ) * x + startspeedfactor
2382 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2384 if(startspeedfactor < 0 || endspeedfactor < 0)
2388 // if this is the case, the possible zeros of the first derivative are outside
2390 We can calculate this condition as condition
2395 // better, see below:
2396 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2399 // if this is the case, the first derivative has no zeros at all
2400 float se = startspeedfactor + endspeedfactor;
2401 float s_e = startspeedfactor - endspeedfactor;
2402 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2405 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2406 // we also get s_e <= 6 - se
2407 // 3 * (se - 4)^2 + (6 - se)^2
2408 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2409 // Therefore, above "better" check works!
2413 // known good cases:
2421 // (3.5, [0.2..2.3])
2426 vector cliptoplane(vector v, vector p)
2428 return v - (v * p) * p;
2431 vector solve_cubic_pq(float p, float q)
2434 D = q*q/4.0 + p*p*p/27.0;
2438 a = 1.0/3.0 * acos(-q/2.0 * sqrt(-27.0/(p*p*p)));
2439 u = sqrt(-4.0/3.0 * p);
2440 // a in range 0..pi/3
2447 '1 0 0' * cos(a + 2.0/3.0*M_PI)
2449 '0 1 0' * cos(a + 4.0/3.0*M_PI)
2462 return '1 1 0' * v + '0 0 1' * u;
2464 return '0 1 1' * v + '1 0 0' * u;
2469 u = cbrt(-q/2.0 + sqrt(D));
2470 v = cbrt(-q/2.0 - sqrt(D));
2471 return '1 1 1' * (u + v);
2474 vector solve_cubic_abcd(float a, float b, float c, float d)
2480 p = (9*a*c - 3*b*b);
2481 q = (27*a*a*d - 9*a*b*c + 2*b*b*b);
2482 v = solve_cubic_pq(p, q);
2483 v = (v - b * '1 1 1') * (1.0 / (3.0 * a));
2485 v += '1 0 -1' * (v_z - v_x); // swap x, z
2489 vector findperpendicular(vector v)
2495 return normalize(cliptoplane(p, v));
2498 vector W_CalculateSpread(vector forward, float spread, float spreadfactor, float spreadstyle)
2504 spread *= spreadfactor; //g_weaponspreadfactor;
2507 sstyle = spreadstyle; //autocvar_g_projectiles_spread_style;
2511 // this is the baseline for the spread value!
2512 // standard deviation: sqrt(2/5)
2513 // density function: sqrt(1-r^2)
2514 return forward + randomvec() * spread;
2516 else if(sstyle == 1)
2518 // same thing, basically
2519 return normalize(forward + cliptoplane(randomvec() * spread, forward));
2521 else if(sstyle == 2)
2523 // circle spread... has at sigma=1 a standard deviation of sqrt(1/2)
2524 sigma = spread * 0.89442719099991587855; // match baseline stddev
2525 v1 = findperpendicular(forward);
2526 v2 = cross(forward, v1);
2527 // random point on unit circle
2528 dx = random() * 2 * M_PI;
2531 // radius in our dist function
2534 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
2536 else if(sstyle == 3) // gauss 3d
2538 sigma = spread * 0.44721359549996; // match baseline stddev
2539 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
2541 v1_x += gsl_ran_gaussian(sigma);
2542 v1_y += gsl_ran_gaussian(sigma);
2543 v1_z += gsl_ran_gaussian(sigma);
2546 else if(sstyle == 4) // gauss 2d
2548 sigma = spread * 0.44721359549996; // match baseline stddev
2549 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right
2550 v1_x = gsl_ran_gaussian(sigma);
2551 v1_y = gsl_ran_gaussian(sigma);
2552 v1_z = gsl_ran_gaussian(sigma);
2553 return normalize(forward + cliptoplane(v1, forward));
2555 else if(sstyle == 5) // 1-r
2557 sigma = spread * 1.154700538379252; // match baseline stddev
2558 v1 = findperpendicular(forward);
2559 v2 = cross(forward, v1);
2560 // random point on unit circle
2561 dx = random() * 2 * M_PI;
2564 // radius in our dist function
2566 r = solve_cubic_abcd(-2, 3, 0, -r) * '0 1 0';
2567 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
2569 else if(sstyle == 6) // 1-r^2
2571 sigma = spread * 1.095445115010332; // match baseline stddev
2572 v1 = findperpendicular(forward);
2573 v2 = cross(forward, v1);
2574 // random point on unit circle
2575 dx = random() * 2 * M_PI;
2578 // radius in our dist function
2582 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
2584 else if(sstyle == 7) // (1-r) (2-r)
2586 sigma = spread * 1.224744871391589; // match baseline stddev
2587 v1 = findperpendicular(forward);
2588 v2 = cross(forward, v1);
2589 // random point on unit circle
2590 dx = random() * 2 * M_PI;
2593 // radius in our dist function
2597 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);
2600 error("g_projectiles_spread_style must be 0 (sphere), 1 (flattened sphere), 2 (circle), 3 (gauss 3D), 4 (gauss plane), 5 (linear falloff), 6 (quadratic falloff), 7 (stronger falloff)!");
2603 * how to derive falloff functions:
2604 * rho(r) := (2-r) * (1-r);
2607 * rhor(r) := r * rho(r);
2608 * cr(t) := integrate(rhor(r), r, a, t);
2609 * scr(t) := integrate(rhor(r) * r^2, r, a, t);
2610 * variance : scr(b) / cr(b);
2611 * solve(cr(r) = rand * cr(b), r), programmmode:false;
2612 * sqrt(0.4 / variance), numer;