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 float compressShotOrigin(vector v)
1726 y = rint(v_y * 4) + 128;
1727 z = rint(v_z * 4) + 128;
1728 if(x > 255 || x < 0)
1730 print("shot origin ", vtos(v), " x out of bounds\n");
1731 x = bound(0, x, 255);
1733 if(y > 255 || y < 0)
1735 print("shot origin ", vtos(v), " y out of bounds\n");
1736 y = bound(0, y, 255);
1738 if(z > 255 || z < 0)
1740 print("shot origin ", vtos(v), " z out of bounds\n");
1741 z = bound(0, z, 255);
1743 return x * 0x10000 + y * 0x100 + z;
1745 vector decompressShotOrigin(float f)
1748 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1749 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1750 v_z = ((f & 0xFF) - 128) / 4;
1754 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1756 float start, end, root, child;
1759 start = floor((n - 2) / 2);
1762 // siftdown(start, count-1);
1764 while(root * 2 + 1 <= n-1)
1766 child = root * 2 + 1;
1768 if(cmp(child, child+1, pass) < 0)
1770 if(cmp(root, child, pass) < 0)
1772 swap(root, child, pass);
1788 // siftdown(0, end);
1790 while(root * 2 + 1 <= end)
1792 child = root * 2 + 1;
1793 if(child < end && cmp(child, child+1, pass) < 0)
1795 if(cmp(root, child, pass) < 0)
1797 swap(root, child, pass);
1807 void RandomSelection_Init()
1809 RandomSelection_totalweight = 0;
1810 RandomSelection_chosen_ent = world;
1811 RandomSelection_chosen_float = 0;
1812 RandomSelection_chosen_string = string_null;
1813 RandomSelection_best_priority = -1;
1815 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1817 if(priority > RandomSelection_best_priority)
1819 RandomSelection_best_priority = priority;
1820 RandomSelection_chosen_ent = e;
1821 RandomSelection_chosen_float = f;
1822 RandomSelection_chosen_string = s;
1823 RandomSelection_totalweight = weight;
1825 else if(priority == RandomSelection_best_priority)
1827 RandomSelection_totalweight += weight;
1828 if(random() * RandomSelection_totalweight <= weight)
1830 RandomSelection_chosen_ent = e;
1831 RandomSelection_chosen_float = f;
1832 RandomSelection_chosen_string = s;
1838 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1840 // NOTE: we'll always choose the SMALLER value...
1841 float healthdamage, armordamage, armorideal;
1842 if (deathtype == DEATH_DROWN) // Why should armor help here...
1845 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1846 armordamage = a + (h - 1); // damage we can take if we could use more armor
1847 armorideal = healthdamage * armorblock;
1849 if(armordamage < healthdamage)
1862 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1865 if (deathtype == DEATH_DROWN) // Why should armor help here...
1867 v_y = bound(0, damage * armorblock, a); // save
1868 v_x = bound(0, damage - v_y, damage); // take
1874 string getcurrentmod()
1878 m = cvar_string("fs_gamedir");
1879 n = tokenize_console(m);
1891 v = ReadShort() * 256; // note: this is signed
1892 v += ReadByte(); // note: this is unsigned
1895 vector ReadInt48_t()
1898 v_x = ReadInt24_t();
1899 v_y = ReadInt24_t();
1903 vector ReadInt72_t()
1906 v_x = ReadInt24_t();
1907 v_y = ReadInt24_t();
1908 v_z = ReadInt24_t();
1912 void WriteInt24_t(float dst, float val)
1915 WriteShort(dst, (v = floor(val / 256)));
1916 WriteByte(dst, val - v * 256); // 0..255
1918 void WriteInt48_t(float dst, vector val)
1920 WriteInt24_t(dst, val_x);
1921 WriteInt24_t(dst, val_y);
1923 void WriteInt72_t(float dst, vector val)
1925 WriteInt24_t(dst, val_x);
1926 WriteInt24_t(dst, val_y);
1927 WriteInt24_t(dst, val_z);
1932 float float2range11(float f)
1934 // continuous function mapping all reals into -1..1
1935 return f / (fabs(f) + 1);
1938 float float2range01(float f)
1940 // continuous function mapping all reals into 0..1
1941 return 0.5 + 0.5 * float2range11(f);
1944 // from the GNU Scientific Library
1945 float gsl_ran_gaussian_lastvalue;
1946 float gsl_ran_gaussian_lastvalue_set;
1947 float gsl_ran_gaussian(float sigma)
1950 if(gsl_ran_gaussian_lastvalue_set)
1952 gsl_ran_gaussian_lastvalue_set = 0;
1953 return sigma * gsl_ran_gaussian_lastvalue;
1957 a = random() * 2 * M_PI;
1958 b = sqrt(-2 * log(random()));
1959 gsl_ran_gaussian_lastvalue = cos(a) * b;
1960 gsl_ran_gaussian_lastvalue_set = 1;
1961 return sigma * sin(a) * b;
1965 string car(string s)
1968 o = strstrofs(s, " ", 0);
1971 return substring(s, 0, o);
1973 string cdr(string s)
1976 o = strstrofs(s, " ", 0);
1979 return substring(s, o + 1, strlen(s) - (o + 1));
1981 float matchacl(string acl, string str)
1988 t = car(acl); acl = cdr(acl);
1991 if(substring(t, 0, 1) == "-")
1994 t = substring(t, 1, strlen(t) - 1);
1996 else if(substring(t, 0, 1) == "+")
1997 t = substring(t, 1, strlen(t) - 1);
1999 if(substring(t, -1, 1) == "*")
2001 t = substring(t, 0, strlen(t) - 1);
2002 s = substring(str, 0, strlen(t));
2014 float startsWith(string haystack, string needle)
2016 return substring(haystack, 0, strlen(needle)) == needle;
2018 float startsWithNocase(string haystack, string needle)
2020 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2023 string get_model_datafilename(string m, float sk, string fil)
2028 m = "models/player/*_";
2030 m = strcat(m, ftos(sk));
2033 return strcat(m, ".", fil);
2036 float get_model_parameters(string m, float sk)
2041 get_model_parameters_modelname = string_null;
2042 get_model_parameters_modelskin = -1;
2043 get_model_parameters_name = string_null;
2044 get_model_parameters_species = -1;
2045 get_model_parameters_sex = string_null;
2046 get_model_parameters_weight = -1;
2047 get_model_parameters_age = -1;
2048 get_model_parameters_desc = string_null;
2049 get_model_parameters_bone_upperbody = string_null;
2050 get_model_parameters_bone_weapon = string_null;
2051 for(i = 0; i < MAX_AIM_BONES; ++i)
2053 get_model_parameters_bone_aim[i] = string_null;
2054 get_model_parameters_bone_aimweight[i] = 0;
2056 get_model_parameters_fixbone = 0;
2061 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2062 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2066 if(substring(m, -4, -1) != ".txt")
2068 if(substring(m, -6, 1) != "_")
2070 sk = stof(substring(m, -5, 1));
2071 m = substring(m, 0, -7);
2074 fn = get_model_datafilename(m, sk, "txt");
2075 fh = fopen(fn, FILE_READ);
2079 fn = get_model_datafilename(m, sk, "txt");
2080 fh = fopen(fn, FILE_READ);
2085 get_model_parameters_modelname = m;
2086 get_model_parameters_modelskin = sk;
2087 while((s = fgets(fh)))
2090 break; // next lines will be description
2094 get_model_parameters_name = s;
2098 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2099 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2100 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2101 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2102 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2103 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2104 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2107 get_model_parameters_sex = s;
2109 get_model_parameters_weight = stof(s);
2111 get_model_parameters_age = stof(s);
2112 if(c == "description")
2113 get_model_parameters_description = s;
2114 if(c == "bone_upperbody")
2115 get_model_parameters_bone_upperbody = s;
2116 if(c == "bone_weapon")
2117 get_model_parameters_bone_weapon = s;
2118 for(i = 0; i < MAX_AIM_BONES; ++i)
2119 if(c == strcat("bone_aim", ftos(i)))
2121 get_model_parameters_bone_aimweight[i] = stof(car(s));
2122 get_model_parameters_bone_aim[i] = cdr(s);
2125 get_model_parameters_fixbone = stof(s);
2128 while((s = fgets(fh)))
2130 if(get_model_parameters_desc)
2131 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2133 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2141 vector vec2(vector v)
2148 vector NearestPointOnBox(entity box, vector org)
2150 vector m1, m2, nearest;
2152 m1 = box.mins + box.origin;
2153 m2 = box.maxs + box.origin;
2155 nearest_x = bound(m1_x, org_x, m2_x);
2156 nearest_y = bound(m1_y, org_y, m2_y);
2157 nearest_z = bound(m1_z, org_z, m2_z);
2163 float vercmp_recursive(string v1, string v2)
2169 dot1 = strstrofs(v1, ".", 0);
2170 dot2 = strstrofs(v2, ".", 0);
2174 s1 = substring(v1, 0, dot1);
2178 s2 = substring(v2, 0, dot2);
2180 r = stof(s1) - stof(s2);
2184 r = strcasecmp(s1, s2);
2197 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2200 float vercmp(string v1, string v2)
2202 if(strcasecmp(v1, v2) == 0) // early out check
2211 return vercmp_recursive(v1, v2);
2214 float u8_strsize(string s)
2234 // translation helpers
2235 string language_filename(string s)
2240 if(fn == "" || fn == "dump")
2242 fn = strcat(s, ".", fn);
2243 if((fh = fopen(fn, FILE_READ)) >= 0)
2250 string CTX(string s)
2252 float p = strstrofs(s, "^", 0);
2255 return substring(s, p+1, -1);
2258 // x-encoding (encoding as zero length invisible string)
2259 const string XENCODE_2 = "xX";
2260 const string XENCODE_22 = "0123456789abcdefABCDEF";
2261 string xencode(float f)
2264 d = mod(f, 22); f = floor(f / 22);
2265 c = mod(f, 22); f = floor(f / 22);
2266 b = mod(f, 22); f = floor(f / 22);
2267 a = mod(f, 2); // f = floor(f / 2);
2270 substring(XENCODE_2, a, 1),
2271 substring(XENCODE_22, b, 1),
2272 substring(XENCODE_22, c, 1),
2273 substring(XENCODE_22, d, 1)
2276 float xdecode(string s)
2279 if(substring(s, 0, 1) != "^")
2283 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2284 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2285 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2286 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2287 if(a < 0 || b < 0 || c < 0 || d < 0)
2289 return ((a * 22 + b) * 22 + c) * 22 + d;
2292 float lowestbit(float f)
2303 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2305 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2308 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2311 // escape the string to make it safe for consoles
2312 string MakeConsoleSafe(string input)
2314 input = strreplace("\n", "", input);
2315 input = strreplace("\\", "\\\\", input);
2316 input = strreplace("$", "$$", input);
2317 input = strreplace("\"", "\\\"", input);
2322 // get true/false value of a string with multiple different inputs
2323 float InterpretBoolean(string input)
2325 switch(strtolower(input))
2337 default: return stof(input);
2343 entity ReadCSQCEntity()
2349 return findfloat(world, entnum, f);
2353 float shutdown_running;
2358 void CSQC_Shutdown()
2364 if(shutdown_running)
2366 print("Recursive shutdown detected! Only restoring cvars...\n");
2370 shutdown_running = 1;
2373 cvar_settemp_restore(); // this must be done LAST, but in any case
2376 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2377 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2378 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2379 // this will use the value:
2381 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2382 // accuracy at x is 1/derivative, i.e.
2383 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2385 void WriteApproxPastTime(float dst, float t)
2387 float dt = time - t;
2389 // warning: this is approximate; do not resend when you don't have to!
2390 // be careful with sendflags here!
2391 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2394 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2397 dt = rint(bound(0, dt, 255));
2403 float ReadApproxPastTime()
2405 float dt = ReadByte();
2407 // map from range...PPROXPASTTIME_MAX / 256
2408 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2410 return servertime - dt;
2415 .float skeleton_bones_index;
2416 void Skeleton_SetBones(entity e)
2418 // set skeleton_bones to the total number of bones on the model
2419 if(e.skeleton_bones_index == e.modelindex)
2420 return; // same model, nothing to update
2423 skelindex = skel_create(e.modelindex);
2424 e.skeleton_bones = skel_get_numbones(skelindex);
2425 skel_delete(skelindex);
2426 e.skeleton_bones_index = e.modelindex;
2430 string to_execute_next_frame;
2431 void execute_next_frame()
2433 if(to_execute_next_frame)
2435 localcmd("\n", to_execute_next_frame, "\n");
2436 strunzone(to_execute_next_frame);
2437 to_execute_next_frame = string_null;
2440 void queue_to_execute_next_frame(string s)
2442 if(to_execute_next_frame)
2444 s = strcat(s, "\n", to_execute_next_frame);
2445 strunzone(to_execute_next_frame);
2447 to_execute_next_frame = strzone(s);
2450 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2453 ((( startspeedfactor + endspeedfactor - 2
2454 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2455 ) * x + startspeedfactor
2459 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2461 if(startspeedfactor < 0 || endspeedfactor < 0)
2465 // if this is the case, the possible zeros of the first derivative are outside
2467 We can calculate this condition as condition
2472 // better, see below:
2473 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2476 // if this is the case, the first derivative has no zeros at all
2477 float se = startspeedfactor + endspeedfactor;
2478 float s_e = startspeedfactor - endspeedfactor;
2479 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2482 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2483 // we also get s_e <= 6 - se
2484 // 3 * (se - 4)^2 + (6 - se)^2
2485 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2486 // Therefore, above "better" check works!
2490 // known good cases:
2498 // (3.5, [0.2..2.3])
2503 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2505 s + e - 2 == 0: no inflection
2508 0 < inflection < 1 if:
2509 0 < 2s + e - 3 < 3s + 3e - 6
2510 2s + e > 3 and 2e + s > 3
2513 0 < inflection < 1 if:
2514 0 > 2s + e - 3 > 3s + 3e - 6
2515 2s + e < 3 and 2e + s < 3
2517 Therefore: there is an inflection point iff:
2518 e outside (3 - s)/2 .. 3 - s*2
2520 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)
2524 .float FindConnectedComponent_processing;
2525 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2527 entity queue_start, queue_end;
2529 // we build a queue of to-be-processed entities.
2530 // queue_start is the next entity to be checked for neighbors
2531 // queue_end is the last entity added
2533 if(e.FindConnectedComponent_processing)
2534 error("recursion or broken cleanup");
2536 // start with a 1-element queue
2537 queue_start = queue_end = e;
2538 queue_end.fld = world;
2539 queue_end.FindConnectedComponent_processing = 1;
2541 // for each queued item:
2542 for(; queue_start; queue_start = queue_start.fld)
2544 // find all neighbors of queue_start
2546 for(t = world; (t = nxt(t, queue_start, pass)); )
2548 if(t.FindConnectedComponent_processing)
2550 if(iscon(t, queue_start, pass))
2552 // it is connected? ADD IT. It will look for neighbors soon too.
2555 queue_end.fld = world;
2556 queue_end.FindConnectedComponent_processing = 1;
2562 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2563 queue_start.FindConnectedComponent_processing = 0;
2567 vector combine_to_vector(float x, float y, float z)
2569 vector result; result_x = x; result_y = y; result_z = z;
2573 vector get_corner_position(entity box, float corner)
2577 case 1: return combine_to_vector(box.absmin_x, box.absmin_y, box.absmin_z);
2578 case 2: return combine_to_vector(box.absmax_x, box.absmin_y, box.absmin_z);
2579 case 3: return combine_to_vector(box.absmin_x, box.absmax_y, box.absmin_z);
2580 case 4: return combine_to_vector(box.absmin_x, box.absmin_y, box.absmax_z);
2581 case 5: return combine_to_vector(box.absmax_x, box.absmax_y, box.absmin_z);
2582 case 6: return combine_to_vector(box.absmin_x, box.absmax_y, box.absmax_z);
2583 case 7: return combine_to_vector(box.absmax_x, box.absmin_y, box.absmax_z);
2584 case 8: return combine_to_vector(box.absmax_x, box.absmax_y, box.absmax_z);
2585 default: return '0 0 0';
2590 // todo: this sucks, lets find a better way to do backtraces?
2591 void backtrace(string msg)
2595 dev = autocvar_developer;
2596 war = autocvar_prvm_backtraceforwarnings;
2598 dev = cvar("developer");
2599 war = cvar("prvm_backtraceforwarnings");
2601 cvar_set("developer", "1");
2602 cvar_set("prvm_backtraceforwarnings", "1");
2604 print("--- CUT HERE ---\nWARNING: ");
2607 remove(world); // isn't there any better way to cause a backtrace?
2608 print("\n--- CUT UNTIL HERE ---\n");
2609 cvar_set("developer", ftos(dev));
2610 cvar_set("prvm_backtraceforwarnings", ftos(war));
2613 // color code replace, place inside of sprintf and parse the string
2614 string CCR(string input)
2616 // See the autocvar declarations in util.qh for default values
2618 // foreground/normal colors
2619 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2620 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2621 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2622 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2625 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2626 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2627 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2629 // background colors
2630 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2631 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2635 vector vec3(float x, float y, float z)
2645 vector animfixfps(entity e, vector a, vector b)
2647 // multi-frame anim: keep as-is
2651 dur = frameduration(e.modelindex, a_x);
2655 dur = frameduration(e.modelindex, a_x);
2665 void dedicated_print(string input) // print(), but only print if the server is not local
2667 if(server_is_dedicated) { print(input); }
2672 float Announcer_PickNumber(float type, float num)
2680 case 10: return ANNCE_NUM_GAMESTART_10;
2681 case 9: return ANNCE_NUM_GAMESTART_9;
2682 case 8: return ANNCE_NUM_GAMESTART_8;
2683 case 7: return ANNCE_NUM_GAMESTART_7;
2684 case 6: return ANNCE_NUM_GAMESTART_6;
2685 case 5: return ANNCE_NUM_GAMESTART_5;
2686 case 4: return ANNCE_NUM_GAMESTART_4;
2687 case 3: return ANNCE_NUM_GAMESTART_3;
2688 case 2: return ANNCE_NUM_GAMESTART_2;
2689 case 1: return ANNCE_NUM_GAMESTART_1;
2697 case 10: return ANNCE_NUM_IDLE_10;
2698 case 9: return ANNCE_NUM_IDLE_9;
2699 case 8: return ANNCE_NUM_IDLE_8;
2700 case 7: return ANNCE_NUM_IDLE_7;
2701 case 6: return ANNCE_NUM_IDLE_6;
2702 case 5: return ANNCE_NUM_IDLE_5;
2703 case 4: return ANNCE_NUM_IDLE_4;
2704 case 3: return ANNCE_NUM_IDLE_3;
2705 case 2: return ANNCE_NUM_IDLE_2;
2706 case 1: return ANNCE_NUM_IDLE_1;
2714 case 10: return ANNCE_NUM_KILL_10;
2715 case 9: return ANNCE_NUM_KILL_9;
2716 case 8: return ANNCE_NUM_KILL_8;
2717 case 7: return ANNCE_NUM_KILL_7;
2718 case 6: return ANNCE_NUM_KILL_6;
2719 case 5: return ANNCE_NUM_KILL_5;
2720 case 4: return ANNCE_NUM_KILL_4;
2721 case 3: return ANNCE_NUM_KILL_3;
2722 case 2: return ANNCE_NUM_KILL_2;
2723 case 1: return ANNCE_NUM_KILL_1;
2731 case 10: return ANNCE_NUM_RESPAWN_10;
2732 case 9: return ANNCE_NUM_RESPAWN_9;
2733 case 8: return ANNCE_NUM_RESPAWN_8;
2734 case 7: return ANNCE_NUM_RESPAWN_7;
2735 case 6: return ANNCE_NUM_RESPAWN_6;
2736 case 5: return ANNCE_NUM_RESPAWN_5;
2737 case 4: return ANNCE_NUM_RESPAWN_4;
2738 case 3: return ANNCE_NUM_RESPAWN_3;
2739 case 2: return ANNCE_NUM_RESPAWN_2;
2740 case 1: return ANNCE_NUM_RESPAWN_1;
2744 case CNT_ROUNDSTART:
2748 case 10: return ANNCE_NUM_ROUNDSTART_10;
2749 case 9: return ANNCE_NUM_ROUNDSTART_9;
2750 case 8: return ANNCE_NUM_ROUNDSTART_8;
2751 case 7: return ANNCE_NUM_ROUNDSTART_7;
2752 case 6: return ANNCE_NUM_ROUNDSTART_6;
2753 case 5: return ANNCE_NUM_ROUNDSTART_5;
2754 case 4: return ANNCE_NUM_ROUNDSTART_4;
2755 case 3: return ANNCE_NUM_ROUNDSTART_3;
2756 case 2: return ANNCE_NUM_ROUNDSTART_2;
2757 case 1: return ANNCE_NUM_ROUNDSTART_1;
2765 case 10: return ANNCE_NUM_10;
2766 case 9: return ANNCE_NUM_9;
2767 case 8: return ANNCE_NUM_8;
2768 case 7: return ANNCE_NUM_7;
2769 case 6: return ANNCE_NUM_6;
2770 case 5: return ANNCE_NUM_5;
2771 case 4: return ANNCE_NUM_4;
2772 case 3: return ANNCE_NUM_3;
2773 case 2: return ANNCE_NUM_2;
2774 case 1: return ANNCE_NUM_1;
2779 return NOTIF_ABORT; // abort sending if none of these numbers were right
2784 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
2786 switch(nativecontents)
2791 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2793 return DPCONTENTS_WATER;
2795 return DPCONTENTS_SLIME;
2797 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2799 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2804 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
2806 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2807 return CONTENT_SOLID;
2808 if(supercontents & DPCONTENTS_SKY)
2810 if(supercontents & DPCONTENTS_LAVA)
2811 return CONTENT_LAVA;
2812 if(supercontents & DPCONTENTS_SLIME)
2813 return CONTENT_SLIME;
2814 if(supercontents & DPCONTENTS_WATER)
2815 return CONTENT_WATER;
2816 return CONTENT_EMPTY;
2820 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2823 (c - 2 * b + a) * (t * t) +
2828 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2831 (c - 2 * b + a) * (2 * t) +