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 void check_unacceptable_compiler_bugs()
1708 if(cvar("_allow_unacceptable_compiler_bugs"))
1710 tokenize_console("foo bar");
1711 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1712 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.");
1716 error("The empty string counts as false. We do not want that!");
1719 float compressShotOrigin(vector v)
1723 y = rint(v_y * 4) + 128;
1724 z = rint(v_z * 4) + 128;
1725 if(x > 255 || x < 0)
1727 print("shot origin ", vtos(v), " x out of bounds\n");
1728 x = bound(0, x, 255);
1730 if(y > 255 || y < 0)
1732 print("shot origin ", vtos(v), " y out of bounds\n");
1733 y = bound(0, y, 255);
1735 if(z > 255 || z < 0)
1737 print("shot origin ", vtos(v), " z out of bounds\n");
1738 z = bound(0, z, 255);
1740 return x * 0x10000 + y * 0x100 + z;
1742 vector decompressShotOrigin(float f)
1745 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1746 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1747 v_z = ((f & 0xFF) - 128) / 4;
1751 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1753 float start, end, root, child;
1756 start = floor((n - 2) / 2);
1759 // siftdown(start, count-1);
1761 while(root * 2 + 1 <= n-1)
1763 child = root * 2 + 1;
1765 if(cmp(child, child+1, pass) < 0)
1767 if(cmp(root, child, pass) < 0)
1769 swap(root, child, pass);
1785 // siftdown(0, end);
1787 while(root * 2 + 1 <= end)
1789 child = root * 2 + 1;
1790 if(child < end && cmp(child, child+1, pass) < 0)
1792 if(cmp(root, child, pass) < 0)
1794 swap(root, child, pass);
1804 void RandomSelection_Init()
1806 RandomSelection_totalweight = 0;
1807 RandomSelection_chosen_ent = world;
1808 RandomSelection_chosen_float = 0;
1809 RandomSelection_chosen_string = string_null;
1810 RandomSelection_best_priority = -1;
1812 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1814 if(priority > RandomSelection_best_priority)
1816 RandomSelection_best_priority = priority;
1817 RandomSelection_chosen_ent = e;
1818 RandomSelection_chosen_float = f;
1819 RandomSelection_chosen_string = s;
1820 RandomSelection_totalweight = weight;
1822 else if(priority == RandomSelection_best_priority)
1824 RandomSelection_totalweight += weight;
1825 if(random() * RandomSelection_totalweight <= weight)
1827 RandomSelection_chosen_ent = e;
1828 RandomSelection_chosen_float = f;
1829 RandomSelection_chosen_string = s;
1835 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1837 // NOTE: we'll always choose the SMALLER value...
1838 float healthdamage, armordamage, armorideal;
1839 if (deathtype == DEATH_DROWN) // Why should armor help here...
1842 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1843 armordamage = a + (h - 1); // damage we can take if we could use more armor
1844 armorideal = healthdamage * armorblock;
1846 if(armordamage < healthdamage)
1859 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1862 if (deathtype == DEATH_DROWN) // Why should armor help here...
1864 v_y = bound(0, damage * armorblock, a); // save
1865 v_x = bound(0, damage - v_y, damage); // take
1871 string getcurrentmod()
1875 m = cvar_string("fs_gamedir");
1876 n = tokenize_console(m);
1888 v = ReadShort() * 256; // note: this is signed
1889 v += ReadByte(); // note: this is unsigned
1892 vector ReadInt48_t()
1895 v_x = ReadInt24_t();
1896 v_y = ReadInt24_t();
1900 vector ReadInt72_t()
1903 v_x = ReadInt24_t();
1904 v_y = ReadInt24_t();
1905 v_z = ReadInt24_t();
1909 void WriteInt24_t(float dst, float val)
1912 WriteShort(dst, (v = floor(val / 256)));
1913 WriteByte(dst, val - v * 256); // 0..255
1915 void WriteInt48_t(float dst, vector val)
1917 WriteInt24_t(dst, val_x);
1918 WriteInt24_t(dst, val_y);
1920 void WriteInt72_t(float dst, vector val)
1922 WriteInt24_t(dst, val_x);
1923 WriteInt24_t(dst, val_y);
1924 WriteInt24_t(dst, val_z);
1929 float float2range11(float f)
1931 // continuous function mapping all reals into -1..1
1932 return f / (fabs(f) + 1);
1935 float float2range01(float f)
1937 // continuous function mapping all reals into 0..1
1938 return 0.5 + 0.5 * float2range11(f);
1941 // from the GNU Scientific Library
1942 float gsl_ran_gaussian_lastvalue;
1943 float gsl_ran_gaussian_lastvalue_set;
1944 float gsl_ran_gaussian(float sigma)
1947 if(gsl_ran_gaussian_lastvalue_set)
1949 gsl_ran_gaussian_lastvalue_set = 0;
1950 return sigma * gsl_ran_gaussian_lastvalue;
1954 a = random() * 2 * M_PI;
1955 b = sqrt(-2 * log(random()));
1956 gsl_ran_gaussian_lastvalue = cos(a) * b;
1957 gsl_ran_gaussian_lastvalue_set = 1;
1958 return sigma * sin(a) * b;
1962 string car(string s)
1965 o = strstrofs(s, " ", 0);
1968 return substring(s, 0, o);
1970 string cdr(string s)
1973 o = strstrofs(s, " ", 0);
1976 return substring(s, o + 1, strlen(s) - (o + 1));
1978 float matchacl(string acl, string str)
1985 t = car(acl); acl = cdr(acl);
1988 if(substring(t, 0, 1) == "-")
1991 t = substring(t, 1, strlen(t) - 1);
1993 else if(substring(t, 0, 1) == "+")
1994 t = substring(t, 1, strlen(t) - 1);
1996 if(substring(t, -1, 1) == "*")
1998 t = substring(t, 0, strlen(t) - 1);
1999 s = substring(str, 0, strlen(t));
2011 float startsWith(string haystack, string needle)
2013 return substring(haystack, 0, strlen(needle)) == needle;
2015 float startsWithNocase(string haystack, string needle)
2017 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2020 string get_model_datafilename(string m, float sk, string fil)
2025 m = "models/player/*_";
2027 m = strcat(m, ftos(sk));
2030 return strcat(m, ".", fil);
2033 float get_model_parameters(string m, float sk)
2038 get_model_parameters_modelname = string_null;
2039 get_model_parameters_modelskin = -1;
2040 get_model_parameters_name = string_null;
2041 get_model_parameters_species = -1;
2042 get_model_parameters_sex = string_null;
2043 get_model_parameters_weight = -1;
2044 get_model_parameters_age = -1;
2045 get_model_parameters_desc = string_null;
2046 get_model_parameters_bone_upperbody = string_null;
2047 get_model_parameters_bone_weapon = string_null;
2048 for(i = 0; i < MAX_AIM_BONES; ++i)
2050 get_model_parameters_bone_aim[i] = string_null;
2051 get_model_parameters_bone_aimweight[i] = 0;
2053 get_model_parameters_fixbone = 0;
2058 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2059 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2063 if(substring(m, -4, -1) != ".txt")
2065 if(substring(m, -6, 1) != "_")
2067 sk = stof(substring(m, -5, 1));
2068 m = substring(m, 0, -7);
2071 fn = get_model_datafilename(m, sk, "txt");
2072 fh = fopen(fn, FILE_READ);
2076 fn = get_model_datafilename(m, sk, "txt");
2077 fh = fopen(fn, FILE_READ);
2082 get_model_parameters_modelname = m;
2083 get_model_parameters_modelskin = sk;
2084 while((s = fgets(fh)))
2087 break; // next lines will be description
2091 get_model_parameters_name = s;
2095 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2096 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2097 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2098 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2099 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2100 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2101 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2104 get_model_parameters_sex = s;
2106 get_model_parameters_weight = stof(s);
2108 get_model_parameters_age = stof(s);
2109 if(c == "description")
2110 get_model_parameters_description = s;
2111 if(c == "bone_upperbody")
2112 get_model_parameters_bone_upperbody = s;
2113 if(c == "bone_weapon")
2114 get_model_parameters_bone_weapon = s;
2115 for(i = 0; i < MAX_AIM_BONES; ++i)
2116 if(c == strcat("bone_aim", ftos(i)))
2118 get_model_parameters_bone_aimweight[i] = stof(car(s));
2119 get_model_parameters_bone_aim[i] = cdr(s);
2122 get_model_parameters_fixbone = stof(s);
2125 while((s = fgets(fh)))
2127 if(get_model_parameters_desc)
2128 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2130 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2138 vector vec2(vector v)
2145 vector NearestPointOnBox(entity box, vector org)
2147 vector m1, m2, nearest;
2149 m1 = box.mins + box.origin;
2150 m2 = box.maxs + box.origin;
2152 nearest_x = bound(m1_x, org_x, m2_x);
2153 nearest_y = bound(m1_y, org_y, m2_y);
2154 nearest_z = bound(m1_z, org_z, m2_z);
2160 float vercmp_recursive(string v1, string v2)
2166 dot1 = strstrofs(v1, ".", 0);
2167 dot2 = strstrofs(v2, ".", 0);
2171 s1 = substring(v1, 0, dot1);
2175 s2 = substring(v2, 0, dot2);
2177 r = stof(s1) - stof(s2);
2181 r = strcasecmp(s1, s2);
2194 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2197 float vercmp(string v1, string v2)
2199 if(strcasecmp(v1, v2) == 0) // early out check
2208 return vercmp_recursive(v1, v2);
2211 float u8_strsize(string s)
2231 // translation helpers
2232 string language_filename(string s)
2237 if(fn == "" || fn == "dump")
2239 fn = strcat(s, ".", fn);
2240 if((fh = fopen(fn, FILE_READ)) >= 0)
2247 string CTX(string s)
2249 float p = strstrofs(s, "^", 0);
2252 return substring(s, p+1, -1);
2255 // x-encoding (encoding as zero length invisible string)
2256 const string XENCODE_2 = "xX";
2257 const string XENCODE_22 = "0123456789abcdefABCDEF";
2258 string xencode(float f)
2261 d = mod(f, 22); f = floor(f / 22);
2262 c = mod(f, 22); f = floor(f / 22);
2263 b = mod(f, 22); f = floor(f / 22);
2264 a = mod(f, 2); // f = floor(f / 2);
2267 substring(XENCODE_2, a, 1),
2268 substring(XENCODE_22, b, 1),
2269 substring(XENCODE_22, c, 1),
2270 substring(XENCODE_22, d, 1)
2273 float xdecode(string s)
2276 if(substring(s, 0, 1) != "^")
2280 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2281 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2282 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2283 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2284 if(a < 0 || b < 0 || c < 0 || d < 0)
2286 return ((a * 22 + b) * 22 + c) * 22 + d;
2289 float lowestbit(float f)
2300 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2302 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2305 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2308 // escape the string to make it safe for consoles
2309 string MakeConsoleSafe(string input)
2311 input = strreplace("\n", "", input);
2312 input = strreplace("\\", "\\\\", input);
2313 input = strreplace("$", "$$", input);
2314 input = strreplace("\"", "\\\"", input);
2319 // get true/false value of a string with multiple different inputs
2320 float InterpretBoolean(string input)
2322 switch(strtolower(input))
2334 default: return stof(input);
2340 entity ReadCSQCEntity()
2346 return findfloat(world, entnum, f);
2350 float shutdown_running;
2355 void CSQC_Shutdown()
2361 if(shutdown_running)
2363 print("Recursive shutdown detected! Only restoring cvars...\n");
2367 shutdown_running = 1;
2370 cvar_settemp_restore(); // this must be done LAST, but in any case
2373 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2374 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2375 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2376 // this will use the value:
2378 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2379 // accuracy at x is 1/derivative, i.e.
2380 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2382 void WriteApproxPastTime(float dst, float t)
2384 float dt = time - t;
2386 // warning: this is approximate; do not resend when you don't have to!
2387 // be careful with sendflags here!
2388 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2391 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2394 dt = rint(bound(0, dt, 255));
2400 float ReadApproxPastTime()
2402 float dt = ReadByte();
2404 // map from range...PPROXPASTTIME_MAX / 256
2405 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2407 return servertime - dt;
2412 .float skeleton_bones_index;
2413 void Skeleton_SetBones(entity e)
2415 // set skeleton_bones to the total number of bones on the model
2416 if(e.skeleton_bones_index == e.modelindex)
2417 return; // same model, nothing to update
2420 skelindex = skel_create(e.modelindex);
2421 e.skeleton_bones = skel_get_numbones(skelindex);
2422 skel_delete(skelindex);
2423 e.skeleton_bones_index = e.modelindex;
2427 string to_execute_next_frame;
2428 void execute_next_frame()
2430 if(to_execute_next_frame)
2432 localcmd("\n", to_execute_next_frame, "\n");
2433 strunzone(to_execute_next_frame);
2434 to_execute_next_frame = string_null;
2437 void queue_to_execute_next_frame(string s)
2439 if(to_execute_next_frame)
2441 s = strcat(s, "\n", to_execute_next_frame);
2442 strunzone(to_execute_next_frame);
2444 to_execute_next_frame = strzone(s);
2447 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2450 ((( startspeedfactor + endspeedfactor - 2
2451 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2452 ) * x + startspeedfactor
2456 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2458 if(startspeedfactor < 0 || endspeedfactor < 0)
2462 // if this is the case, the possible zeros of the first derivative are outside
2464 We can calculate this condition as condition
2469 // better, see below:
2470 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2473 // if this is the case, the first derivative has no zeros at all
2474 float se = startspeedfactor + endspeedfactor;
2475 float s_e = startspeedfactor - endspeedfactor;
2476 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2479 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2480 // we also get s_e <= 6 - se
2481 // 3 * (se - 4)^2 + (6 - se)^2
2482 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2483 // Therefore, above "better" check works!
2487 // known good cases:
2495 // (3.5, [0.2..2.3])
2500 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2502 s + e - 2 == 0: no inflection
2505 0 < inflection < 1 if:
2506 0 < 2s + e - 3 < 3s + 3e - 6
2507 2s + e > 3 and 2e + s > 3
2510 0 < inflection < 1 if:
2511 0 > 2s + e - 3 > 3s + 3e - 6
2512 2s + e < 3 and 2e + s < 3
2514 Therefore: there is an inflection point iff:
2515 e outside (3 - s)/2 .. 3 - s*2
2517 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)
2521 .float FindConnectedComponent_processing;
2522 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2524 entity queue_start, queue_end;
2526 // we build a queue of to-be-processed entities.
2527 // queue_start is the next entity to be checked for neighbors
2528 // queue_end is the last entity added
2530 if(e.FindConnectedComponent_processing)
2531 error("recursion or broken cleanup");
2533 // start with a 1-element queue
2534 queue_start = queue_end = e;
2535 queue_end.fld = world;
2536 queue_end.FindConnectedComponent_processing = 1;
2538 // for each queued item:
2539 for(; queue_start; queue_start = queue_start.fld)
2541 // find all neighbors of queue_start
2543 for(t = world; (t = nxt(t, queue_start, pass)); )
2545 if(t.FindConnectedComponent_processing)
2547 if(iscon(t, queue_start, pass))
2549 // it is connected? ADD IT. It will look for neighbors soon too.
2552 queue_end.fld = world;
2553 queue_end.FindConnectedComponent_processing = 1;
2559 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2560 queue_start.FindConnectedComponent_processing = 0;
2563 // todo: this sucks, lets find a better way to do backtraces?
2565 void backtrace(string msg)
2569 dev = autocvar_developer;
2570 war = autocvar_prvm_backtraceforwarnings;
2572 dev = cvar("developer");
2573 war = cvar("prvm_backtraceforwarnings");
2575 cvar_set("developer", "1");
2576 cvar_set("prvm_backtraceforwarnings", "1");
2578 print("--- CUT HERE ---\nWARNING: ");
2581 remove(world); // isn't there any better way to cause a backtrace?
2582 print("\n--- CUT UNTIL HERE ---\n");
2583 cvar_set("developer", ftos(dev));
2584 cvar_set("prvm_backtraceforwarnings", ftos(war));
2588 // color code replace, place inside of sprintf and parse the string
2589 string CCR(string input)
2591 // See the autocvar declarations in util.qh for default values
2593 // foreground/normal colors
2594 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2595 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2596 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2597 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2600 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2601 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2602 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2604 // background colors
2605 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2606 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2610 vector vec3(float x, float y, float z)
2620 vector animfixfps(entity e, vector a, vector b)
2622 // multi-frame anim: keep as-is
2626 dur = frameduration(e.modelindex, a_x);
2630 dur = frameduration(e.modelindex, a_x);
2640 void dedicated_print(string input) // print(), but only print if the server is not local
2642 if(server_is_dedicated) { print(input); }
2647 float Announcer_PickNumber(float type, float num)
2655 case 10: return ANNCE_NUM_GAMESTART_10;
2656 case 9: return ANNCE_NUM_GAMESTART_9;
2657 case 8: return ANNCE_NUM_GAMESTART_8;
2658 case 7: return ANNCE_NUM_GAMESTART_7;
2659 case 6: return ANNCE_NUM_GAMESTART_6;
2660 case 5: return ANNCE_NUM_GAMESTART_5;
2661 case 4: return ANNCE_NUM_GAMESTART_4;
2662 case 3: return ANNCE_NUM_GAMESTART_3;
2663 case 2: return ANNCE_NUM_GAMESTART_2;
2664 case 1: return ANNCE_NUM_GAMESTART_1;
2672 case 10: return ANNCE_NUM_IDLE_10;
2673 case 9: return ANNCE_NUM_IDLE_9;
2674 case 8: return ANNCE_NUM_IDLE_8;
2675 case 7: return ANNCE_NUM_IDLE_7;
2676 case 6: return ANNCE_NUM_IDLE_6;
2677 case 5: return ANNCE_NUM_IDLE_5;
2678 case 4: return ANNCE_NUM_IDLE_4;
2679 case 3: return ANNCE_NUM_IDLE_3;
2680 case 2: return ANNCE_NUM_IDLE_2;
2681 case 1: return ANNCE_NUM_IDLE_1;
2689 case 10: return ANNCE_NUM_KILL_10;
2690 case 9: return ANNCE_NUM_KILL_9;
2691 case 8: return ANNCE_NUM_KILL_8;
2692 case 7: return ANNCE_NUM_KILL_7;
2693 case 6: return ANNCE_NUM_KILL_6;
2694 case 5: return ANNCE_NUM_KILL_5;
2695 case 4: return ANNCE_NUM_KILL_4;
2696 case 3: return ANNCE_NUM_KILL_3;
2697 case 2: return ANNCE_NUM_KILL_2;
2698 case 1: return ANNCE_NUM_KILL_1;
2706 case 10: return ANNCE_NUM_RESPAWN_10;
2707 case 9: return ANNCE_NUM_RESPAWN_9;
2708 case 8: return ANNCE_NUM_RESPAWN_8;
2709 case 7: return ANNCE_NUM_RESPAWN_7;
2710 case 6: return ANNCE_NUM_RESPAWN_6;
2711 case 5: return ANNCE_NUM_RESPAWN_5;
2712 case 4: return ANNCE_NUM_RESPAWN_4;
2713 case 3: return ANNCE_NUM_RESPAWN_3;
2714 case 2: return ANNCE_NUM_RESPAWN_2;
2715 case 1: return ANNCE_NUM_RESPAWN_1;
2719 case CNT_ROUNDSTART:
2723 case 10: return ANNCE_NUM_ROUNDSTART_10;
2724 case 9: return ANNCE_NUM_ROUNDSTART_9;
2725 case 8: return ANNCE_NUM_ROUNDSTART_8;
2726 case 7: return ANNCE_NUM_ROUNDSTART_7;
2727 case 6: return ANNCE_NUM_ROUNDSTART_6;
2728 case 5: return ANNCE_NUM_ROUNDSTART_5;
2729 case 4: return ANNCE_NUM_ROUNDSTART_4;
2730 case 3: return ANNCE_NUM_ROUNDSTART_3;
2731 case 2: return ANNCE_NUM_ROUNDSTART_2;
2732 case 1: return ANNCE_NUM_ROUNDSTART_1;
2740 case 10: return ANNCE_NUM_10;
2741 case 9: return ANNCE_NUM_9;
2742 case 8: return ANNCE_NUM_8;
2743 case 7: return ANNCE_NUM_7;
2744 case 6: return ANNCE_NUM_6;
2745 case 5: return ANNCE_NUM_5;
2746 case 4: return ANNCE_NUM_4;
2747 case 3: return ANNCE_NUM_3;
2748 case 2: return ANNCE_NUM_2;
2749 case 1: return ANNCE_NUM_1;
2754 return NOTIF_ABORT; // abort sending if none of these numbers were right
2759 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
2761 switch(nativecontents)
2766 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2768 return DPCONTENTS_WATER;
2770 return DPCONTENTS_SLIME;
2772 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2774 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2779 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
2781 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2782 return CONTENT_SOLID;
2783 if(supercontents & DPCONTENTS_SKY)
2785 if(supercontents & DPCONTENTS_LAVA)
2786 return CONTENT_LAVA;
2787 if(supercontents & DPCONTENTS_SLIME)
2788 return CONTENT_SLIME;
2789 if(supercontents & DPCONTENTS_WATER)
2790 return CONTENT_WATER;
2791 return CONTENT_EMPTY;
2795 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2798 (c - 2 * b + a) * (t * t) +
2803 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2806 (c - 2 * b + a) * (2 * t) +