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 format_time(float seconds)
413 float days, hours, minutes;
414 seconds = floor(seconds + 0.5);
415 days = floor(seconds / 864000);
416 seconds -= days * 864000;
417 hours = floor(seconds / 36000);
418 seconds -= hours * 36000;
419 minutes = floor(seconds / 600);
420 seconds -= minutes * 600;
422 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
424 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
427 string mmsss(float tenths)
431 tenths = floor(tenths + 0.5);
432 minutes = floor(tenths / 600);
433 tenths -= minutes * 600;
434 s = ftos(1000 + tenths);
435 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
438 string mmssss(float hundredths)
442 hundredths = floor(hundredths + 0.5);
443 minutes = floor(hundredths / 6000);
444 hundredths -= minutes * 6000;
445 s = ftos(10000 + hundredths);
446 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
449 string ScoreString(float pFlags, float pValue)
454 pValue = floor(pValue + 0.5); // round
456 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
458 else if(pFlags & SFL_RANK)
460 valstr = ftos(pValue);
462 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
463 valstr = strcat(valstr, "th");
464 else if(substring(valstr, l - 1, 1) == "1")
465 valstr = strcat(valstr, "st");
466 else if(substring(valstr, l - 1, 1) == "2")
467 valstr = strcat(valstr, "nd");
468 else if(substring(valstr, l - 1, 1) == "3")
469 valstr = strcat(valstr, "rd");
471 valstr = strcat(valstr, "th");
473 else if(pFlags & SFL_TIME)
474 valstr = TIME_ENCODED_TOSTRING(pValue);
476 valstr = ftos(pValue);
481 float dotproduct(vector a, vector b)
483 return a_x * b_x + a_y * b_y + a_z * b_z;
486 vector cross(vector a, vector b)
489 '1 0 0' * (a_y * b_z - a_z * b_y)
490 + '0 1 0' * (a_z * b_x - a_x * b_z)
491 + '0 0 1' * (a_x * b_y - a_y * b_x);
494 // compressed vector format:
495 // like MD3, just even shorter
496 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
497 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
498 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
499 // length = 2^(length_encoded/8) / 8
500 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
501 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
502 // the special value 0 indicates the zero vector
504 float lengthLogTable[128];
506 float invertLengthLog(float x)
508 float l, r, m, lerr, rerr;
510 if(x >= lengthLogTable[127])
512 if(x <= lengthLogTable[0])
520 m = floor((l + r) / 2);
521 if(lengthLogTable[m] < x)
527 // now: r is >=, l is <
528 lerr = (x - lengthLogTable[l]);
529 rerr = (lengthLogTable[r] - x);
535 vector decompressShortVector(float data)
541 p = (data & 0xF000) / 0x1000;
542 y = (data & 0x0F80) / 0x80;
543 len = (data & 0x007F);
545 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
558 y = .19634954084936207740 * y;
559 p = .19634954084936207740 * p - 1.57079632679489661922;
560 out_x = cos(y) * cos(p);
561 out_y = sin(y) * cos(p);
565 //print("decompressed: ", vtos(out), "\n");
567 return out * lengthLogTable[len];
570 float compressShortVector(vector vec)
576 //print("compress: ", vtos(vec), "\n");
577 ang = vectoangles(vec);
581 if(ang_x < -90 && ang_x > +90)
582 error("BOGUS vectoangles");
583 //print("angles: ", vtos(ang), "\n");
585 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
594 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
595 len = invertLengthLog(vlen(vec));
597 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
599 return (p * 0x1000) + (y * 0x80) + len;
602 void compressShortVector_init()
607 for(i = 0; i < 128; ++i)
609 lengthLogTable[i] = l;
613 if(cvar("developer"))
615 print("Verifying vector compression table...\n");
616 for(i = 0x0F00; i < 0xFFFF; ++i)
617 if(i != compressShortVector(decompressShortVector(i)))
619 print("BROKEN vector compression: ", ftos(i));
620 print(" -> ", vtos(decompressShortVector(i)));
621 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
630 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
632 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
633 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
634 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
635 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
636 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
637 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
638 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
639 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
640 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
641 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
642 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
643 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
648 string fixPriorityList(string order, float from, float to, float subtract, float complete)
653 n = tokenize_console(order);
655 for(i = 0; i < n; ++i)
660 if(w >= from && w <= to)
661 neworder = strcat(neworder, ftos(w), " ");
665 if(w >= from && w <= to)
666 neworder = strcat(neworder, ftos(w), " ");
673 n = tokenize_console(neworder);
674 for(w = to; w >= from; --w)
676 for(i = 0; i < n; ++i)
677 if(stof(argv(i)) == w)
679 if(i == n) // not found
680 neworder = strcat(neworder, ftos(w), " ");
684 return substring(neworder, 0, strlen(neworder) - 1);
687 string mapPriorityList(string order, string(string) mapfunc)
692 n = tokenize_console(order);
694 for(i = 0; i < n; ++i)
695 neworder = strcat(neworder, mapfunc(argv(i)), " ");
697 return substring(neworder, 0, strlen(neworder) - 1);
700 string swapInPriorityList(string order, float i, float j)
705 n = tokenize_console(order);
707 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
710 for(w = 0; w < n; ++w)
713 s = strcat(s, argv(j), " ");
715 s = strcat(s, argv(i), " ");
717 s = strcat(s, argv(w), " ");
719 return substring(s, 0, strlen(s) - 1);
725 float cvar_value_issafe(string s)
727 if(strstrofs(s, "\"", 0) >= 0)
729 if(strstrofs(s, "\\", 0) >= 0)
731 if(strstrofs(s, ";", 0) >= 0)
733 if(strstrofs(s, "$", 0) >= 0)
735 if(strstrofs(s, "\r", 0) >= 0)
737 if(strstrofs(s, "\n", 0) >= 0)
743 void get_mi_min_max(float mode)
748 strunzone(mi_shortname);
749 mi_shortname = mapname;
750 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
751 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
752 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
753 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
754 mi_shortname = strzone(mi_shortname);
766 MapInfo_Get_ByName(mi_shortname, 0, 0);
767 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
769 mi_min = MapInfo_Map_mins;
770 mi_max = MapInfo_Map_maxs;
778 tracebox('1 0 0' * mi_x,
779 '0 1 0' * mi_y + '0 0 1' * mi_z,
780 '0 1 0' * ma_y + '0 0 1' * ma_z,
784 if(!trace_startsolid)
785 mi_min_x = trace_endpos_x;
787 tracebox('0 1 0' * mi_y,
788 '1 0 0' * mi_x + '0 0 1' * mi_z,
789 '1 0 0' * ma_x + '0 0 1' * ma_z,
793 if(!trace_startsolid)
794 mi_min_y = trace_endpos_y;
796 tracebox('0 0 1' * mi_z,
797 '1 0 0' * mi_x + '0 1 0' * mi_y,
798 '1 0 0' * ma_x + '0 1 0' * ma_y,
802 if(!trace_startsolid)
803 mi_min_z = trace_endpos_z;
805 tracebox('1 0 0' * ma_x,
806 '0 1 0' * mi_y + '0 0 1' * mi_z,
807 '0 1 0' * ma_y + '0 0 1' * ma_z,
811 if(!trace_startsolid)
812 mi_max_x = trace_endpos_x;
814 tracebox('0 1 0' * ma_y,
815 '1 0 0' * mi_x + '0 0 1' * mi_z,
816 '1 0 0' * ma_x + '0 0 1' * ma_z,
820 if(!trace_startsolid)
821 mi_max_y = trace_endpos_y;
823 tracebox('0 0 1' * ma_z,
824 '1 0 0' * mi_x + '0 1 0' * mi_y,
825 '1 0 0' * ma_x + '0 1 0' * ma_y,
829 if(!trace_startsolid)
830 mi_max_z = trace_endpos_z;
835 void get_mi_min_max_texcoords(float mode)
839 get_mi_min_max(mode);
844 // extend mi_picmax to get a square aspect ratio
845 // center the map in that area
846 extend = mi_picmax - mi_picmin;
847 if(extend_y > extend_x)
849 mi_picmin_x -= (extend_y - extend_x) * 0.5;
850 mi_picmax_x += (extend_y - extend_x) * 0.5;
854 mi_picmin_y -= (extend_x - extend_y) * 0.5;
855 mi_picmax_y += (extend_x - extend_y) * 0.5;
858 // add another some percent
859 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
863 // calculate the texcoords
864 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
865 // first the two corners of the origin
866 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
867 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
868 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
869 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
870 // then the other corners
871 mi_pictexcoord1_x = mi_pictexcoord0_x;
872 mi_pictexcoord1_y = mi_pictexcoord2_y;
873 mi_pictexcoord3_x = mi_pictexcoord2_x;
874 mi_pictexcoord3_y = mi_pictexcoord0_y;
878 float cvar_settemp(string tmp_cvar, string tmp_value)
880 float created_saved_value;
883 created_saved_value = 0;
885 if (!(tmp_cvar || tmp_value))
887 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
891 if(!cvar_type(tmp_cvar))
893 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
897 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
898 if(e.netname == tmp_cvar)
899 created_saved_value = -1; // skip creation
901 if(created_saved_value != -1)
903 // creating a new entity to keep track of this cvar
905 e.classname = "saved_cvar_value";
906 e.netname = strzone(tmp_cvar);
907 e.message = strzone(cvar_string(tmp_cvar));
908 created_saved_value = 1;
911 // update the cvar to the value given
912 cvar_set(tmp_cvar, tmp_value);
914 return created_saved_value;
917 float cvar_settemp_restore()
921 while((e = find(e, classname, "saved_cvar_value")))
923 if(cvar_type(e.netname))
925 cvar_set(e.netname, e.message);
930 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
936 float almost_equals(float a, float b)
939 eps = (max(a, -a) + max(b, -b)) * 0.001;
940 if(a - b < eps && b - a < eps)
945 float almost_in_bounds(float a, float b, float c)
948 eps = (max(a, -a) + max(c, -c)) * 0.001;
951 return b == median(a - eps, b, c + eps);
954 float power2of(float e)
958 float log2of(float x)
960 // NOTE: generated code
1033 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1037 else if(ma == rgb_x)
1040 return (rgb_y - rgb_z) / (ma - mi);
1042 return (rgb_y - rgb_z) / (ma - mi) + 6;
1044 else if(ma == rgb_y)
1045 return (rgb_z - rgb_x) / (ma - mi) + 2;
1046 else // if(ma == rgb_z)
1047 return (rgb_x - rgb_y) / (ma - mi) + 4;
1050 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1054 hue -= 6 * floor(hue / 6);
1056 //else if(ma == rgb_x)
1057 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1061 rgb_y = hue * (ma - mi) + mi;
1064 //else if(ma == rgb_y)
1065 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1068 rgb_x = (2 - hue) * (ma - mi) + mi;
1076 rgb_z = (hue - 2) * (ma - mi) + mi;
1078 //else // if(ma == rgb_z)
1079 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1083 rgb_y = (4 - hue) * (ma - mi) + mi;
1088 rgb_x = (hue - 4) * (ma - mi) + mi;
1092 //else if(ma == rgb_x)
1093 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1094 else // if(hue <= 6)
1098 rgb_z = (6 - hue) * (ma - mi) + mi;
1104 vector rgb_to_hsv(vector rgb)
1109 mi = min(rgb_x, rgb_y, rgb_z);
1110 ma = max(rgb_x, rgb_y, rgb_z);
1112 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1123 vector hsv_to_rgb(vector hsv)
1125 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1128 vector rgb_to_hsl(vector rgb)
1133 mi = min(rgb_x, rgb_y, rgb_z);
1134 ma = max(rgb_x, rgb_y, rgb_z);
1136 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1138 hsl_z = 0.5 * (mi + ma);
1141 else if(hsl_z <= 0.5)
1142 hsl_y = (ma - mi) / (2*hsl_z);
1143 else // if(hsl_z > 0.5)
1144 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1149 vector hsl_to_rgb(vector hsl)
1151 float mi, ma, maminusmi;
1154 maminusmi = hsl_y * 2 * hsl_z;
1156 maminusmi = hsl_y * (2 - 2 * hsl_z);
1158 // hsl_z = 0.5 * mi + 0.5 * ma
1159 // maminusmi = - mi + ma
1160 mi = hsl_z - 0.5 * maminusmi;
1161 ma = hsl_z + 0.5 * maminusmi;
1163 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1166 string rgb_to_hexcolor(vector rgb)
1171 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1172 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1173 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1177 // requires that m2>m1 in all coordinates, and that m4>m3
1178 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;}
1180 // requires the same, but is a stronger condition
1181 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;}
1186 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1189 // The following function is SLOW.
1190 // For your safety and for the protection of those around you...
1191 // DO NOT CALL THIS AT HOME.
1192 // No really, don't.
1193 if(w(theText, theSize) <= maxWidth)
1194 return strlen(theText); // yeah!
1196 // binary search for right place to cut string
1198 float left, right, middle; // this always works
1200 right = strlen(theText); // this always fails
1203 middle = floor((left + right) / 2);
1204 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1209 while(left < right - 1);
1211 if(w("^7", theSize) == 0) // detect color codes support in the width function
1213 // NOTE: when color codes are involved, this binary search is,
1214 // mathematically, BROKEN. However, it is obviously guaranteed to
1215 // terminate, as the range still halves each time - but nevertheless, it is
1216 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1217 // range, and "right" is outside).
1219 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1220 // and decrease left on the basis of the chars detected of the truncated tag
1221 // Even if the ^xrgb tag is not complete/correct, left is decreased
1222 // (sometimes too much but with a correct result)
1223 // it fixes also ^[0-9]
1224 while(left >= 1 && substring(theText, left-1, 1) == "^")
1227 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1229 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1231 ch = str2chr(theText, left-1);
1232 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1235 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1237 ch = str2chr(theText, left-2);
1238 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1240 ch = str2chr(theText, left-1);
1241 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1250 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1253 // The following function is SLOW.
1254 // For your safety and for the protection of those around you...
1255 // DO NOT CALL THIS AT HOME.
1256 // No really, don't.
1257 if(w(theText) <= maxWidth)
1258 return strlen(theText); // yeah!
1260 // binary search for right place to cut string
1262 float left, right, middle; // this always works
1264 right = strlen(theText); // this always fails
1267 middle = floor((left + right) / 2);
1268 if(w(substring(theText, 0, middle)) <= maxWidth)
1273 while(left < right - 1);
1275 if(w("^7") == 0) // detect color codes support in the width function
1277 // NOTE: when color codes are involved, this binary search is,
1278 // mathematically, BROKEN. However, it is obviously guaranteed to
1279 // terminate, as the range still halves each time - but nevertheless, it is
1280 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1281 // range, and "right" is outside).
1283 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1284 // and decrease left on the basis of the chars detected of the truncated tag
1285 // Even if the ^xrgb tag is not complete/correct, left is decreased
1286 // (sometimes too much but with a correct result)
1287 // it fixes also ^[0-9]
1288 while(left >= 1 && substring(theText, left-1, 1) == "^")
1291 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1293 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1295 ch = str2chr(theText, left-1);
1296 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1299 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1301 ch = str2chr(theText, left-2);
1302 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1304 ch = str2chr(theText, left-1);
1305 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1314 string find_last_color_code(string s)
1316 float start, len, i, carets;
1317 start = strstrofs(s, "^", 0);
1318 if (start == -1) // no caret found
1321 for(i = len; i >= start; --i)
1323 if(substring(s, i, 1) != "^")
1327 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1330 // check if carets aren't all escaped
1334 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1335 return substring(s, i, 2);
1338 if(substring(s, i+1, 1) == "x")
1339 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1340 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1341 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1342 return substring(s, i, 5);
1344 i -= carets; // this also skips one char before the carets
1350 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1356 s = getWrappedLine_remaining;
1360 getWrappedLine_remaining = string_null;
1361 return s; // the line has no size ANYWAY, nothing would be displayed.
1364 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1365 if(cantake > 0 && cantake < strlen(s))
1368 while(take > 0 && substring(s, take, 1) != " ")
1372 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1373 if(getWrappedLine_remaining == "")
1374 getWrappedLine_remaining = string_null;
1375 else if (tw("^7", theFontSize) == 0)
1376 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1377 return substring(s, 0, cantake);
1381 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1382 if(getWrappedLine_remaining == "")
1383 getWrappedLine_remaining = string_null;
1384 else if (tw("^7", theFontSize) == 0)
1385 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1386 return substring(s, 0, take);
1391 getWrappedLine_remaining = string_null;
1396 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1402 s = getWrappedLine_remaining;
1406 getWrappedLine_remaining = string_null;
1407 return s; // the line has no size ANYWAY, nothing would be displayed.
1410 cantake = textLengthUpToLength(s, w, tw);
1411 if(cantake > 0 && cantake < strlen(s))
1414 while(take > 0 && substring(s, take, 1) != " ")
1418 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1419 if(getWrappedLine_remaining == "")
1420 getWrappedLine_remaining = string_null;
1421 else if (tw("^7") == 0)
1422 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1423 return substring(s, 0, cantake);
1427 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1428 if(getWrappedLine_remaining == "")
1429 getWrappedLine_remaining = string_null;
1430 else if (tw("^7") == 0)
1431 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1432 return substring(s, 0, take);
1437 getWrappedLine_remaining = string_null;
1442 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1444 if(tw(theText, theFontSize) <= maxWidth)
1447 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1450 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1452 if(tw(theText) <= maxWidth)
1455 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1458 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1460 string subpattern, subpattern2, subpattern3, subpattern4;
1461 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1463 subpattern2 = ",teams,";
1465 subpattern2 = ",noteams,";
1467 subpattern3 = ",teamspawns,";
1469 subpattern3 = ",noteamspawns,";
1470 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1471 subpattern4 = ",race,";
1473 subpattern4 = string_null;
1475 if(substring(pattern, 0, 1) == "-")
1477 pattern = substring(pattern, 1, strlen(pattern) - 1);
1478 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1480 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1482 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1484 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1489 if(substring(pattern, 0, 1) == "+")
1490 pattern = substring(pattern, 1, strlen(pattern) - 1);
1491 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1492 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1493 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1497 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1504 void shuffle(float n, swapfunc_t swap, entity pass)
1507 for(i = 1; i < n; ++i)
1509 // swap i-th item at a random position from 0 to i
1510 // proof for even distribution:
1513 // item n+1 gets at any position with chance 1/(n+1)
1514 // all others will get their 1/n chance reduced by factor n/(n+1)
1515 // to be on place n+1, their chance will be 1/(n+1)
1516 // 1/n * n/(n+1) = 1/(n+1)
1518 j = floor(random() * (i + 1));
1524 string substring_range(string s, float b, float e)
1526 return substring(s, b, e - b);
1529 string swapwords(string str, float i, float j)
1532 string s1, s2, s3, s4, s5;
1533 float si, ei, sj, ej, s0, en;
1534 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1535 si = argv_start_index(i);
1536 sj = argv_start_index(j);
1537 ei = argv_end_index(i);
1538 ej = argv_end_index(j);
1539 s0 = argv_start_index(0);
1540 en = argv_end_index(n-1);
1541 s1 = substring_range(str, s0, si);
1542 s2 = substring_range(str, si, ei);
1543 s3 = substring_range(str, ei, sj);
1544 s4 = substring_range(str, sj, ej);
1545 s5 = substring_range(str, ej, en);
1546 return strcat(s1, s4, s3, s2, s5);
1549 string _shufflewords_str;
1550 void _shufflewords_swapfunc(float i, float j, entity pass)
1552 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1554 string shufflewords(string str)
1557 _shufflewords_str = str;
1558 n = tokenizebyseparator(str, " ");
1559 shuffle(n, _shufflewords_swapfunc, world);
1560 str = _shufflewords_str;
1561 _shufflewords_str = string_null;
1565 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1581 // actually, every number solves the equation!
1592 if(a > 0) // put the smaller solution first
1594 v_x = ((-b)-D) / (2*a);
1595 v_y = ((-b)+D) / (2*a);
1599 v_x = (-b+D) / (2*a);
1600 v_y = (-b-D) / (2*a);
1606 // complex solutions!
1619 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1623 // make origin and speed relative
1628 // now solve for ret, ret normalized:
1629 // eorg + t * evel == t * ret * spd
1630 // or, rather, solve for t:
1631 // |eorg + t * evel| == t * spd
1632 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1633 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1634 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1635 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1636 // q = (eorg * eorg) / (evel * evel - spd * spd)
1637 if(!solution_z) // no real solution
1640 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1641 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1642 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1643 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1644 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1645 // spd < |evel| * sin angle(evel, eorg)
1648 else if(solution_x > 0)
1650 // both solutions > 0: take the smaller one
1651 // happens if p < 0 and q > 0
1652 ret = normalize(eorg + solution_x * evel);
1654 else if(solution_y > 0)
1656 // one solution > 0: take the larger one
1657 // happens if q < 0 or q == 0 and p < 0
1658 ret = normalize(eorg + solution_y * evel);
1662 // no solution > 0: reject
1663 // happens if p > 0 and q >= 0
1664 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1665 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1670 // "Enemy is moving away from me at more than spd"
1674 // NOTE: we always got a solution if spd > |evel|
1676 if(newton_style == 2)
1677 ret = normalize(ret * spd + myvel);
1682 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1687 if(newton_style == 2)
1689 // true Newtonian projectiles with automatic aim adjustment
1691 // solve: |outspeed * mydir - myvel| = spd
1692 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1693 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1697 // myvel^2 - (mydir * myvel)^2 > spd^2
1698 // velocity without mydir component > spd
1699 // fire at smallest possible spd that works?
1700 // |(mydir * myvel) * myvel - myvel| = spd
1702 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1706 outspeed = solution_y; // the larger one
1709 //outspeed = 0; // slowest possible shot
1710 outspeed = solution_x; // the real part (that is, the average!)
1711 //dprint("impossible shot, adjusting\n");
1714 outspeed = bound(spd * mi, outspeed, spd * ma);
1715 return mydir * outspeed;
1719 return myvel + spd * mydir;
1722 void check_unacceptable_compiler_bugs()
1724 if(cvar("_allow_unacceptable_compiler_bugs"))
1726 tokenize_console("foo bar");
1727 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1728 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.");
1732 error("The empty string counts as false. We do not want that!");
1735 float compressShotOrigin(vector v)
1739 y = rint(v_y * 4) + 128;
1740 z = rint(v_z * 4) + 128;
1741 if(x > 255 || x < 0)
1743 print("shot origin ", vtos(v), " x out of bounds\n");
1744 x = bound(0, x, 255);
1746 if(y > 255 || y < 0)
1748 print("shot origin ", vtos(v), " y out of bounds\n");
1749 y = bound(0, y, 255);
1751 if(z > 255 || z < 0)
1753 print("shot origin ", vtos(v), " z out of bounds\n");
1754 z = bound(0, z, 255);
1756 return x * 0x10000 + y * 0x100 + z;
1758 vector decompressShotOrigin(float f)
1761 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1762 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1763 v_z = ((f & 0xFF) - 128) / 4;
1767 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1769 float start, end, root, child;
1772 start = floor((n - 2) / 2);
1775 // siftdown(start, count-1);
1777 while(root * 2 + 1 <= n-1)
1779 child = root * 2 + 1;
1781 if(cmp(child, child+1, pass) < 0)
1783 if(cmp(root, child, pass) < 0)
1785 swap(root, child, pass);
1801 // siftdown(0, end);
1803 while(root * 2 + 1 <= end)
1805 child = root * 2 + 1;
1806 if(child < end && cmp(child, child+1, pass) < 0)
1808 if(cmp(root, child, pass) < 0)
1810 swap(root, child, pass);
1820 void RandomSelection_Init()
1822 RandomSelection_totalweight = 0;
1823 RandomSelection_chosen_ent = world;
1824 RandomSelection_chosen_float = 0;
1825 RandomSelection_chosen_string = string_null;
1826 RandomSelection_best_priority = -1;
1828 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1830 if(priority > RandomSelection_best_priority)
1832 RandomSelection_best_priority = priority;
1833 RandomSelection_chosen_ent = e;
1834 RandomSelection_chosen_float = f;
1835 RandomSelection_chosen_string = s;
1836 RandomSelection_totalweight = weight;
1838 else if(priority == RandomSelection_best_priority)
1840 RandomSelection_totalweight += weight;
1841 if(random() * RandomSelection_totalweight <= weight)
1843 RandomSelection_chosen_ent = e;
1844 RandomSelection_chosen_float = f;
1845 RandomSelection_chosen_string = s;
1851 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1853 // NOTE: we'll always choose the SMALLER value...
1854 float healthdamage, armordamage, armorideal;
1855 if (deathtype == DEATH_DROWN) // Why should armor help here...
1858 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1859 armordamage = a + (h - 1); // damage we can take if we could use more armor
1860 armorideal = healthdamage * armorblock;
1862 if(armordamage < healthdamage)
1875 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1878 if (deathtype == DEATH_DROWN) // Why should armor help here...
1880 v_y = bound(0, damage * armorblock, a); // save
1881 v_x = bound(0, damage - v_y, damage); // take
1887 string getcurrentmod()
1891 m = cvar_string("fs_gamedir");
1892 n = tokenize_console(m);
1904 v = ReadShort() * 256; // note: this is signed
1905 v += ReadByte(); // note: this is unsigned
1908 vector ReadInt48_t()
1911 v_x = ReadInt24_t();
1912 v_y = ReadInt24_t();
1916 vector ReadInt72_t()
1919 v_x = ReadInt24_t();
1920 v_y = ReadInt24_t();
1921 v_z = ReadInt24_t();
1925 void WriteInt24_t(float dst, float val)
1928 WriteShort(dst, (v = floor(val / 256)));
1929 WriteByte(dst, val - v * 256); // 0..255
1931 void WriteInt48_t(float dst, vector val)
1933 WriteInt24_t(dst, val_x);
1934 WriteInt24_t(dst, val_y);
1936 void WriteInt72_t(float dst, vector val)
1938 WriteInt24_t(dst, val_x);
1939 WriteInt24_t(dst, val_y);
1940 WriteInt24_t(dst, val_z);
1945 float float2range11(float f)
1947 // continuous function mapping all reals into -1..1
1948 return f / (fabs(f) + 1);
1951 float float2range01(float f)
1953 // continuous function mapping all reals into 0..1
1954 return 0.5 + 0.5 * float2range11(f);
1957 // from the GNU Scientific Library
1958 float gsl_ran_gaussian_lastvalue;
1959 float gsl_ran_gaussian_lastvalue_set;
1960 float gsl_ran_gaussian(float sigma)
1963 if(gsl_ran_gaussian_lastvalue_set)
1965 gsl_ran_gaussian_lastvalue_set = 0;
1966 return sigma * gsl_ran_gaussian_lastvalue;
1970 a = random() * 2 * M_PI;
1971 b = sqrt(-2 * log(random()));
1972 gsl_ran_gaussian_lastvalue = cos(a) * b;
1973 gsl_ran_gaussian_lastvalue_set = 1;
1974 return sigma * sin(a) * b;
1978 string car(string s)
1981 o = strstrofs(s, " ", 0);
1984 return substring(s, 0, o);
1986 string cdr(string s)
1989 o = strstrofs(s, " ", 0);
1992 return substring(s, o + 1, strlen(s) - (o + 1));
1994 float matchacl(string acl, string str)
2001 t = car(acl); acl = cdr(acl);
2004 if(substring(t, 0, 1) == "-")
2007 t = substring(t, 1, strlen(t) - 1);
2009 else if(substring(t, 0, 1) == "+")
2010 t = substring(t, 1, strlen(t) - 1);
2012 if(substring(t, -1, 1) == "*")
2014 t = substring(t, 0, strlen(t) - 1);
2015 s = substring(str, 0, strlen(t));
2027 float startsWith(string haystack, string needle)
2029 return substring(haystack, 0, strlen(needle)) == needle;
2031 float startsWithNocase(string haystack, string needle)
2033 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2036 string get_model_datafilename(string m, float sk, string fil)
2041 m = "models/player/*_";
2043 m = strcat(m, ftos(sk));
2046 return strcat(m, ".", fil);
2049 float get_model_parameters(string m, float sk)
2054 get_model_parameters_modelname = string_null;
2055 get_model_parameters_modelskin = -1;
2056 get_model_parameters_name = string_null;
2057 get_model_parameters_species = -1;
2058 get_model_parameters_sex = string_null;
2059 get_model_parameters_weight = -1;
2060 get_model_parameters_age = -1;
2061 get_model_parameters_desc = string_null;
2062 get_model_parameters_bone_upperbody = string_null;
2063 get_model_parameters_bone_weapon = string_null;
2064 for(i = 0; i < MAX_AIM_BONES; ++i)
2066 get_model_parameters_bone_aim[i] = string_null;
2067 get_model_parameters_bone_aimweight[i] = 0;
2069 get_model_parameters_fixbone = 0;
2074 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2075 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2079 if(substring(m, -4, -1) != ".txt")
2081 if(substring(m, -6, 1) != "_")
2083 sk = stof(substring(m, -5, 1));
2084 m = substring(m, 0, -7);
2087 fn = get_model_datafilename(m, sk, "txt");
2088 fh = fopen(fn, FILE_READ);
2092 fn = get_model_datafilename(m, sk, "txt");
2093 fh = fopen(fn, FILE_READ);
2098 get_model_parameters_modelname = m;
2099 get_model_parameters_modelskin = sk;
2100 while((s = fgets(fh)))
2103 break; // next lines will be description
2107 get_model_parameters_name = s;
2111 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2112 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2113 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2114 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2115 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2116 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2117 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2120 get_model_parameters_sex = s;
2122 get_model_parameters_weight = stof(s);
2124 get_model_parameters_age = stof(s);
2125 if(c == "description")
2126 get_model_parameters_description = s;
2127 if(c == "bone_upperbody")
2128 get_model_parameters_bone_upperbody = s;
2129 if(c == "bone_weapon")
2130 get_model_parameters_bone_weapon = s;
2131 for(i = 0; i < MAX_AIM_BONES; ++i)
2132 if(c == strcat("bone_aim", ftos(i)))
2134 get_model_parameters_bone_aimweight[i] = stof(car(s));
2135 get_model_parameters_bone_aim[i] = cdr(s);
2138 get_model_parameters_fixbone = stof(s);
2141 while((s = fgets(fh)))
2143 if(get_model_parameters_desc)
2144 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2146 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2154 vector vec2(vector v)
2161 vector NearestPointOnBox(entity box, vector org)
2163 vector m1, m2, nearest;
2165 m1 = box.mins + box.origin;
2166 m2 = box.maxs + box.origin;
2168 nearest_x = bound(m1_x, org_x, m2_x);
2169 nearest_y = bound(m1_y, org_y, m2_y);
2170 nearest_z = bound(m1_z, org_z, m2_z);
2176 float vercmp_recursive(string v1, string v2)
2182 dot1 = strstrofs(v1, ".", 0);
2183 dot2 = strstrofs(v2, ".", 0);
2187 s1 = substring(v1, 0, dot1);
2191 s2 = substring(v2, 0, dot2);
2193 r = stof(s1) - stof(s2);
2197 r = strcasecmp(s1, s2);
2210 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2213 float vercmp(string v1, string v2)
2215 if(strcasecmp(v1, v2) == 0) // early out check
2224 return vercmp_recursive(v1, v2);
2227 float u8_strsize(string s)
2247 // translation helpers
2248 string language_filename(string s)
2253 if(fn == "" || fn == "dump")
2255 fn = strcat(s, ".", fn);
2256 if((fh = fopen(fn, FILE_READ)) >= 0)
2263 string CTX(string s)
2265 float p = strstrofs(s, "^", 0);
2268 return substring(s, p+1, -1);
2271 // x-encoding (encoding as zero length invisible string)
2272 const string XENCODE_2 = "xX";
2273 const string XENCODE_22 = "0123456789abcdefABCDEF";
2274 string xencode(float f)
2277 d = mod(f, 22); f = floor(f / 22);
2278 c = mod(f, 22); f = floor(f / 22);
2279 b = mod(f, 22); f = floor(f / 22);
2280 a = mod(f, 2); // f = floor(f / 2);
2283 substring(XENCODE_2, a, 1),
2284 substring(XENCODE_22, b, 1),
2285 substring(XENCODE_22, c, 1),
2286 substring(XENCODE_22, d, 1)
2289 float xdecode(string s)
2292 if(substring(s, 0, 1) != "^")
2296 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2297 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2298 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2299 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2300 if(a < 0 || b < 0 || c < 0 || d < 0)
2302 return ((a * 22 + b) * 22 + c) * 22 + d;
2305 float lowestbit(float f)
2316 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2318 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2321 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2324 // escape the string to make it safe for consoles
2325 string MakeConsoleSafe(string input)
2327 input = strreplace("\n", "", input);
2328 input = strreplace("\\", "\\\\", input);
2329 input = strreplace("$", "$$", input);
2330 input = strreplace("\"", "\\\"", input);
2335 // get true/false value of a string with multiple different inputs
2336 float InterpretBoolean(string input)
2338 switch(strtolower(input))
2350 default: return stof(input);
2356 entity ReadCSQCEntity()
2362 return findfloat(world, entnum, f);
2366 float shutdown_running;
2371 void CSQC_Shutdown()
2377 if(shutdown_running)
2379 print("Recursive shutdown detected! Only restoring cvars...\n");
2383 shutdown_running = 1;
2386 cvar_settemp_restore(); // this must be done LAST, but in any case
2389 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2390 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2391 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2392 // this will use the value:
2394 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2395 // accuracy at x is 1/derivative, i.e.
2396 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2398 void WriteApproxPastTime(float dst, float t)
2400 float dt = time - t;
2402 // warning: this is approximate; do not resend when you don't have to!
2403 // be careful with sendflags here!
2404 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2407 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2410 dt = rint(bound(0, dt, 255));
2416 float ReadApproxPastTime()
2418 float dt = ReadByte();
2420 // map from range...PPROXPASTTIME_MAX / 256
2421 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2423 return servertime - dt;
2428 .float skeleton_bones_index;
2429 void Skeleton_SetBones(entity e)
2431 // set skeleton_bones to the total number of bones on the model
2432 if(e.skeleton_bones_index == e.modelindex)
2433 return; // same model, nothing to update
2436 skelindex = skel_create(e.modelindex);
2437 e.skeleton_bones = skel_get_numbones(skelindex);
2438 skel_delete(skelindex);
2439 e.skeleton_bones_index = e.modelindex;
2443 string to_execute_next_frame;
2444 void execute_next_frame()
2446 if(to_execute_next_frame)
2448 localcmd("\n", to_execute_next_frame, "\n");
2449 strunzone(to_execute_next_frame);
2450 to_execute_next_frame = string_null;
2453 void queue_to_execute_next_frame(string s)
2455 if(to_execute_next_frame)
2457 s = strcat(s, "\n", to_execute_next_frame);
2458 strunzone(to_execute_next_frame);
2460 to_execute_next_frame = strzone(s);
2463 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2466 ((( startspeedfactor + endspeedfactor - 2
2467 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2468 ) * x + startspeedfactor
2472 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2474 if(startspeedfactor < 0 || endspeedfactor < 0)
2478 // if this is the case, the possible zeros of the first derivative are outside
2480 We can calculate this condition as condition
2485 // better, see below:
2486 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2489 // if this is the case, the first derivative has no zeros at all
2490 float se = startspeedfactor + endspeedfactor;
2491 float s_e = startspeedfactor - endspeedfactor;
2492 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2495 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2496 // we also get s_e <= 6 - se
2497 // 3 * (se - 4)^2 + (6 - se)^2
2498 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2499 // Therefore, above "better" check works!
2503 // known good cases:
2511 // (3.5, [0.2..2.3])
2516 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2518 s + e - 2 == 0: no inflection
2521 0 < inflection < 1 if:
2522 0 < 2s + e - 3 < 3s + 3e - 6
2523 2s + e > 3 and 2e + s > 3
2526 0 < inflection < 1 if:
2527 0 > 2s + e - 3 > 3s + 3e - 6
2528 2s + e < 3 and 2e + s < 3
2530 Therefore: there is an inflection point iff:
2531 e outside (3 - s)/2 .. 3 - s*2
2533 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)
2537 .float FindConnectedComponent_processing;
2538 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2540 entity queue_start, queue_end;
2542 // we build a queue of to-be-processed entities.
2543 // queue_start is the next entity to be checked for neighbors
2544 // queue_end is the last entity added
2546 if(e.FindConnectedComponent_processing)
2547 error("recursion or broken cleanup");
2549 // start with a 1-element queue
2550 queue_start = queue_end = e;
2551 queue_end.fld = world;
2552 queue_end.FindConnectedComponent_processing = 1;
2554 // for each queued item:
2555 for(; queue_start; queue_start = queue_start.fld)
2557 // find all neighbors of queue_start
2559 for(t = world; (t = nxt(t, queue_start, pass)); )
2561 if(t.FindConnectedComponent_processing)
2563 if(iscon(t, queue_start, pass))
2565 // it is connected? ADD IT. It will look for neighbors soon too.
2568 queue_end.fld = world;
2569 queue_end.FindConnectedComponent_processing = 1;
2575 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2576 queue_start.FindConnectedComponent_processing = 0;
2579 // todo: this sucks, lets find a better way to do backtraces?
2580 void backtrace(string msg)
2584 dev = autocvar_developer;
2585 war = autocvar_prvm_backtraceforwarnings;
2587 dev = cvar("developer");
2588 war = cvar("prvm_backtraceforwarnings");
2590 cvar_set("developer", "1");
2591 cvar_set("prvm_backtraceforwarnings", "1");
2593 print("--- CUT HERE ---\nWARNING: ");
2596 remove(world); // isn't there any better way to cause a backtrace?
2597 print("\n--- CUT UNTIL HERE ---\n");
2598 cvar_set("developer", ftos(dev));
2599 cvar_set("prvm_backtraceforwarnings", ftos(war));
2602 // color code replace, place inside of sprintf and parse the string
2603 string CCR(string input)
2605 // See the autocvar declarations in util.qh for default values
2607 // foreground/normal colors
2608 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2609 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2610 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2611 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2614 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2615 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2616 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2618 // background colors
2619 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2620 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2624 vector vec3(float x, float y, float z)
2634 vector animfixfps(entity e, vector a, vector b)
2636 // multi-frame anim: keep as-is
2640 dur = frameduration(e.modelindex, a_x);
2644 dur = frameduration(e.modelindex, a_x);
2654 void dedicated_print(string input) // print(), but only print if the server is not local
2656 if(server_is_dedicated) { print(input); }
2661 float Announcer_PickNumber(float type, float num)
2669 case 10: return ANNCE_NUM_GAMESTART_10;
2670 case 9: return ANNCE_NUM_GAMESTART_9;
2671 case 8: return ANNCE_NUM_GAMESTART_8;
2672 case 7: return ANNCE_NUM_GAMESTART_7;
2673 case 6: return ANNCE_NUM_GAMESTART_6;
2674 case 5: return ANNCE_NUM_GAMESTART_5;
2675 case 4: return ANNCE_NUM_GAMESTART_4;
2676 case 3: return ANNCE_NUM_GAMESTART_3;
2677 case 2: return ANNCE_NUM_GAMESTART_2;
2678 case 1: return ANNCE_NUM_GAMESTART_1;
2686 case 10: return ANNCE_NUM_IDLE_10;
2687 case 9: return ANNCE_NUM_IDLE_9;
2688 case 8: return ANNCE_NUM_IDLE_8;
2689 case 7: return ANNCE_NUM_IDLE_7;
2690 case 6: return ANNCE_NUM_IDLE_6;
2691 case 5: return ANNCE_NUM_IDLE_5;
2692 case 4: return ANNCE_NUM_IDLE_4;
2693 case 3: return ANNCE_NUM_IDLE_3;
2694 case 2: return ANNCE_NUM_IDLE_2;
2695 case 1: return ANNCE_NUM_IDLE_1;
2703 case 10: return ANNCE_NUM_KILL_10;
2704 case 9: return ANNCE_NUM_KILL_9;
2705 case 8: return ANNCE_NUM_KILL_8;
2706 case 7: return ANNCE_NUM_KILL_7;
2707 case 6: return ANNCE_NUM_KILL_6;
2708 case 5: return ANNCE_NUM_KILL_5;
2709 case 4: return ANNCE_NUM_KILL_4;
2710 case 3: return ANNCE_NUM_KILL_3;
2711 case 2: return ANNCE_NUM_KILL_2;
2712 case 1: return ANNCE_NUM_KILL_1;
2720 case 10: return ANNCE_NUM_RESPAWN_10;
2721 case 9: return ANNCE_NUM_RESPAWN_9;
2722 case 8: return ANNCE_NUM_RESPAWN_8;
2723 case 7: return ANNCE_NUM_RESPAWN_7;
2724 case 6: return ANNCE_NUM_RESPAWN_6;
2725 case 5: return ANNCE_NUM_RESPAWN_5;
2726 case 4: return ANNCE_NUM_RESPAWN_4;
2727 case 3: return ANNCE_NUM_RESPAWN_3;
2728 case 2: return ANNCE_NUM_RESPAWN_2;
2729 case 1: return ANNCE_NUM_RESPAWN_1;
2733 case CNT_ROUNDSTART:
2737 case 10: return ANNCE_NUM_ROUNDSTART_10;
2738 case 9: return ANNCE_NUM_ROUNDSTART_9;
2739 case 8: return ANNCE_NUM_ROUNDSTART_8;
2740 case 7: return ANNCE_NUM_ROUNDSTART_7;
2741 case 6: return ANNCE_NUM_ROUNDSTART_6;
2742 case 5: return ANNCE_NUM_ROUNDSTART_5;
2743 case 4: return ANNCE_NUM_ROUNDSTART_4;
2744 case 3: return ANNCE_NUM_ROUNDSTART_3;
2745 case 2: return ANNCE_NUM_ROUNDSTART_2;
2746 case 1: return ANNCE_NUM_ROUNDSTART_1;
2754 case 10: return ANNCE_NUM_10;
2755 case 9: return ANNCE_NUM_9;
2756 case 8: return ANNCE_NUM_8;
2757 case 7: return ANNCE_NUM_7;
2758 case 6: return ANNCE_NUM_6;
2759 case 5: return ANNCE_NUM_5;
2760 case 4: return ANNCE_NUM_4;
2761 case 3: return ANNCE_NUM_3;
2762 case 2: return ANNCE_NUM_2;
2763 case 1: return ANNCE_NUM_1;
2768 return NOTIF_ABORT; // abort sending if none of these numbers were right
2773 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
2775 switch(nativecontents)
2780 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2782 return DPCONTENTS_WATER;
2784 return DPCONTENTS_SLIME;
2786 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2788 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2793 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
2795 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2796 return CONTENT_SOLID;
2797 if(supercontents & DPCONTENTS_SKY)
2799 if(supercontents & DPCONTENTS_LAVA)
2800 return CONTENT_LAVA;
2801 if(supercontents & DPCONTENTS_SLIME)
2802 return CONTENT_SLIME;
2803 if(supercontents & DPCONTENTS_WATER)
2804 return CONTENT_WATER;
2805 return CONTENT_EMPTY;
2809 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2812 (c - 2 * b + a) * (t * t) +
2817 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2820 (c - 2 * b + a) * (2 * t) +