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);
206 vector colormapPaletteColor(float c, float isPants)
210 case 0: return '1.000000 1.000000 1.000000';
211 case 1: return '1.000000 0.333333 0.000000';
212 case 2: return '0.000000 1.000000 0.501961';
213 case 3: return '0.000000 1.000000 0.000000';
214 case 4: return '1.000000 0.000000 0.000000';
215 case 5: return '0.000000 0.666667 1.000000';
216 case 6: return '0.000000 1.000000 1.000000';
217 case 7: return '0.501961 1.000000 0.000000';
218 case 8: return '0.501961 0.000000 1.000000';
219 case 9: return '1.000000 0.000000 1.000000';
220 case 10: return '1.000000 0.000000 0.501961';
221 case 11: return '0.000000 0.000000 1.000000';
222 case 12: return '1.000000 1.000000 0.000000';
223 case 13: return '0.000000 0.333333 1.000000';
224 case 14: return '1.000000 0.666667 0.000000';
228 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
229 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
230 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
233 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
234 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
235 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
236 default: return '0.000 0.000 0.000';
240 // unzone the string, and return it as tempstring. Safe to be called on string_null
241 string fstrunzone(string s)
251 float fexists(string f)
254 fh = fopen(f, FILE_READ);
261 // Databases (hash tables)
262 #define DB_BUCKETS 8192
263 void db_save(float db, string pFilename)
266 fh = fopen(pFilename, FILE_WRITE);
269 print(strcat("^1Can't write DB to ", pFilename));
273 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
274 for(i = 0; i < n; ++i)
275 fputs(fh, strcat(bufstr_get(db, i), "\n"));
284 float db_load(string pFilename)
286 float db, fh, i, j, n;
291 fh = fopen(pFilename, FILE_READ);
295 if(stof(l) == DB_BUCKETS)
298 while((l = fgets(fh)))
301 bufstr_set(db, i, l);
307 // different count of buckets, or a dump?
308 // need to reorganize the database then (SLOW)
310 // note: we also parse the first line (l) in case the DB file is
311 // missing the bucket count
314 n = tokenizebyseparator(l, "\\");
315 for(j = 2; j < n; j += 2)
316 db_put(db, argv(j-1), uri_unescape(argv(j)));
318 while((l = fgets(fh)));
324 void db_dump(float db, string pFilename)
326 float fh, i, j, n, m;
327 fh = fopen(pFilename, FILE_WRITE);
329 error(strcat("Can't dump DB to ", pFilename));
332 for(i = 0; i < n; ++i)
334 m = tokenizebyseparator(bufstr_get(db, i), "\\");
335 for(j = 2; j < m; j += 2)
336 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
341 void db_close(float db)
346 string db_get(float db, string pKey)
349 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
350 return uri_unescape(infoget(bufstr_get(db, h), pKey));
353 void db_put(float db, string pKey, string pValue)
356 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
357 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
364 db = db_load("foo.db");
365 print("LOADED. FILL...\n");
366 for(i = 0; i < DB_BUCKETS; ++i)
367 db_put(db, ftos(random()), "X");
368 print("FILLED. SAVE...\n");
369 db_save(db, "foo.db");
370 print("SAVED. CLOSE...\n");
375 // Multiline text file buffers
376 float buf_load(string pFilename)
383 fh = fopen(pFilename, FILE_READ);
390 while((l = fgets(fh)))
392 bufstr_set(buf, i, l);
399 void buf_save(float buf, string pFilename)
402 fh = fopen(pFilename, FILE_WRITE);
404 error(strcat("Can't write buf to ", pFilename));
405 n = buf_getsize(buf);
406 for(i = 0; i < n; ++i)
407 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
411 string mmsss(float tenths)
415 tenths = floor(tenths + 0.5);
416 minutes = floor(tenths / 600);
417 tenths -= minutes * 600;
418 s = ftos(1000 + tenths);
419 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
422 string mmssss(float hundredths)
426 hundredths = floor(hundredths + 0.5);
427 minutes = floor(hundredths / 6000);
428 hundredths -= minutes * 6000;
429 s = ftos(10000 + hundredths);
430 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
433 string ScoreString(float pFlags, float pValue)
438 pValue = floor(pValue + 0.5); // round
440 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
442 else if(pFlags & SFL_RANK)
444 valstr = ftos(pValue);
446 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
447 valstr = strcat(valstr, "th");
448 else if(substring(valstr, l - 1, 1) == "1")
449 valstr = strcat(valstr, "st");
450 else if(substring(valstr, l - 1, 1) == "2")
451 valstr = strcat(valstr, "nd");
452 else if(substring(valstr, l - 1, 1) == "3")
453 valstr = strcat(valstr, "rd");
455 valstr = strcat(valstr, "th");
457 else if(pFlags & SFL_TIME)
458 valstr = TIME_ENCODED_TOSTRING(pValue);
460 valstr = ftos(pValue);
465 float dotproduct(vector a, vector b)
467 return a_x * b_x + a_y * b_y + a_z * b_z;
470 vector cross(vector a, vector b)
473 '1 0 0' * (a_y * b_z - a_z * b_y)
474 + '0 1 0' * (a_z * b_x - a_x * b_z)
475 + '0 0 1' * (a_x * b_y - a_y * b_x);
478 // compressed vector format:
479 // like MD3, just even shorter
480 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
481 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
482 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
483 // length = 2^(length_encoded/8) / 8
484 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
485 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
486 // the special value 0 indicates the zero vector
488 float lengthLogTable[128];
490 float invertLengthLog(float x)
492 float l, r, m, lerr, rerr;
494 if(x >= lengthLogTable[127])
496 if(x <= lengthLogTable[0])
504 m = floor((l + r) / 2);
505 if(lengthLogTable[m] < x)
511 // now: r is >=, l is <
512 lerr = (x - lengthLogTable[l]);
513 rerr = (lengthLogTable[r] - x);
519 vector decompressShortVector(float data)
525 p = (data & 0xF000) / 0x1000;
526 y = (data & 0x0F80) / 0x80;
527 len = (data & 0x007F);
529 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
542 y = .19634954084936207740 * y;
543 p = .19634954084936207740 * p - 1.57079632679489661922;
544 out_x = cos(y) * cos(p);
545 out_y = sin(y) * cos(p);
549 //print("decompressed: ", vtos(out), "\n");
551 return out * lengthLogTable[len];
554 float compressShortVector(vector vec)
560 //print("compress: ", vtos(vec), "\n");
561 ang = vectoangles(vec);
565 if(ang_x < -90 && ang_x > +90)
566 error("BOGUS vectoangles");
567 //print("angles: ", vtos(ang), "\n");
569 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
578 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
579 len = invertLengthLog(vlen(vec));
581 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
583 return (p * 0x1000) + (y * 0x80) + len;
586 void compressShortVector_init()
591 for(i = 0; i < 128; ++i)
593 lengthLogTable[i] = l;
597 if(cvar("developer"))
599 print("Verifying vector compression table...\n");
600 for(i = 0x0F00; i < 0xFFFF; ++i)
601 if(i != compressShortVector(decompressShortVector(i)))
603 print("BROKEN vector compression: ", ftos(i));
604 print(" -> ", vtos(decompressShortVector(i)));
605 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
614 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
616 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
619 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
620 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
632 string fixPriorityList(string order, float from, float to, float subtract, float complete)
637 n = tokenize_console(order);
639 for(i = 0; i < n; ++i)
644 if(w >= from && w <= to)
645 neworder = strcat(neworder, ftos(w), " ");
649 if(w >= from && w <= to)
650 neworder = strcat(neworder, ftos(w), " ");
657 n = tokenize_console(neworder);
658 for(w = to; w >= from; --w)
660 for(i = 0; i < n; ++i)
661 if(stof(argv(i)) == w)
663 if(i == n) // not found
664 neworder = strcat(neworder, ftos(w), " ");
668 return substring(neworder, 0, strlen(neworder) - 1);
671 string mapPriorityList(string order, string(string) mapfunc)
676 n = tokenize_console(order);
678 for(i = 0; i < n; ++i)
679 neworder = strcat(neworder, mapfunc(argv(i)), " ");
681 return substring(neworder, 0, strlen(neworder) - 1);
684 string swapInPriorityList(string order, float i, float j)
689 n = tokenize_console(order);
691 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
694 for(w = 0; w < n; ++w)
697 s = strcat(s, argv(j), " ");
699 s = strcat(s, argv(i), " ");
701 s = strcat(s, argv(w), " ");
703 return substring(s, 0, strlen(s) - 1);
709 float cvar_value_issafe(string s)
711 if(strstrofs(s, "\"", 0) >= 0)
713 if(strstrofs(s, "\\", 0) >= 0)
715 if(strstrofs(s, ";", 0) >= 0)
717 if(strstrofs(s, "$", 0) >= 0)
719 if(strstrofs(s, "\r", 0) >= 0)
721 if(strstrofs(s, "\n", 0) >= 0)
727 void get_mi_min_max(float mode)
732 strunzone(mi_shortname);
733 mi_shortname = mapname;
734 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
735 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
736 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
737 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
738 mi_shortname = strzone(mi_shortname);
750 MapInfo_Get_ByName(mi_shortname, 0, 0);
751 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
753 mi_min = MapInfo_Map_mins;
754 mi_max = MapInfo_Map_maxs;
762 tracebox('1 0 0' * mi_x,
763 '0 1 0' * mi_y + '0 0 1' * mi_z,
764 '0 1 0' * ma_y + '0 0 1' * ma_z,
768 if(!trace_startsolid)
769 mi_min_x = trace_endpos_x;
771 tracebox('0 1 0' * mi_y,
772 '1 0 0' * mi_x + '0 0 1' * mi_z,
773 '1 0 0' * ma_x + '0 0 1' * ma_z,
777 if(!trace_startsolid)
778 mi_min_y = trace_endpos_y;
780 tracebox('0 0 1' * mi_z,
781 '1 0 0' * mi_x + '0 1 0' * mi_y,
782 '1 0 0' * ma_x + '0 1 0' * ma_y,
786 if(!trace_startsolid)
787 mi_min_z = trace_endpos_z;
789 tracebox('1 0 0' * ma_x,
790 '0 1 0' * mi_y + '0 0 1' * mi_z,
791 '0 1 0' * ma_y + '0 0 1' * ma_z,
795 if(!trace_startsolid)
796 mi_max_x = trace_endpos_x;
798 tracebox('0 1 0' * ma_y,
799 '1 0 0' * mi_x + '0 0 1' * mi_z,
800 '1 0 0' * ma_x + '0 0 1' * ma_z,
804 if(!trace_startsolid)
805 mi_max_y = trace_endpos_y;
807 tracebox('0 0 1' * ma_z,
808 '1 0 0' * mi_x + '0 1 0' * mi_y,
809 '1 0 0' * ma_x + '0 1 0' * ma_y,
813 if(!trace_startsolid)
814 mi_max_z = trace_endpos_z;
819 void get_mi_min_max_texcoords(float mode)
823 get_mi_min_max(mode);
828 // extend mi_picmax to get a square aspect ratio
829 // center the map in that area
830 extend = mi_picmax - mi_picmin;
831 if(extend_y > extend_x)
833 mi_picmin_x -= (extend_y - extend_x) * 0.5;
834 mi_picmax_x += (extend_y - extend_x) * 0.5;
838 mi_picmin_y -= (extend_x - extend_y) * 0.5;
839 mi_picmax_y += (extend_x - extend_y) * 0.5;
842 // add another some percent
843 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
847 // calculate the texcoords
848 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
849 // first the two corners of the origin
850 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
851 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
852 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
853 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
854 // then the other corners
855 mi_pictexcoord1_x = mi_pictexcoord0_x;
856 mi_pictexcoord1_y = mi_pictexcoord2_y;
857 mi_pictexcoord3_x = mi_pictexcoord2_x;
858 mi_pictexcoord3_y = mi_pictexcoord0_y;
862 float cvar_settemp(string tmp_cvar, string tmp_value)
864 float created_saved_value;
867 created_saved_value = 0;
869 if (!(tmp_cvar || tmp_value))
871 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
875 if(!cvar_type(tmp_cvar))
877 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
881 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
882 if(e.netname == tmp_cvar)
883 created_saved_value = -1; // skip creation
885 if(created_saved_value != -1)
887 // creating a new entity to keep track of this cvar
889 e.classname = "saved_cvar_value";
890 e.netname = strzone(tmp_cvar);
891 e.message = strzone(cvar_string(tmp_cvar));
892 created_saved_value = 1;
895 // update the cvar to the value given
896 cvar_set(tmp_cvar, tmp_value);
898 return created_saved_value;
901 float cvar_settemp_restore()
905 while((e = find(e, classname, "saved_cvar_value")))
907 if(cvar_type(e.netname))
909 cvar_set(e.netname, e.message);
914 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
920 float almost_equals(float a, float b)
923 eps = (max(a, -a) + max(b, -b)) * 0.001;
924 if(a - b < eps && b - a < eps)
929 float almost_in_bounds(float a, float b, float c)
932 eps = (max(a, -a) + max(c, -c)) * 0.001;
935 return b == median(a - eps, b, c + eps);
938 float power2of(float e)
942 float log2of(float x)
944 // NOTE: generated code
1017 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1021 else if(ma == rgb_x)
1024 return (rgb_y - rgb_z) / (ma - mi);
1026 return (rgb_y - rgb_z) / (ma - mi) + 6;
1028 else if(ma == rgb_y)
1029 return (rgb_z - rgb_x) / (ma - mi) + 2;
1030 else // if(ma == rgb_z)
1031 return (rgb_x - rgb_y) / (ma - mi) + 4;
1034 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1038 hue -= 6 * floor(hue / 6);
1040 //else if(ma == rgb_x)
1041 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1045 rgb_y = hue * (ma - mi) + mi;
1048 //else if(ma == rgb_y)
1049 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1052 rgb_x = (2 - hue) * (ma - mi) + mi;
1060 rgb_z = (hue - 2) * (ma - mi) + mi;
1062 //else // if(ma == rgb_z)
1063 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1067 rgb_y = (4 - hue) * (ma - mi) + mi;
1072 rgb_x = (hue - 4) * (ma - mi) + mi;
1076 //else if(ma == rgb_x)
1077 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1078 else // if(hue <= 6)
1082 rgb_z = (6 - hue) * (ma - mi) + mi;
1088 vector rgb_to_hsv(vector rgb)
1093 mi = min(rgb_x, rgb_y, rgb_z);
1094 ma = max(rgb_x, rgb_y, rgb_z);
1096 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1107 vector hsv_to_rgb(vector hsv)
1109 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1112 vector rgb_to_hsl(vector rgb)
1117 mi = min(rgb_x, rgb_y, rgb_z);
1118 ma = max(rgb_x, rgb_y, rgb_z);
1120 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1122 hsl_z = 0.5 * (mi + ma);
1125 else if(hsl_z <= 0.5)
1126 hsl_y = (ma - mi) / (2*hsl_z);
1127 else // if(hsl_z > 0.5)
1128 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1133 vector hsl_to_rgb(vector hsl)
1135 float mi, ma, maminusmi;
1138 maminusmi = hsl_y * 2 * hsl_z;
1140 maminusmi = hsl_y * (2 - 2 * hsl_z);
1142 // hsl_z = 0.5 * mi + 0.5 * ma
1143 // maminusmi = - mi + ma
1144 mi = hsl_z - 0.5 * maminusmi;
1145 ma = hsl_z + 0.5 * maminusmi;
1147 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1150 string rgb_to_hexcolor(vector rgb)
1155 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1156 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1157 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1161 // requires that m2>m1 in all coordinates, and that m4>m3
1162 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;}
1164 // requires the same, but is a stronger condition
1165 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;}
1170 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1173 // The following function is SLOW.
1174 // For your safety and for the protection of those around you...
1175 // DO NOT CALL THIS AT HOME.
1176 // No really, don't.
1177 if(w(theText, theSize) <= maxWidth)
1178 return strlen(theText); // yeah!
1180 // binary search for right place to cut string
1182 float left, right, middle; // this always works
1184 right = strlen(theText); // this always fails
1187 middle = floor((left + right) / 2);
1188 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1193 while(left < right - 1);
1195 if(w("^7", theSize) == 0) // detect color codes support in the width function
1197 // NOTE: when color codes are involved, this binary search is,
1198 // mathematically, BROKEN. However, it is obviously guaranteed to
1199 // terminate, as the range still halves each time - but nevertheless, it is
1200 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1201 // range, and "right" is outside).
1203 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1204 // and decrease left on the basis of the chars detected of the truncated tag
1205 // Even if the ^xrgb tag is not complete/correct, left is decreased
1206 // (sometimes too much but with a correct result)
1207 // it fixes also ^[0-9]
1208 while(left >= 1 && substring(theText, left-1, 1) == "^")
1211 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1213 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1215 ch = str2chr(theText, left-1);
1216 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1219 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1221 ch = str2chr(theText, left-2);
1222 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1224 ch = str2chr(theText, left-1);
1225 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1234 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1237 // The following function is SLOW.
1238 // For your safety and for the protection of those around you...
1239 // DO NOT CALL THIS AT HOME.
1240 // No really, don't.
1241 if(w(theText) <= maxWidth)
1242 return strlen(theText); // yeah!
1244 // binary search for right place to cut string
1246 float left, right, middle; // this always works
1248 right = strlen(theText); // this always fails
1251 middle = floor((left + right) / 2);
1252 if(w(substring(theText, 0, middle)) <= maxWidth)
1257 while(left < right - 1);
1259 if(w("^7") == 0) // detect color codes support in the width function
1261 // NOTE: when color codes are involved, this binary search is,
1262 // mathematically, BROKEN. However, it is obviously guaranteed to
1263 // terminate, as the range still halves each time - but nevertheless, it is
1264 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1265 // range, and "right" is outside).
1267 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1268 // and decrease left on the basis of the chars detected of the truncated tag
1269 // Even if the ^xrgb tag is not complete/correct, left is decreased
1270 // (sometimes too much but with a correct result)
1271 // it fixes also ^[0-9]
1272 while(left >= 1 && substring(theText, left-1, 1) == "^")
1275 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1277 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1279 ch = str2chr(theText, left-1);
1280 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1283 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1285 ch = str2chr(theText, left-2);
1286 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1288 ch = str2chr(theText, left-1);
1289 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1298 string find_last_color_code(string s)
1300 float start, len, i, carets;
1301 start = strstrofs(s, "^", 0);
1302 if (start == -1) // no caret found
1305 for(i = len; i >= start; --i)
1307 if(substring(s, i, 1) != "^")
1311 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1314 // check if carets aren't all escaped
1318 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1319 return substring(s, i, 2);
1322 if(substring(s, i+1, 1) == "x")
1323 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1324 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1325 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1326 return substring(s, i, 5);
1328 i -= carets; // this also skips one char before the carets
1334 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1340 s = getWrappedLine_remaining;
1344 getWrappedLine_remaining = string_null;
1345 return s; // the line has no size ANYWAY, nothing would be displayed.
1348 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1349 if(cantake > 0 && cantake < strlen(s))
1352 while(take > 0 && substring(s, take, 1) != " ")
1356 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1357 if(getWrappedLine_remaining == "")
1358 getWrappedLine_remaining = string_null;
1359 else if (tw("^7", theFontSize) == 0)
1360 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1361 return substring(s, 0, cantake);
1365 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1366 if(getWrappedLine_remaining == "")
1367 getWrappedLine_remaining = string_null;
1368 else if (tw("^7", theFontSize) == 0)
1369 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1370 return substring(s, 0, take);
1375 getWrappedLine_remaining = string_null;
1380 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1386 s = getWrappedLine_remaining;
1390 getWrappedLine_remaining = string_null;
1391 return s; // the line has no size ANYWAY, nothing would be displayed.
1394 cantake = textLengthUpToLength(s, w, tw);
1395 if(cantake > 0 && cantake < strlen(s))
1398 while(take > 0 && substring(s, take, 1) != " ")
1402 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1403 if(getWrappedLine_remaining == "")
1404 getWrappedLine_remaining = string_null;
1405 else if (tw("^7") == 0)
1406 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1407 return substring(s, 0, cantake);
1411 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1412 if(getWrappedLine_remaining == "")
1413 getWrappedLine_remaining = string_null;
1414 else if (tw("^7") == 0)
1415 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1416 return substring(s, 0, take);
1421 getWrappedLine_remaining = string_null;
1426 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1428 if(tw(theText, theFontSize) <= maxWidth)
1431 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1434 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1436 if(tw(theText) <= maxWidth)
1439 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1442 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1444 string subpattern, subpattern2, subpattern3, subpattern4;
1445 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1447 subpattern2 = ",teams,";
1449 subpattern2 = ",noteams,";
1451 subpattern3 = ",teamspawns,";
1453 subpattern3 = ",noteamspawns,";
1454 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1455 subpattern4 = ",race,";
1457 subpattern4 = string_null;
1459 if(substring(pattern, 0, 1) == "-")
1461 pattern = substring(pattern, 1, strlen(pattern) - 1);
1462 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1464 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1466 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1468 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1473 if(substring(pattern, 0, 1) == "+")
1474 pattern = substring(pattern, 1, strlen(pattern) - 1);
1475 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1476 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1477 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1481 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1488 void shuffle(float n, swapfunc_t swap, entity pass)
1491 for(i = 1; i < n; ++i)
1493 // swap i-th item at a random position from 0 to i
1494 // proof for even distribution:
1497 // item n+1 gets at any position with chance 1/(n+1)
1498 // all others will get their 1/n chance reduced by factor n/(n+1)
1499 // to be on place n+1, their chance will be 1/(n+1)
1500 // 1/n * n/(n+1) = 1/(n+1)
1502 j = floor(random() * (i + 1));
1508 string substring_range(string s, float b, float e)
1510 return substring(s, b, e - b);
1513 string swapwords(string str, float i, float j)
1516 string s1, s2, s3, s4, s5;
1517 float si, ei, sj, ej, s0, en;
1518 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1519 si = argv_start_index(i);
1520 sj = argv_start_index(j);
1521 ei = argv_end_index(i);
1522 ej = argv_end_index(j);
1523 s0 = argv_start_index(0);
1524 en = argv_end_index(n-1);
1525 s1 = substring_range(str, s0, si);
1526 s2 = substring_range(str, si, ei);
1527 s3 = substring_range(str, ei, sj);
1528 s4 = substring_range(str, sj, ej);
1529 s5 = substring_range(str, ej, en);
1530 return strcat(s1, s4, s3, s2, s5);
1533 string _shufflewords_str;
1534 void _shufflewords_swapfunc(float i, float j, entity pass)
1536 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1538 string shufflewords(string str)
1541 _shufflewords_str = str;
1542 n = tokenizebyseparator(str, " ");
1543 shuffle(n, _shufflewords_swapfunc, world);
1544 str = _shufflewords_str;
1545 _shufflewords_str = string_null;
1549 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1565 // actually, every number solves the equation!
1576 if(a > 0) // put the smaller solution first
1578 v_x = ((-b)-D) / (2*a);
1579 v_y = ((-b)+D) / (2*a);
1583 v_x = (-b+D) / (2*a);
1584 v_y = (-b-D) / (2*a);
1590 // complex solutions!
1603 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1607 // make origin and speed relative
1612 // now solve for ret, ret normalized:
1613 // eorg + t * evel == t * ret * spd
1614 // or, rather, solve for t:
1615 // |eorg + t * evel| == t * spd
1616 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1617 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1618 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1619 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1620 // q = (eorg * eorg) / (evel * evel - spd * spd)
1621 if(!solution_z) // no real solution
1624 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1625 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1626 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1627 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1628 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1629 // spd < |evel| * sin angle(evel, eorg)
1632 else if(solution_x > 0)
1634 // both solutions > 0: take the smaller one
1635 // happens if p < 0 and q > 0
1636 ret = normalize(eorg + solution_x * evel);
1638 else if(solution_y > 0)
1640 // one solution > 0: take the larger one
1641 // happens if q < 0 or q == 0 and p < 0
1642 ret = normalize(eorg + solution_y * evel);
1646 // no solution > 0: reject
1647 // happens if p > 0 and q >= 0
1648 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1649 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1654 // "Enemy is moving away from me at more than spd"
1658 // NOTE: we always got a solution if spd > |evel|
1660 if(newton_style == 2)
1661 ret = normalize(ret * spd + myvel);
1666 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1671 if(newton_style == 2)
1673 // true Newtonian projectiles with automatic aim adjustment
1675 // solve: |outspeed * mydir - myvel| = spd
1676 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1677 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1681 // myvel^2 - (mydir * myvel)^2 > spd^2
1682 // velocity without mydir component > spd
1683 // fire at smallest possible spd that works?
1684 // |(mydir * myvel) * myvel - myvel| = spd
1686 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1690 outspeed = solution_y; // the larger one
1693 //outspeed = 0; // slowest possible shot
1694 outspeed = solution_x; // the real part (that is, the average!)
1695 //dprint("impossible shot, adjusting\n");
1698 outspeed = bound(spd * mi, outspeed, spd * ma);
1699 return mydir * outspeed;
1703 return myvel + spd * mydir;
1706 float compressShotOrigin(vector v)
1710 y = rint(v_y * 4) + 128;
1711 z = rint(v_z * 4) + 128;
1712 if(x > 255 || x < 0)
1714 print("shot origin ", vtos(v), " x out of bounds\n");
1715 x = bound(0, x, 255);
1717 if(y > 255 || y < 0)
1719 print("shot origin ", vtos(v), " y out of bounds\n");
1720 y = bound(0, y, 255);
1722 if(z > 255 || z < 0)
1724 print("shot origin ", vtos(v), " z out of bounds\n");
1725 z = bound(0, z, 255);
1727 return x * 0x10000 + y * 0x100 + z;
1729 vector decompressShotOrigin(float f)
1732 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1733 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1734 v_z = ((f & 0xFF) - 128) / 4;
1738 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1740 float start, end, root, child;
1743 start = floor((n - 2) / 2);
1746 // siftdown(start, count-1);
1748 while(root * 2 + 1 <= n-1)
1750 child = root * 2 + 1;
1752 if(cmp(child, child+1, pass) < 0)
1754 if(cmp(root, child, pass) < 0)
1756 swap(root, child, pass);
1772 // siftdown(0, end);
1774 while(root * 2 + 1 <= end)
1776 child = root * 2 + 1;
1777 if(child < end && cmp(child, child+1, pass) < 0)
1779 if(cmp(root, child, pass) < 0)
1781 swap(root, child, pass);
1791 void RandomSelection_Init()
1793 RandomSelection_totalweight = 0;
1794 RandomSelection_chosen_ent = world;
1795 RandomSelection_chosen_float = 0;
1796 RandomSelection_chosen_string = string_null;
1797 RandomSelection_best_priority = -1;
1799 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1801 if(priority > RandomSelection_best_priority)
1803 RandomSelection_best_priority = priority;
1804 RandomSelection_chosen_ent = e;
1805 RandomSelection_chosen_float = f;
1806 RandomSelection_chosen_string = s;
1807 RandomSelection_totalweight = weight;
1809 else if(priority == RandomSelection_best_priority)
1811 RandomSelection_totalweight += weight;
1812 if(random() * RandomSelection_totalweight <= weight)
1814 RandomSelection_chosen_ent = e;
1815 RandomSelection_chosen_float = f;
1816 RandomSelection_chosen_string = s;
1822 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1824 // NOTE: we'll always choose the SMALLER value...
1825 float healthdamage, armordamage, armorideal;
1826 if (deathtype == DEATH_DROWN) // Why should armor help here...
1829 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1830 armordamage = a + (h - 1); // damage we can take if we could use more armor
1831 armorideal = healthdamage * armorblock;
1833 if(armordamage < healthdamage)
1846 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1849 if (deathtype == DEATH_DROWN) // Why should armor help here...
1851 v_y = bound(0, damage * armorblock, a); // save
1852 v_x = bound(0, damage - v_y, damage); // take
1858 string getcurrentmod()
1862 m = cvar_string("fs_gamedir");
1863 n = tokenize_console(m);
1875 v = ReadShort() * 256; // note: this is signed
1876 v += ReadByte(); // note: this is unsigned
1879 vector ReadInt48_t()
1882 v_x = ReadInt24_t();
1883 v_y = ReadInt24_t();
1887 vector ReadInt72_t()
1890 v_x = ReadInt24_t();
1891 v_y = ReadInt24_t();
1892 v_z = ReadInt24_t();
1896 void WriteInt24_t(float dst, float val)
1899 WriteShort(dst, (v = floor(val / 256)));
1900 WriteByte(dst, val - v * 256); // 0..255
1902 void WriteInt48_t(float dst, vector val)
1904 WriteInt24_t(dst, val_x);
1905 WriteInt24_t(dst, val_y);
1907 void WriteInt72_t(float dst, vector val)
1909 WriteInt24_t(dst, val_x);
1910 WriteInt24_t(dst, val_y);
1911 WriteInt24_t(dst, val_z);
1916 float float2range11(float f)
1918 // continuous function mapping all reals into -1..1
1919 return f / (fabs(f) + 1);
1922 float float2range01(float f)
1924 // continuous function mapping all reals into 0..1
1925 return 0.5 + 0.5 * float2range11(f);
1928 // from the GNU Scientific Library
1929 float gsl_ran_gaussian_lastvalue;
1930 float gsl_ran_gaussian_lastvalue_set;
1931 float gsl_ran_gaussian(float sigma)
1934 if(gsl_ran_gaussian_lastvalue_set)
1936 gsl_ran_gaussian_lastvalue_set = 0;
1937 return sigma * gsl_ran_gaussian_lastvalue;
1941 a = random() * 2 * M_PI;
1942 b = sqrt(-2 * log(random()));
1943 gsl_ran_gaussian_lastvalue = cos(a) * b;
1944 gsl_ran_gaussian_lastvalue_set = 1;
1945 return sigma * sin(a) * b;
1949 string car(string s)
1952 o = strstrofs(s, " ", 0);
1955 return substring(s, 0, o);
1957 string cdr(string s)
1960 o = strstrofs(s, " ", 0);
1963 return substring(s, o + 1, strlen(s) - (o + 1));
1965 float matchacl(string acl, string str)
1972 t = car(acl); acl = cdr(acl);
1975 if(substring(t, 0, 1) == "-")
1978 t = substring(t, 1, strlen(t) - 1);
1980 else if(substring(t, 0, 1) == "+")
1981 t = substring(t, 1, strlen(t) - 1);
1983 if(substring(t, -1, 1) == "*")
1985 t = substring(t, 0, strlen(t) - 1);
1986 s = substring(str, 0, strlen(t));
1998 float startsWith(string haystack, string needle)
2000 return substring(haystack, 0, strlen(needle)) == needle;
2002 float startsWithNocase(string haystack, string needle)
2004 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2007 string get_model_datafilename(string m, float sk, string fil)
2012 m = "models/player/*_";
2014 m = strcat(m, ftos(sk));
2017 return strcat(m, ".", fil);
2020 float get_model_parameters(string m, float sk)
2025 get_model_parameters_modelname = string_null;
2026 get_model_parameters_modelskin = -1;
2027 get_model_parameters_name = string_null;
2028 get_model_parameters_species = -1;
2029 get_model_parameters_sex = string_null;
2030 get_model_parameters_weight = -1;
2031 get_model_parameters_age = -1;
2032 get_model_parameters_desc = string_null;
2033 get_model_parameters_bone_upperbody = string_null;
2034 get_model_parameters_bone_weapon = string_null;
2035 for(i = 0; i < MAX_AIM_BONES; ++i)
2037 get_model_parameters_bone_aim[i] = string_null;
2038 get_model_parameters_bone_aimweight[i] = 0;
2040 get_model_parameters_fixbone = 0;
2045 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2046 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2050 if(substring(m, -4, -1) != ".txt")
2052 if(substring(m, -6, 1) != "_")
2054 sk = stof(substring(m, -5, 1));
2055 m = substring(m, 0, -7);
2058 fn = get_model_datafilename(m, sk, "txt");
2059 fh = fopen(fn, FILE_READ);
2063 fn = get_model_datafilename(m, sk, "txt");
2064 fh = fopen(fn, FILE_READ);
2069 get_model_parameters_modelname = m;
2070 get_model_parameters_modelskin = sk;
2071 while((s = fgets(fh)))
2074 break; // next lines will be description
2078 get_model_parameters_name = s;
2082 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2083 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2084 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2085 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2086 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2087 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2088 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2091 get_model_parameters_sex = s;
2093 get_model_parameters_weight = stof(s);
2095 get_model_parameters_age = stof(s);
2096 if(c == "description")
2097 get_model_parameters_description = s;
2098 if(c == "bone_upperbody")
2099 get_model_parameters_bone_upperbody = s;
2100 if(c == "bone_weapon")
2101 get_model_parameters_bone_weapon = s;
2102 for(i = 0; i < MAX_AIM_BONES; ++i)
2103 if(c == strcat("bone_aim", ftos(i)))
2105 get_model_parameters_bone_aimweight[i] = stof(car(s));
2106 get_model_parameters_bone_aim[i] = cdr(s);
2109 get_model_parameters_fixbone = stof(s);
2112 while((s = fgets(fh)))
2114 if(get_model_parameters_desc)
2115 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2117 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2125 vector vec2(vector v)
2132 vector NearestPointOnBox(entity box, vector org)
2134 vector m1, m2, nearest;
2136 m1 = box.mins + box.origin;
2137 m2 = box.maxs + box.origin;
2139 nearest_x = bound(m1_x, org_x, m2_x);
2140 nearest_y = bound(m1_y, org_y, m2_y);
2141 nearest_z = bound(m1_z, org_z, m2_z);
2147 float vercmp_recursive(string v1, string v2)
2153 dot1 = strstrofs(v1, ".", 0);
2154 dot2 = strstrofs(v2, ".", 0);
2158 s1 = substring(v1, 0, dot1);
2162 s2 = substring(v2, 0, dot2);
2164 r = stof(s1) - stof(s2);
2168 r = strcasecmp(s1, s2);
2181 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2184 float vercmp(string v1, string v2)
2186 if(strcasecmp(v1, v2) == 0) // early out check
2195 return vercmp_recursive(v1, v2);
2198 float u8_strsize(string s)
2218 // translation helpers
2219 string language_filename(string s)
2224 if(fn == "" || fn == "dump")
2226 fn = strcat(s, ".", fn);
2227 if((fh = fopen(fn, FILE_READ)) >= 0)
2234 string CTX(string s)
2236 float p = strstrofs(s, "^", 0);
2239 return substring(s, p+1, -1);
2242 // x-encoding (encoding as zero length invisible string)
2243 const string XENCODE_2 = "xX";
2244 const string XENCODE_22 = "0123456789abcdefABCDEF";
2245 string xencode(float f)
2248 d = mod(f, 22); f = floor(f / 22);
2249 c = mod(f, 22); f = floor(f / 22);
2250 b = mod(f, 22); f = floor(f / 22);
2251 a = mod(f, 2); // f = floor(f / 2);
2254 substring(XENCODE_2, a, 1),
2255 substring(XENCODE_22, b, 1),
2256 substring(XENCODE_22, c, 1),
2257 substring(XENCODE_22, d, 1)
2260 float xdecode(string s)
2263 if(substring(s, 0, 1) != "^")
2267 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2268 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2269 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2270 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2271 if(a < 0 || b < 0 || c < 0 || d < 0)
2273 return ((a * 22 + b) * 22 + c) * 22 + d;
2276 float lowestbit(float f)
2287 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2289 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2292 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2295 // escape the string to make it safe for consoles
2296 string MakeConsoleSafe(string input)
2298 input = strreplace("\n", "", input);
2299 input = strreplace("\\", "\\\\", input);
2300 input = strreplace("$", "$$", input);
2301 input = strreplace("\"", "\\\"", input);
2306 // get true/false value of a string with multiple different inputs
2307 float InterpretBoolean(string input)
2309 switch(strtolower(input))
2321 default: return stof(input);
2327 entity ReadCSQCEntity()
2333 return findfloat(world, entnum, f);
2337 float shutdown_running;
2342 void CSQC_Shutdown()
2348 if(shutdown_running)
2350 print("Recursive shutdown detected! Only restoring cvars...\n");
2354 shutdown_running = 1;
2357 cvar_settemp_restore(); // this must be done LAST, but in any case
2360 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2361 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2362 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2363 // this will use the value:
2365 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2366 // accuracy at x is 1/derivative, i.e.
2367 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2369 void WriteApproxPastTime(float dst, float t)
2371 float dt = time - t;
2373 // warning: this is approximate; do not resend when you don't have to!
2374 // be careful with sendflags here!
2375 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2378 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2381 dt = rint(bound(0, dt, 255));
2387 float ReadApproxPastTime()
2389 float dt = ReadByte();
2391 // map from range...PPROXPASTTIME_MAX / 256
2392 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2394 return servertime - dt;
2399 .float skeleton_bones_index;
2400 void Skeleton_SetBones(entity e)
2402 // set skeleton_bones to the total number of bones on the model
2403 if(e.skeleton_bones_index == e.modelindex)
2404 return; // same model, nothing to update
2407 skelindex = skel_create(e.modelindex);
2408 e.skeleton_bones = skel_get_numbones(skelindex);
2409 skel_delete(skelindex);
2410 e.skeleton_bones_index = e.modelindex;
2414 string to_execute_next_frame;
2415 void execute_next_frame()
2417 if(to_execute_next_frame)
2419 localcmd("\n", to_execute_next_frame, "\n");
2420 strunzone(to_execute_next_frame);
2421 to_execute_next_frame = string_null;
2424 void queue_to_execute_next_frame(string s)
2426 if(to_execute_next_frame)
2428 s = strcat(s, "\n", to_execute_next_frame);
2429 strunzone(to_execute_next_frame);
2431 to_execute_next_frame = strzone(s);
2434 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2437 ((( startspeedfactor + endspeedfactor - 2
2438 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2439 ) * x + startspeedfactor
2443 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2445 if(startspeedfactor < 0 || endspeedfactor < 0)
2449 // if this is the case, the possible zeros of the first derivative are outside
2451 We can calculate this condition as condition
2456 // better, see below:
2457 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2460 // if this is the case, the first derivative has no zeros at all
2461 float se = startspeedfactor + endspeedfactor;
2462 float s_e = startspeedfactor - endspeedfactor;
2463 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2466 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2467 // we also get s_e <= 6 - se
2468 // 3 * (se - 4)^2 + (6 - se)^2
2469 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2470 // Therefore, above "better" check works!
2474 // known good cases:
2482 // (3.5, [0.2..2.3])
2487 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2489 s + e - 2 == 0: no inflection
2492 0 < inflection < 1 if:
2493 0 < 2s + e - 3 < 3s + 3e - 6
2494 2s + e > 3 and 2e + s > 3
2497 0 < inflection < 1 if:
2498 0 > 2s + e - 3 > 3s + 3e - 6
2499 2s + e < 3 and 2e + s < 3
2501 Therefore: there is an inflection point iff:
2502 e outside (3 - s)/2 .. 3 - s*2
2504 in other words, if (s,e) in triangle (1,1)(0,3)(0,1.5) or in triangle (1,1)(3,0)(1.5,0)
2508 .float FindConnectedComponent_processing;
2509 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2511 entity queue_start, queue_end;
2513 // we build a queue of to-be-processed entities.
2514 // queue_start is the next entity to be checked for neighbors
2515 // queue_end is the last entity added
2517 if(e.FindConnectedComponent_processing)
2518 error("recursion or broken cleanup");
2520 // start with a 1-element queue
2521 queue_start = queue_end = e;
2522 queue_end.fld = world;
2523 queue_end.FindConnectedComponent_processing = 1;
2525 // for each queued item:
2526 for(; queue_start; queue_start = queue_start.fld)
2528 // find all neighbors of queue_start
2530 for(t = world; (t = nxt(t, queue_start, pass)); )
2532 if(t.FindConnectedComponent_processing)
2534 if(iscon(t, queue_start, pass))
2536 // it is connected? ADD IT. It will look for neighbors soon too.
2539 queue_end.fld = world;
2540 queue_end.FindConnectedComponent_processing = 1;
2546 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2547 queue_start.FindConnectedComponent_processing = 0;
2551 vector combine_to_vector(float x, float y, float z)
2553 vector result; result_x = x; result_y = y; result_z = z;
2557 vector get_corner_position(entity box, float corner)
2561 case 1: return combine_to_vector(box.absmin_x, box.absmin_y, box.absmin_z);
2562 case 2: return combine_to_vector(box.absmax_x, box.absmin_y, box.absmin_z);
2563 case 3: return combine_to_vector(box.absmin_x, box.absmax_y, box.absmin_z);
2564 case 4: return combine_to_vector(box.absmin_x, box.absmin_y, box.absmax_z);
2565 case 5: return combine_to_vector(box.absmax_x, box.absmax_y, box.absmin_z);
2566 case 6: return combine_to_vector(box.absmin_x, box.absmax_y, box.absmax_z);
2567 case 7: return combine_to_vector(box.absmax_x, box.absmin_y, box.absmax_z);
2568 case 8: return combine_to_vector(box.absmax_x, box.absmax_y, box.absmax_z);
2569 default: return '0 0 0';
2574 // todo: this sucks, lets find a better way to do backtraces?
2576 void backtrace(string msg)
2580 dev = autocvar_developer;
2581 war = autocvar_prvm_backtraceforwarnings;
2583 dev = cvar("developer");
2584 war = cvar("prvm_backtraceforwarnings");
2586 cvar_set("developer", "1");
2587 cvar_set("prvm_backtraceforwarnings", "1");
2589 print("--- CUT HERE ---\nWARNING: ");
2592 remove(world); // isn't there any better way to cause a backtrace?
2593 print("\n--- CUT UNTIL HERE ---\n");
2594 cvar_set("developer", ftos(dev));
2595 cvar_set("prvm_backtraceforwarnings", ftos(war));
2599 // color code replace, place inside of sprintf and parse the string
2600 string CCR(string input)
2602 // See the autocvar declarations in util.qh for default values
2604 // foreground/normal colors
2605 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2606 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2607 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2608 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2611 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2612 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2613 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2615 // background colors
2616 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2617 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2621 vector vec3(float x, float y, float z)
2631 vector animfixfps(entity e, vector a, vector b)
2633 // multi-frame anim: keep as-is
2637 dur = frameduration(e.modelindex, a_x);
2641 dur = frameduration(e.modelindex, a_x);
2651 void dedicated_print(string input) // print(), but only print if the server is not local
2653 if(server_is_dedicated) { print(input); }
2658 float Announcer_PickNumber(float type, float num)
2666 case 10: return ANNCE_NUM_GAMESTART_10;
2667 case 9: return ANNCE_NUM_GAMESTART_9;
2668 case 8: return ANNCE_NUM_GAMESTART_8;
2669 case 7: return ANNCE_NUM_GAMESTART_7;
2670 case 6: return ANNCE_NUM_GAMESTART_6;
2671 case 5: return ANNCE_NUM_GAMESTART_5;
2672 case 4: return ANNCE_NUM_GAMESTART_4;
2673 case 3: return ANNCE_NUM_GAMESTART_3;
2674 case 2: return ANNCE_NUM_GAMESTART_2;
2675 case 1: return ANNCE_NUM_GAMESTART_1;
2683 case 10: return ANNCE_NUM_IDLE_10;
2684 case 9: return ANNCE_NUM_IDLE_9;
2685 case 8: return ANNCE_NUM_IDLE_8;
2686 case 7: return ANNCE_NUM_IDLE_7;
2687 case 6: return ANNCE_NUM_IDLE_6;
2688 case 5: return ANNCE_NUM_IDLE_5;
2689 case 4: return ANNCE_NUM_IDLE_4;
2690 case 3: return ANNCE_NUM_IDLE_3;
2691 case 2: return ANNCE_NUM_IDLE_2;
2692 case 1: return ANNCE_NUM_IDLE_1;
2700 case 10: return ANNCE_NUM_KILL_10;
2701 case 9: return ANNCE_NUM_KILL_9;
2702 case 8: return ANNCE_NUM_KILL_8;
2703 case 7: return ANNCE_NUM_KILL_7;
2704 case 6: return ANNCE_NUM_KILL_6;
2705 case 5: return ANNCE_NUM_KILL_5;
2706 case 4: return ANNCE_NUM_KILL_4;
2707 case 3: return ANNCE_NUM_KILL_3;
2708 case 2: return ANNCE_NUM_KILL_2;
2709 case 1: return ANNCE_NUM_KILL_1;
2717 case 10: return ANNCE_NUM_RESPAWN_10;
2718 case 9: return ANNCE_NUM_RESPAWN_9;
2719 case 8: return ANNCE_NUM_RESPAWN_8;
2720 case 7: return ANNCE_NUM_RESPAWN_7;
2721 case 6: return ANNCE_NUM_RESPAWN_6;
2722 case 5: return ANNCE_NUM_RESPAWN_5;
2723 case 4: return ANNCE_NUM_RESPAWN_4;
2724 case 3: return ANNCE_NUM_RESPAWN_3;
2725 case 2: return ANNCE_NUM_RESPAWN_2;
2726 case 1: return ANNCE_NUM_RESPAWN_1;
2730 case CNT_ROUNDSTART:
2734 case 10: return ANNCE_NUM_ROUNDSTART_10;
2735 case 9: return ANNCE_NUM_ROUNDSTART_9;
2736 case 8: return ANNCE_NUM_ROUNDSTART_8;
2737 case 7: return ANNCE_NUM_ROUNDSTART_7;
2738 case 6: return ANNCE_NUM_ROUNDSTART_6;
2739 case 5: return ANNCE_NUM_ROUNDSTART_5;
2740 case 4: return ANNCE_NUM_ROUNDSTART_4;
2741 case 3: return ANNCE_NUM_ROUNDSTART_3;
2742 case 2: return ANNCE_NUM_ROUNDSTART_2;
2743 case 1: return ANNCE_NUM_ROUNDSTART_1;
2751 case 10: return ANNCE_NUM_10;
2752 case 9: return ANNCE_NUM_9;
2753 case 8: return ANNCE_NUM_8;
2754 case 7: return ANNCE_NUM_7;
2755 case 6: return ANNCE_NUM_6;
2756 case 5: return ANNCE_NUM_5;
2757 case 4: return ANNCE_NUM_4;
2758 case 3: return ANNCE_NUM_3;
2759 case 2: return ANNCE_NUM_2;
2760 case 1: return ANNCE_NUM_1;
2765 return NOTIF_ABORT; // abort sending if none of these numbers were right
2770 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
2772 switch(nativecontents)
2777 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2779 return DPCONTENTS_WATER;
2781 return DPCONTENTS_SLIME;
2783 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2785 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2790 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
2792 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2793 return CONTENT_SOLID;
2794 if(supercontents & DPCONTENTS_SKY)
2796 if(supercontents & DPCONTENTS_LAVA)
2797 return CONTENT_LAVA;
2798 if(supercontents & DPCONTENTS_SLIME)
2799 return CONTENT_SLIME;
2800 if(supercontents & DPCONTENTS_WATER)
2801 return CONTENT_WATER;
2802 return CONTENT_EMPTY;
2806 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2809 (c - 2 * b + a) * (t * t) +
2814 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2817 (c - 2 * b + a) * (2 * t) +