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 == "bone_upperbody")
2126 get_model_parameters_bone_upperbody = s;
2127 if(c == "bone_weapon")
2128 get_model_parameters_bone_weapon = s;
2129 for(i = 0; i < MAX_AIM_BONES; ++i)
2130 if(c == strcat("bone_aim", ftos(i)))
2132 get_model_parameters_bone_aimweight[i] = stof(car(s));
2133 get_model_parameters_bone_aim[i] = cdr(s);
2136 get_model_parameters_fixbone = stof(s);
2139 while((s = fgets(fh)))
2141 if(get_model_parameters_desc)
2142 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2144 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2152 vector vec2(vector v)
2159 vector NearestPointOnBox(entity box, vector org)
2161 vector m1, m2, nearest;
2163 m1 = box.mins + box.origin;
2164 m2 = box.maxs + box.origin;
2166 nearest_x = bound(m1_x, org_x, m2_x);
2167 nearest_y = bound(m1_y, org_y, m2_y);
2168 nearest_z = bound(m1_z, org_z, m2_z);
2174 float vercmp_recursive(string v1, string v2)
2180 dot1 = strstrofs(v1, ".", 0);
2181 dot2 = strstrofs(v2, ".", 0);
2185 s1 = substring(v1, 0, dot1);
2189 s2 = substring(v2, 0, dot2);
2191 r = stof(s1) - stof(s2);
2195 r = strcasecmp(s1, s2);
2208 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2211 float vercmp(string v1, string v2)
2213 if(strcasecmp(v1, v2) == 0) // early out check
2222 return vercmp_recursive(v1, v2);
2225 float u8_strsize(string s)
2245 // translation helpers
2246 string language_filename(string s)
2251 if(fn == "" || fn == "dump")
2253 fn = strcat(s, ".", fn);
2254 if((fh = fopen(fn, FILE_READ)) >= 0)
2261 string CTX(string s)
2263 float p = strstrofs(s, "^", 0);
2266 return substring(s, p+1, -1);
2269 // x-encoding (encoding as zero length invisible string)
2270 const string XENCODE_2 = "xX";
2271 const string XENCODE_22 = "0123456789abcdefABCDEF";
2272 string xencode(float f)
2275 d = mod(f, 22); f = floor(f / 22);
2276 c = mod(f, 22); f = floor(f / 22);
2277 b = mod(f, 22); f = floor(f / 22);
2278 a = mod(f, 2); // f = floor(f / 2);
2281 substring(XENCODE_2, a, 1),
2282 substring(XENCODE_22, b, 1),
2283 substring(XENCODE_22, c, 1),
2284 substring(XENCODE_22, d, 1)
2287 float xdecode(string s)
2290 if(substring(s, 0, 1) != "^")
2294 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2295 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2296 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2297 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2298 if(a < 0 || b < 0 || c < 0 || d < 0)
2300 return ((a * 22 + b) * 22 + c) * 22 + d;
2303 float lowestbit(float f)
2314 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2316 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2319 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2322 // escape the string to make it safe for consoles
2323 string MakeConsoleSafe(string input)
2325 input = strreplace("\n", "", input);
2326 input = strreplace("\\", "\\\\", input);
2327 input = strreplace("$", "$$", input);
2328 input = strreplace("\"", "\\\"", input);
2333 // get true/false value of a string with multiple different inputs
2334 float InterpretBoolean(string input)
2336 switch(strtolower(input))
2348 default: return stof(input);
2354 entity ReadCSQCEntity()
2360 return findfloat(world, entnum, f);
2364 float shutdown_running;
2369 void CSQC_Shutdown()
2375 if(shutdown_running)
2377 print("Recursive shutdown detected! Only restoring cvars...\n");
2381 shutdown_running = 1;
2384 cvar_settemp_restore(); // this must be done LAST, but in any case
2387 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2388 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2389 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2390 // this will use the value:
2392 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2393 // accuracy at x is 1/derivative, i.e.
2394 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2396 void WriteApproxPastTime(float dst, float t)
2398 float dt = time - t;
2400 // warning: this is approximate; do not resend when you don't have to!
2401 // be careful with sendflags here!
2402 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2405 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2408 dt = rint(bound(0, dt, 255));
2414 float ReadApproxPastTime()
2416 float dt = ReadByte();
2418 // map from range...PPROXPASTTIME_MAX / 256
2419 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2421 return servertime - dt;
2426 .float skeleton_bones_index;
2427 void Skeleton_SetBones(entity e)
2429 // set skeleton_bones to the total number of bones on the model
2430 if(e.skeleton_bones_index == e.modelindex)
2431 return; // same model, nothing to update
2434 skelindex = skel_create(e.modelindex);
2435 e.skeleton_bones = skel_get_numbones(skelindex);
2436 skel_delete(skelindex);
2437 e.skeleton_bones_index = e.modelindex;
2441 string to_execute_next_frame;
2442 void execute_next_frame()
2444 if(to_execute_next_frame)
2446 localcmd("\n", to_execute_next_frame, "\n");
2447 strunzone(to_execute_next_frame);
2448 to_execute_next_frame = string_null;
2451 void queue_to_execute_next_frame(string s)
2453 if(to_execute_next_frame)
2455 s = strcat(s, "\n", to_execute_next_frame);
2456 strunzone(to_execute_next_frame);
2458 to_execute_next_frame = strzone(s);
2461 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2464 ((( startspeedfactor + endspeedfactor - 2
2465 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2466 ) * x + startspeedfactor
2470 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2472 if(startspeedfactor < 0 || endspeedfactor < 0)
2476 // if this is the case, the possible zeros of the first derivative are outside
2478 We can calculate this condition as condition
2483 // better, see below:
2484 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2487 // if this is the case, the first derivative has no zeros at all
2488 float se = startspeedfactor + endspeedfactor;
2489 float s_e = startspeedfactor - endspeedfactor;
2490 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2493 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2494 // we also get s_e <= 6 - se
2495 // 3 * (se - 4)^2 + (6 - se)^2
2496 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2497 // Therefore, above "better" check works!
2501 // known good cases:
2509 // (3.5, [0.2..2.3])
2514 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2516 s + e - 2 == 0: no inflection
2519 0 < inflection < 1 if:
2520 0 < 2s + e - 3 < 3s + 3e - 6
2521 2s + e > 3 and 2e + s > 3
2524 0 < inflection < 1 if:
2525 0 > 2s + e - 3 > 3s + 3e - 6
2526 2s + e < 3 and 2e + s < 3
2528 Therefore: there is an inflection point iff:
2529 e outside (3 - s)/2 .. 3 - s*2
2531 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)
2535 .float FindConnectedComponent_processing;
2536 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2538 entity queue_start, queue_end;
2540 // we build a queue of to-be-processed entities.
2541 // queue_start is the next entity to be checked for neighbors
2542 // queue_end is the last entity added
2544 if(e.FindConnectedComponent_processing)
2545 error("recursion or broken cleanup");
2547 // start with a 1-element queue
2548 queue_start = queue_end = e;
2549 queue_end.fld = world;
2550 queue_end.FindConnectedComponent_processing = 1;
2552 // for each queued item:
2553 for(; queue_start; queue_start = queue_start.fld)
2555 // find all neighbors of queue_start
2557 for(t = world; (t = nxt(t, queue_start, pass)); )
2559 if(t.FindConnectedComponent_processing)
2561 if(iscon(t, queue_start, pass))
2563 // it is connected? ADD IT. It will look for neighbors soon too.
2566 queue_end.fld = world;
2567 queue_end.FindConnectedComponent_processing = 1;
2573 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2574 queue_start.FindConnectedComponent_processing = 0;
2577 // todo: this sucks, lets find a better way to do backtraces?
2578 void backtrace(string msg)
2582 dev = autocvar_developer;
2583 war = autocvar_prvm_backtraceforwarnings;
2585 dev = cvar("developer");
2586 war = cvar("prvm_backtraceforwarnings");
2588 cvar_set("developer", "1");
2589 cvar_set("prvm_backtraceforwarnings", "1");
2591 print("--- CUT HERE ---\nWARNING: ");
2594 remove(world); // isn't there any better way to cause a backtrace?
2595 print("\n--- CUT UNTIL HERE ---\n");
2596 cvar_set("developer", ftos(dev));
2597 cvar_set("prvm_backtraceforwarnings", ftos(war));
2600 // color code replace, place inside of sprintf and parse the string
2601 string CCR(string input)
2603 // See the autocvar declarations in util.qh for default values
2605 // foreground/normal colors
2606 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2607 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2608 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2609 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2612 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2613 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2614 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2616 // background colors
2617 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2618 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2622 vector vec3(float x, float y, float z)
2632 vector animfixfps(entity e, vector a, vector b)
2634 // multi-frame anim: keep as-is
2638 dur = frameduration(e.modelindex, a_x);
2642 dur = frameduration(e.modelindex, a_x);
2652 void dedicated_print(string input) // print(), but only print if the server is not local
2654 if(server_is_dedicated) { print(input); }
2659 float Announcer_PickNumber(float type, float num)
2667 case 10: return ANNCE_NUM_GAMESTART_10;
2668 case 9: return ANNCE_NUM_GAMESTART_9;
2669 case 8: return ANNCE_NUM_GAMESTART_8;
2670 case 7: return ANNCE_NUM_GAMESTART_7;
2671 case 6: return ANNCE_NUM_GAMESTART_6;
2672 case 5: return ANNCE_NUM_GAMESTART_5;
2673 case 4: return ANNCE_NUM_GAMESTART_4;
2674 case 3: return ANNCE_NUM_GAMESTART_3;
2675 case 2: return ANNCE_NUM_GAMESTART_2;
2676 case 1: return ANNCE_NUM_GAMESTART_1;
2684 case 10: return ANNCE_NUM_IDLE_10;
2685 case 9: return ANNCE_NUM_IDLE_9;
2686 case 8: return ANNCE_NUM_IDLE_8;
2687 case 7: return ANNCE_NUM_IDLE_7;
2688 case 6: return ANNCE_NUM_IDLE_6;
2689 case 5: return ANNCE_NUM_IDLE_5;
2690 case 4: return ANNCE_NUM_IDLE_4;
2691 case 3: return ANNCE_NUM_IDLE_3;
2692 case 2: return ANNCE_NUM_IDLE_2;
2693 case 1: return ANNCE_NUM_IDLE_1;
2701 case 10: return ANNCE_NUM_KILL_10;
2702 case 9: return ANNCE_NUM_KILL_9;
2703 case 8: return ANNCE_NUM_KILL_8;
2704 case 7: return ANNCE_NUM_KILL_7;
2705 case 6: return ANNCE_NUM_KILL_6;
2706 case 5: return ANNCE_NUM_KILL_5;
2707 case 4: return ANNCE_NUM_KILL_4;
2708 case 3: return ANNCE_NUM_KILL_3;
2709 case 2: return ANNCE_NUM_KILL_2;
2710 case 1: return ANNCE_NUM_KILL_1;
2718 case 10: return ANNCE_NUM_RESPAWN_10;
2719 case 9: return ANNCE_NUM_RESPAWN_9;
2720 case 8: return ANNCE_NUM_RESPAWN_8;
2721 case 7: return ANNCE_NUM_RESPAWN_7;
2722 case 6: return ANNCE_NUM_RESPAWN_6;
2723 case 5: return ANNCE_NUM_RESPAWN_5;
2724 case 4: return ANNCE_NUM_RESPAWN_4;
2725 case 3: return ANNCE_NUM_RESPAWN_3;
2726 case 2: return ANNCE_NUM_RESPAWN_2;
2727 case 1: return ANNCE_NUM_RESPAWN_1;
2731 case CNT_ROUNDSTART:
2735 case 10: return ANNCE_NUM_ROUNDSTART_10;
2736 case 9: return ANNCE_NUM_ROUNDSTART_9;
2737 case 8: return ANNCE_NUM_ROUNDSTART_8;
2738 case 7: return ANNCE_NUM_ROUNDSTART_7;
2739 case 6: return ANNCE_NUM_ROUNDSTART_6;
2740 case 5: return ANNCE_NUM_ROUNDSTART_5;
2741 case 4: return ANNCE_NUM_ROUNDSTART_4;
2742 case 3: return ANNCE_NUM_ROUNDSTART_3;
2743 case 2: return ANNCE_NUM_ROUNDSTART_2;
2744 case 1: return ANNCE_NUM_ROUNDSTART_1;
2752 case 10: return ANNCE_NUM_10;
2753 case 9: return ANNCE_NUM_9;
2754 case 8: return ANNCE_NUM_8;
2755 case 7: return ANNCE_NUM_7;
2756 case 6: return ANNCE_NUM_6;
2757 case 5: return ANNCE_NUM_5;
2758 case 4: return ANNCE_NUM_4;
2759 case 3: return ANNCE_NUM_3;
2760 case 2: return ANNCE_NUM_2;
2761 case 1: return ANNCE_NUM_1;
2766 return NOTIF_ABORT; // abort sending if none of these numbers were right
2771 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
2773 switch(nativecontents)
2778 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2780 return DPCONTENTS_WATER;
2782 return DPCONTENTS_SLIME;
2784 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2786 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2791 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
2793 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2794 return CONTENT_SOLID;
2795 if(supercontents & DPCONTENTS_SKY)
2797 if(supercontents & DPCONTENTS_LAVA)
2798 return CONTENT_LAVA;
2799 if(supercontents & DPCONTENTS_SLIME)
2800 return CONTENT_SLIME;
2801 if(supercontents & DPCONTENTS_WATER)
2802 return CONTENT_WATER;
2803 return CONTENT_EMPTY;
2807 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2810 (c - 2 * b + a) * (t * t) +
2815 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2818 (c - 2 * b + a) * (2 * t) +