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)
1482 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1489 void shuffle(float n, swapfunc_t swap, entity pass)
1492 for(i = 1; i < n; ++i)
1494 // swap i-th item at a random position from 0 to i
1495 // proof for even distribution:
1498 // item n+1 gets at any position with chance 1/(n+1)
1499 // all others will get their 1/n chance reduced by factor n/(n+1)
1500 // to be on place n+1, their chance will be 1/(n+1)
1501 // 1/n * n/(n+1) = 1/(n+1)
1503 j = floor(random() * (i + 1));
1509 string substring_range(string s, float b, float e)
1511 return substring(s, b, e - b);
1514 string swapwords(string str, float i, float j)
1517 string s1, s2, s3, s4, s5;
1518 float si, ei, sj, ej, s0, en;
1519 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1520 si = argv_start_index(i);
1521 sj = argv_start_index(j);
1522 ei = argv_end_index(i);
1523 ej = argv_end_index(j);
1524 s0 = argv_start_index(0);
1525 en = argv_end_index(n-1);
1526 s1 = substring_range(str, s0, si);
1527 s2 = substring_range(str, si, ei);
1528 s3 = substring_range(str, ei, sj);
1529 s4 = substring_range(str, sj, ej);
1530 s5 = substring_range(str, ej, en);
1531 return strcat(s1, s4, s3, s2, s5);
1534 string _shufflewords_str;
1535 void _shufflewords_swapfunc(float i, float j, entity pass)
1537 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1539 string shufflewords(string str)
1542 _shufflewords_str = str;
1543 n = tokenizebyseparator(str, " ");
1544 shuffle(n, _shufflewords_swapfunc, world);
1545 str = _shufflewords_str;
1546 _shufflewords_str = string_null;
1550 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1566 // actually, every number solves the equation!
1577 if(a > 0) // put the smaller solution first
1579 v_x = ((-b)-D) / (2*a);
1580 v_y = ((-b)+D) / (2*a);
1584 v_x = (-b+D) / (2*a);
1585 v_y = (-b-D) / (2*a);
1591 // complex solutions!
1604 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1608 // make origin and speed relative
1613 // now solve for ret, ret normalized:
1614 // eorg + t * evel == t * ret * spd
1615 // or, rather, solve for t:
1616 // |eorg + t * evel| == t * spd
1617 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1618 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1619 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1620 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1621 // q = (eorg * eorg) / (evel * evel - spd * spd)
1622 if(!solution_z) // no real solution
1625 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1626 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1627 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1628 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1629 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1630 // spd < |evel| * sin angle(evel, eorg)
1633 else if(solution_x > 0)
1635 // both solutions > 0: take the smaller one
1636 // happens if p < 0 and q > 0
1637 ret = normalize(eorg + solution_x * evel);
1639 else if(solution_y > 0)
1641 // one solution > 0: take the larger one
1642 // happens if q < 0 or q == 0 and p < 0
1643 ret = normalize(eorg + solution_y * evel);
1647 // no solution > 0: reject
1648 // happens if p > 0 and q >= 0
1649 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1650 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1655 // "Enemy is moving away from me at more than spd"
1659 // NOTE: we always got a solution if spd > |evel|
1661 if(newton_style == 2)
1662 ret = normalize(ret * spd + myvel);
1667 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1672 if(newton_style == 2)
1674 // true Newtonian projectiles with automatic aim adjustment
1676 // solve: |outspeed * mydir - myvel| = spd
1677 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1678 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1682 // myvel^2 - (mydir * myvel)^2 > spd^2
1683 // velocity without mydir component > spd
1684 // fire at smallest possible spd that works?
1685 // |(mydir * myvel) * myvel - myvel| = spd
1687 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1691 outspeed = solution_y; // the larger one
1694 //outspeed = 0; // slowest possible shot
1695 outspeed = solution_x; // the real part (that is, the average!)
1696 //dprint("impossible shot, adjusting\n");
1699 outspeed = bound(spd * mi, outspeed, spd * ma);
1700 return mydir * outspeed;
1704 return myvel + spd * mydir;
1707 void check_unacceptable_compiler_bugs()
1709 if(cvar("_allow_unacceptable_compiler_bugs"))
1711 tokenize_console("foo bar");
1712 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1713 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.");
1717 error("The empty string counts as false. We do not want that!");
1720 float compressShotOrigin(vector v)
1724 y = rint(v_y * 4) + 128;
1725 z = rint(v_z * 4) + 128;
1726 if(x > 255 || x < 0)
1728 print("shot origin ", vtos(v), " x out of bounds\n");
1729 x = bound(0, x, 255);
1731 if(y > 255 || y < 0)
1733 print("shot origin ", vtos(v), " y out of bounds\n");
1734 y = bound(0, y, 255);
1736 if(z > 255 || z < 0)
1738 print("shot origin ", vtos(v), " z out of bounds\n");
1739 z = bound(0, z, 255);
1741 return x * 0x10000 + y * 0x100 + z;
1743 vector decompressShotOrigin(float f)
1746 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1747 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1748 v_z = ((f & 0xFF) - 128) / 4;
1752 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1754 float start, end, root, child;
1757 start = floor((n - 2) / 2);
1760 // siftdown(start, count-1);
1762 while(root * 2 + 1 <= n-1)
1764 child = root * 2 + 1;
1766 if(cmp(child, child+1, pass) < 0)
1768 if(cmp(root, child, pass) < 0)
1770 swap(root, child, pass);
1786 // siftdown(0, end);
1788 while(root * 2 + 1 <= end)
1790 child = root * 2 + 1;
1791 if(child < end && cmp(child, child+1, pass) < 0)
1793 if(cmp(root, child, pass) < 0)
1795 swap(root, child, pass);
1805 void RandomSelection_Init()
1807 RandomSelection_totalweight = 0;
1808 RandomSelection_chosen_ent = world;
1809 RandomSelection_chosen_float = 0;
1810 RandomSelection_chosen_string = string_null;
1811 RandomSelection_best_priority = -1;
1813 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1815 if(priority > RandomSelection_best_priority)
1817 RandomSelection_best_priority = priority;
1818 RandomSelection_chosen_ent = e;
1819 RandomSelection_chosen_float = f;
1820 RandomSelection_chosen_string = s;
1821 RandomSelection_totalweight = weight;
1823 else if(priority == RandomSelection_best_priority)
1825 RandomSelection_totalweight += weight;
1826 if(random() * RandomSelection_totalweight <= weight)
1828 RandomSelection_chosen_ent = e;
1829 RandomSelection_chosen_float = f;
1830 RandomSelection_chosen_string = s;
1835 vector healtharmor_maxdamage(float h, float a, float armorblock)
1837 // NOTE: we'll always choose the SMALLER value...
1838 float healthdamage, armordamage, armorideal;
1840 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1841 armordamage = a + (h - 1); // damage we can take if we could use more armor
1842 armorideal = healthdamage * armorblock;
1844 if(armordamage < healthdamage)
1857 vector healtharmor_applydamage(float a, float armorblock, float damage)
1860 v_y = bound(0, damage * armorblock, a); // save
1861 v_x = bound(0, damage - v_y, damage); // take
1866 string getcurrentmod()
1870 m = cvar_string("fs_gamedir");
1871 n = tokenize_console(m);
1883 v = ReadShort() * 256; // note: this is signed
1884 v += ReadByte(); // note: this is unsigned
1888 void WriteInt24_t(float dst, float val)
1891 WriteShort(dst, (v = floor(val / 256)));
1892 WriteByte(dst, val - v * 256); // 0..255
1897 float float2range11(float f)
1899 // continuous function mapping all reals into -1..1
1900 return f / (fabs(f) + 1);
1903 float float2range01(float f)
1905 // continuous function mapping all reals into 0..1
1906 return 0.5 + 0.5 * float2range11(f);
1909 // from the GNU Scientific Library
1910 float gsl_ran_gaussian_lastvalue;
1911 float gsl_ran_gaussian_lastvalue_set;
1912 float gsl_ran_gaussian(float sigma)
1915 if(gsl_ran_gaussian_lastvalue_set)
1917 gsl_ran_gaussian_lastvalue_set = 0;
1918 return sigma * gsl_ran_gaussian_lastvalue;
1922 a = random() * 2 * M_PI;
1923 b = sqrt(-2 * log(random()));
1924 gsl_ran_gaussian_lastvalue = cos(a) * b;
1925 gsl_ran_gaussian_lastvalue_set = 1;
1926 return sigma * sin(a) * b;
1930 string car(string s)
1933 o = strstrofs(s, " ", 0);
1936 return substring(s, 0, o);
1938 string cdr(string s)
1941 o = strstrofs(s, " ", 0);
1944 return substring(s, o + 1, strlen(s) - (o + 1));
1946 float matchacl(string acl, string str)
1953 t = car(acl); acl = cdr(acl);
1956 if(substring(t, 0, 1) == "-")
1959 t = substring(t, 1, strlen(t) - 1);
1961 else if(substring(t, 0, 1) == "+")
1962 t = substring(t, 1, strlen(t) - 1);
1964 if(substring(t, -1, 1) == "*")
1966 t = substring(t, 0, strlen(t) - 1);
1967 s = substring(str, 0, strlen(t));
1979 float startsWith(string haystack, string needle)
1981 return substring(haystack, 0, strlen(needle)) == needle;
1983 float startsWithNocase(string haystack, string needle)
1985 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1988 string get_model_datafilename(string m, float sk, string fil)
1993 m = "models/player/*_";
1995 m = strcat(m, ftos(sk));
1998 return strcat(m, ".", fil);
2001 float get_model_parameters(string m, float sk)
2006 get_model_parameters_modelname = string_null;
2007 get_model_parameters_modelskin = -1;
2008 get_model_parameters_name = string_null;
2009 get_model_parameters_species = -1;
2010 get_model_parameters_sex = string_null;
2011 get_model_parameters_weight = -1;
2012 get_model_parameters_age = -1;
2013 get_model_parameters_desc = string_null;
2014 get_model_parameters_bone_upperbody = string_null;
2015 get_model_parameters_bone_weapon = string_null;
2016 for(i = 0; i < MAX_AIM_BONES; ++i)
2018 get_model_parameters_bone_aim[i] = string_null;
2019 get_model_parameters_bone_aimweight[i] = 0;
2021 get_model_parameters_fixbone = 0;
2026 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2027 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2031 if(substring(m, -4, -1) != ".txt")
2033 if(substring(m, -6, 1) != "_")
2035 sk = stof(substring(m, -5, 1));
2036 m = substring(m, 0, -7);
2039 fn = get_model_datafilename(m, sk, "txt");
2040 fh = fopen(fn, FILE_READ);
2044 fn = get_model_datafilename(m, sk, "txt");
2045 fh = fopen(fn, FILE_READ);
2050 get_model_parameters_modelname = m;
2051 get_model_parameters_modelskin = sk;
2052 while((s = fgets(fh)))
2055 break; // next lines will be description
2059 get_model_parameters_name = s;
2063 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2064 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2065 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2066 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2067 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2068 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2069 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2072 get_model_parameters_sex = s;
2074 get_model_parameters_weight = stof(s);
2076 get_model_parameters_age = stof(s);
2077 if(c == "bone_upperbody")
2078 get_model_parameters_bone_upperbody = s;
2079 if(c == "bone_weapon")
2080 get_model_parameters_bone_weapon = s;
2081 for(i = 0; i < MAX_AIM_BONES; ++i)
2082 if(c == strcat("bone_aim", ftos(i)))
2084 get_model_parameters_bone_aimweight[i] = stof(car(s));
2085 get_model_parameters_bone_aim[i] = cdr(s);
2088 get_model_parameters_fixbone = stof(s);
2091 while((s = fgets(fh)))
2093 if(get_model_parameters_desc)
2094 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2096 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2104 vector vec2(vector v)
2111 vector NearestPointOnBox(entity box, vector org)
2113 vector m1, m2, nearest;
2115 m1 = box.mins + box.origin;
2116 m2 = box.maxs + box.origin;
2118 nearest_x = bound(m1_x, org_x, m2_x);
2119 nearest_y = bound(m1_y, org_y, m2_y);
2120 nearest_z = bound(m1_z, org_z, m2_z);
2126 float vercmp_recursive(string v1, string v2)
2132 dot1 = strstrofs(v1, ".", 0);
2133 dot2 = strstrofs(v2, ".", 0);
2137 s1 = substring(v1, 0, dot1);
2141 s2 = substring(v2, 0, dot2);
2143 r = stof(s1) - stof(s2);
2147 r = strcasecmp(s1, s2);
2160 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2163 float vercmp(string v1, string v2)
2165 if(strcasecmp(v1, v2) == 0) // early out check
2174 return vercmp_recursive(v1, v2);
2177 float u8_strsize(string s)
2197 // translation helpers
2198 string language_filename(string s)
2203 if(fn == "" || fn == "dump")
2205 fn = strcat(s, ".", fn);
2206 if((fh = fopen(fn, FILE_READ)) >= 0)
2213 string CTX(string s)
2215 float p = strstrofs(s, "^", 0);
2218 return substring(s, p+1, -1);
2221 // x-encoding (encoding as zero length invisible string)
2222 const string XENCODE_2 = "xX";
2223 const string XENCODE_22 = "0123456789abcdefABCDEF";
2224 string xencode(float f)
2227 d = mod(f, 22); f = floor(f / 22);
2228 c = mod(f, 22); f = floor(f / 22);
2229 b = mod(f, 22); f = floor(f / 22);
2230 a = mod(f, 2); // f = floor(f / 2);
2233 substring(XENCODE_2, a, 1),
2234 substring(XENCODE_22, b, 1),
2235 substring(XENCODE_22, c, 1),
2236 substring(XENCODE_22, d, 1)
2239 float xdecode(string s)
2242 if(substring(s, 0, 1) != "^")
2246 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2247 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2248 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2249 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2250 if(a < 0 || b < 0 || c < 0 || d < 0)
2252 return ((a * 22 + b) * 22 + c) * 22 + d;
2255 float lowestbit(float f)
2266 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2268 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2271 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2274 // escape the string to make it safe for consoles
2275 string MakeConsoleSafe(string input)
2277 input = strreplace("\n", "", input);
2278 input = strreplace("\\", "\\\\", input);
2279 input = strreplace("$", "$$", input);
2280 input = strreplace("\"", "\\\"", input);
2285 // get true/false value of a string with multiple different inputs
2286 float InterpretBoolean(string input)
2288 switch(strtolower(input))
2300 default: return stof(input);
2306 entity ReadCSQCEntity()
2312 return findfloat(world, entnum, f);
2316 float shutdown_running;
2321 void CSQC_Shutdown()
2327 if(shutdown_running)
2329 print("Recursive shutdown detected! Only restoring cvars...\n");
2333 shutdown_running = 1;
2336 cvar_settemp_restore(); // this must be done LAST, but in any case
2339 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2340 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2341 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2342 // this will use the value:
2344 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2345 // accuracy at x is 1/derivative, i.e.
2346 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2348 void WriteApproxPastTime(float dst, float t)
2350 float dt = time - t;
2352 // warning: this is approximate; do not resend when you don't have to!
2353 // be careful with sendflags here!
2354 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2357 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2360 dt = rint(bound(0, dt, 255));
2366 float ReadApproxPastTime()
2368 float dt = ReadByte();
2370 // map from range...PPROXPASTTIME_MAX / 256
2371 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2373 return servertime - dt;
2378 .float skeleton_bones_index;
2379 void Skeleton_SetBones(entity e)
2381 // set skeleton_bones to the total number of bones on the model
2382 if(e.skeleton_bones_index == e.modelindex)
2383 return; // same model, nothing to update
2386 skelindex = skel_create(e.modelindex);
2387 e.skeleton_bones = skel_get_numbones(skelindex);
2388 skel_delete(skelindex);
2389 e.skeleton_bones_index = e.modelindex;
2393 string to_execute_next_frame;
2394 void execute_next_frame()
2396 if(to_execute_next_frame)
2398 localcmd("\n", to_execute_next_frame, "\n");
2399 strunzone(to_execute_next_frame);
2400 to_execute_next_frame = string_null;
2403 void queue_to_execute_next_frame(string s)
2405 if(to_execute_next_frame)
2407 s = strcat(s, "\n", to_execute_next_frame);
2408 strunzone(to_execute_next_frame);
2410 to_execute_next_frame = strzone(s);
2413 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2416 ((( startspeedfactor + endspeedfactor - 2
2417 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2418 ) * x + startspeedfactor
2422 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2424 if(startspeedfactor < 0 || endspeedfactor < 0)
2428 // if this is the case, the possible zeros of the first derivative are outside
2430 We can calculate this condition as condition
2435 // better, see below:
2436 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2439 // if this is the case, the first derivative has no zeros at all
2440 float se = startspeedfactor + endspeedfactor;
2441 float s_e = startspeedfactor - endspeedfactor;
2442 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2445 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2446 // we also get s_e <= 6 - se
2447 // 3 * (se - 4)^2 + (6 - se)^2
2448 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2449 // Therefore, above "better" check works!
2453 // known good cases:
2461 // (3.5, [0.2..2.3])
2465 .float FindConnectedComponent_processing;
2466 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2468 entity queue_start, queue_end;
2470 // we build a queue of to-be-processed entities.
2471 // queue_start is the next entity to be checked for neighbors
2472 // queue_end is the last entity added
2474 if(e.FindConnectedComponent_processing)
2475 error("recursion or broken cleanup");
2477 // start with a 1-element queue
2478 queue_start = queue_end = e;
2479 queue_end.fld = world;
2480 queue_end.FindConnectedComponent_processing = 1;
2482 // for each queued item:
2483 for(; queue_start; queue_start = queue_start.fld)
2485 // find all neighbors of queue_start
2487 for(t = world; (t = nxt(t, queue_start, pass)); )
2489 if(t.FindConnectedComponent_processing)
2491 if(iscon(t, queue_start, pass))
2493 // it is connected? ADD IT. It will look for neighbors soon too.
2496 queue_end.fld = world;
2497 queue_end.FindConnectedComponent_processing = 1;
2503 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2504 queue_start.FindConnectedComponent_processing = 0;
2508 vector combine_to_vector(float x, float y, float z)
2510 vector result; result_x = x; result_y = y; result_z = z;
2514 vector get_corner_position(entity box, float corner)
2518 case 1: return combine_to_vector(box.absmin_x, box.absmin_y, box.absmin_z);
2519 case 2: return combine_to_vector(box.absmax_x, box.absmin_y, box.absmin_z);
2520 case 3: return combine_to_vector(box.absmin_x, box.absmax_y, box.absmin_z);
2521 case 4: return combine_to_vector(box.absmin_x, box.absmin_y, box.absmax_z);
2522 case 5: return combine_to_vector(box.absmax_x, box.absmax_y, box.absmin_z);
2523 case 6: return combine_to_vector(box.absmin_x, box.absmax_y, box.absmax_z);
2524 case 7: return combine_to_vector(box.absmax_x, box.absmin_y, box.absmax_z);
2525 case 8: return combine_to_vector(box.absmax_x, box.absmax_y, box.absmax_z);
2526 default: return '0 0 0';
2531 // todo: this sucks, lets find a better way to do backtraces?
2533 void backtrace(string msg)
2537 dev = autocvar_developer;
2538 war = autocvar_prvm_backtraceforwarnings;
2540 dev = cvar("developer");
2541 war = cvar("prvm_backtraceforwarnings");
2543 cvar_set("developer", "1");
2544 cvar_set("prvm_backtraceforwarnings", "1");
2546 print("--- CUT HERE ---\nWARNING: ");
2549 remove(world); // isn't there any better way to cause a backtrace?
2550 print("\n--- CUT UNTIL HERE ---\n");
2551 cvar_set("developer", ftos(dev));
2552 cvar_set("prvm_backtraceforwarnings", ftos(war));
2556 // color code replace, place inside of sprintf and parse the string
2557 string CCR(string input)
2559 // See the autocvar declarations in util.qh for default values
2561 // foreground/normal colors
2562 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2563 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2564 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2565 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2568 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2569 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2570 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2572 // background colors
2573 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2574 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2578 vector vec3(float x, float y, float z)
2588 vector animfixfps(entity e, vector a, vector b)
2590 // multi-frame anim: keep as-is
2594 dur = frameduration(e.modelindex, a_x);
2598 dur = frameduration(e.modelindex, a_x);
2608 void dedicated_print(string input) // print(), but only print if the server is not local
2610 if(server_is_dedicated) { print(input); }
2615 float Announcer_PickNumber(float num)
2619 case 10: num = ANNCE_NUM_10; break;
2620 case 9: num = ANNCE_NUM_9; break;
2621 case 8: num = ANNCE_NUM_8; break;
2622 case 7: num = ANNCE_NUM_7; break;
2623 case 6: num = ANNCE_NUM_6; break;
2624 case 5: num = ANNCE_NUM_5; break;
2625 case 4: num = ANNCE_NUM_4; break;
2626 case 3: num = ANNCE_NUM_3; break;
2627 case 2: num = ANNCE_NUM_2; break;
2628 case 1: num = ANNCE_NUM_1; break;