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 const float 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 = 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 = 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(int 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)
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 float lerr = (x - lengthLogTable[l]);
529 float rerr = (lengthLogTable[r] - x);
535 vector decompressShortVector(int data)
540 float p = (data & 0xF000) / 0x1000;
541 float y = (data & 0x0F80) / 0x80;
542 int len = (data & 0x007F);
544 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
557 y = .19634954084936207740 * y;
558 p = .19634954084936207740 * p - 1.57079632679489661922;
559 out_x = cos(y) * cos(p);
560 out_y = sin(y) * cos(p);
564 //print("decompressed: ", vtos(out), "\n");
566 return out * lengthLogTable[len];
569 float compressShortVector(vector vec)
575 //print("compress: ", vtos(vec), "\n");
576 ang = vectoangles(vec);
580 if(ang.x < -90 && ang.x > +90)
581 error("BOGUS vectoangles");
582 //print("angles: ", vtos(ang), "\n");
584 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
593 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
594 len = invertLengthLog(vlen(vec));
596 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
598 return (p * 0x1000) + (y * 0x80) + len;
601 void compressShortVector_init()
604 float f = pow(2, 1/8);
606 for(i = 0; i < 128; ++i)
608 lengthLogTable[i] = l;
612 if(cvar("developer"))
614 print("Verifying vector compression table...\n");
615 for(i = 0x0F00; i < 0xFFFF; ++i)
616 if(i != compressShortVector(decompressShortVector(i)))
618 print("BROKEN vector compression: ", ftos(i));
619 print(" -> ", vtos(decompressShortVector(i)));
620 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
629 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
631 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
632 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
633 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
634 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
635 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
636 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
637 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
638 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
639 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
640 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
641 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
642 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
647 string fixPriorityList(string order, float from, float to, float subtract, float complete)
652 n = tokenize_console(order);
654 for(i = 0; i < n; ++i)
659 if(w >= from && w <= to)
660 neworder = strcat(neworder, ftos(w), " ");
664 if(w >= from && w <= to)
665 neworder = strcat(neworder, ftos(w), " ");
672 n = tokenize_console(neworder);
673 for(w = to; w >= from; --w)
675 for(i = 0; i < n; ++i)
676 if(stof(argv(i)) == w)
678 if(i == n) // not found
679 neworder = strcat(neworder, ftos(w), " ");
683 return substring(neworder, 0, strlen(neworder) - 1);
686 string mapPriorityList(string order, string(string) mapfunc)
691 n = tokenize_console(order);
693 for(i = 0; i < n; ++i)
694 neworder = strcat(neworder, mapfunc(argv(i)), " ");
696 return substring(neworder, 0, strlen(neworder) - 1);
699 string swapInPriorityList(string order, float i, float j)
704 n = tokenize_console(order);
706 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
709 for(w = 0; w < n; ++w)
712 s = strcat(s, argv(j), " ");
714 s = strcat(s, argv(i), " ");
716 s = strcat(s, argv(w), " ");
718 return substring(s, 0, strlen(s) - 1);
724 float cvar_value_issafe(string s)
726 if(strstrofs(s, "\"", 0) >= 0)
728 if(strstrofs(s, "\\", 0) >= 0)
730 if(strstrofs(s, ";", 0) >= 0)
732 if(strstrofs(s, "$", 0) >= 0)
734 if(strstrofs(s, "\r", 0) >= 0)
736 if(strstrofs(s, "\n", 0) >= 0)
742 void get_mi_min_max(float mode)
747 strunzone(mi_shortname);
748 mi_shortname = mapname;
749 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
750 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
751 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
752 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
753 mi_shortname = strzone(mi_shortname);
765 MapInfo_Get_ByName(mi_shortname, 0, 0);
766 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
768 mi_min = MapInfo_Map_mins;
769 mi_max = MapInfo_Map_maxs;
777 tracebox('1 0 0' * mi.x,
778 '0 1 0' * mi.y + '0 0 1' * mi.z,
779 '0 1 0' * ma.y + '0 0 1' * ma.z,
783 if(!trace_startsolid)
784 mi_min_x = trace_endpos.x;
786 tracebox('0 1 0' * mi.y,
787 '1 0 0' * mi.x + '0 0 1' * mi.z,
788 '1 0 0' * ma.x + '0 0 1' * ma.z,
792 if(!trace_startsolid)
793 mi_min_y = trace_endpos.y;
795 tracebox('0 0 1' * mi.z,
796 '1 0 0' * mi.x + '0 1 0' * mi.y,
797 '1 0 0' * ma.x + '0 1 0' * ma.y,
801 if(!trace_startsolid)
802 mi_min_z = trace_endpos.z;
804 tracebox('1 0 0' * ma.x,
805 '0 1 0' * mi.y + '0 0 1' * mi.z,
806 '0 1 0' * ma.y + '0 0 1' * ma.z,
810 if(!trace_startsolid)
811 mi_max_x = trace_endpos.x;
813 tracebox('0 1 0' * ma.y,
814 '1 0 0' * mi.x + '0 0 1' * mi.z,
815 '1 0 0' * ma.x + '0 0 1' * ma.z,
819 if(!trace_startsolid)
820 mi_max_y = trace_endpos.y;
822 tracebox('0 0 1' * ma.z,
823 '1 0 0' * mi.x + '0 1 0' * mi.y,
824 '1 0 0' * ma.x + '0 1 0' * ma.y,
828 if(!trace_startsolid)
829 mi_max_z = trace_endpos.z;
834 void get_mi_min_max_texcoords(float mode)
838 get_mi_min_max(mode);
843 // extend mi_picmax to get a square aspect ratio
844 // center the map in that area
845 extend = mi_picmax - mi_picmin;
846 if(extend.y > extend.x)
848 mi_picmin.x -= (extend.y - extend.x) * 0.5;
849 mi_picmax.x += (extend.y - extend.x) * 0.5;
853 mi_picmin.y -= (extend.x - extend.y) * 0.5;
854 mi_picmax.y += (extend.x - extend.y) * 0.5;
857 // add another some percent
858 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
862 // calculate the texcoords
863 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
864 // first the two corners of the origin
865 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
866 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
867 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
868 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
869 // then the other corners
870 mi_pictexcoord1_x = mi_pictexcoord0_x;
871 mi_pictexcoord1_y = mi_pictexcoord2_y;
872 mi_pictexcoord3_x = mi_pictexcoord2_x;
873 mi_pictexcoord3_y = mi_pictexcoord0_y;
877 float cvar_settemp(string tmp_cvar, string tmp_value)
879 float created_saved_value;
882 created_saved_value = 0;
884 if (!(tmp_cvar || tmp_value))
886 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
890 if(!cvar_type(tmp_cvar))
892 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
896 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
897 if(e.netname == tmp_cvar)
898 created_saved_value = -1; // skip creation
900 if(created_saved_value != -1)
902 // creating a new entity to keep track of this cvar
904 e.classname = "saved_cvar_value";
905 e.netname = strzone(tmp_cvar);
906 e.message = strzone(cvar_string(tmp_cvar));
907 created_saved_value = 1;
910 // update the cvar to the value given
911 cvar_set(tmp_cvar, tmp_value);
913 return created_saved_value;
916 float cvar_settemp_restore()
920 while((e = find(e, classname, "saved_cvar_value")))
922 if(cvar_type(e.netname))
924 cvar_set(e.netname, e.message);
929 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
935 float almost_equals(float a, float b)
938 eps = (max(a, -a) + max(b, -b)) * 0.001;
939 if(a - b < eps && b - a < eps)
944 float almost_in_bounds(float a, float b, float c)
947 eps = (max(a, -a) + max(c, -c)) * 0.001;
950 return b == median(a - eps, b, c + eps);
953 float power2of(float e)
957 float log2of(float x)
959 // NOTE: generated code
1032 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1036 else if(ma == rgb.x)
1039 return (rgb.y - rgb.z) / (ma - mi);
1041 return (rgb.y - rgb.z) / (ma - mi) + 6;
1043 else if(ma == rgb.y)
1044 return (rgb.z - rgb.x) / (ma - mi) + 2;
1045 else // if(ma == rgb_z)
1046 return (rgb.x - rgb.y) / (ma - mi) + 4;
1049 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1053 hue -= 6 * floor(hue / 6);
1055 //else if(ma == rgb_x)
1056 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1060 rgb_y = hue * (ma - mi) + mi;
1063 //else if(ma == rgb_y)
1064 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1067 rgb_x = (2 - hue) * (ma - mi) + mi;
1075 rgb_z = (hue - 2) * (ma - mi) + mi;
1077 //else // if(ma == rgb_z)
1078 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1082 rgb_y = (4 - hue) * (ma - mi) + mi;
1087 rgb_x = (hue - 4) * (ma - mi) + mi;
1091 //else if(ma == rgb_x)
1092 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1093 else // if(hue <= 6)
1097 rgb_z = (6 - hue) * (ma - mi) + mi;
1103 vector rgb_to_hsv(vector rgb)
1108 mi = min(rgb.x, rgb.y, rgb.z);
1109 ma = max(rgb.x, rgb.y, rgb.z);
1111 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1122 vector hsv_to_rgb(vector hsv)
1124 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1127 vector rgb_to_hsl(vector rgb)
1132 mi = min(rgb.x, rgb.y, rgb.z);
1133 ma = max(rgb.x, rgb.y, rgb.z);
1135 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1137 hsl_z = 0.5 * (mi + ma);
1140 else if(hsl.z <= 0.5)
1141 hsl_y = (ma - mi) / (2*hsl.z);
1142 else // if(hsl_z > 0.5)
1143 hsl_y = (ma - mi) / (2 - 2*hsl.z);
1148 vector hsl_to_rgb(vector hsl)
1150 float mi, ma, maminusmi;
1153 maminusmi = hsl.y * 2 * hsl.z;
1155 maminusmi = hsl.y * (2 - 2 * hsl.z);
1157 // hsl_z = 0.5 * mi + 0.5 * ma
1158 // maminusmi = - mi + ma
1159 mi = hsl.z - 0.5 * maminusmi;
1160 ma = hsl.z + 0.5 * maminusmi;
1162 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1165 string rgb_to_hexcolor(vector rgb)
1170 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1171 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1172 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1176 // requires that m2>m1 in all coordinates, and that m4>m3
1177 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;}
1179 // requires the same, but is a stronger condition
1180 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;}
1185 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1188 // The following function is SLOW.
1189 // For your safety and for the protection of those around you...
1190 // DO NOT CALL THIS AT HOME.
1191 // No really, don't.
1192 if(w(theText, theSize) <= maxWidth)
1193 return strlen(theText); // yeah!
1195 // binary search for right place to cut string
1197 float left, right, middle; // this always works
1199 right = strlen(theText); // this always fails
1202 middle = floor((left + right) / 2);
1203 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1208 while(left < right - 1);
1210 if(w("^7", theSize) == 0) // detect color codes support in the width function
1212 // NOTE: when color codes are involved, this binary search is,
1213 // mathematically, BROKEN. However, it is obviously guaranteed to
1214 // terminate, as the range still halves each time - but nevertheless, it is
1215 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1216 // range, and "right" is outside).
1218 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1219 // and decrease left on the basis of the chars detected of the truncated tag
1220 // Even if the ^xrgb tag is not complete/correct, left is decreased
1221 // (sometimes too much but with a correct result)
1222 // it fixes also ^[0-9]
1223 while(left >= 1 && substring(theText, left-1, 1) == "^")
1226 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1228 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1230 ch = str2chr(theText, left-1);
1231 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1234 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1236 ch = str2chr(theText, left-2);
1237 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1239 ch = str2chr(theText, left-1);
1240 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1249 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1252 // The following function is SLOW.
1253 // For your safety and for the protection of those around you...
1254 // DO NOT CALL THIS AT HOME.
1255 // No really, don't.
1256 if(w(theText) <= maxWidth)
1257 return strlen(theText); // yeah!
1259 // binary search for right place to cut string
1261 float left, right, middle; // this always works
1263 right = strlen(theText); // this always fails
1266 middle = floor((left + right) / 2);
1267 if(w(substring(theText, 0, middle)) <= maxWidth)
1272 while(left < right - 1);
1274 if(w("^7") == 0) // detect color codes support in the width function
1276 // NOTE: when color codes are involved, this binary search is,
1277 // mathematically, BROKEN. However, it is obviously guaranteed to
1278 // terminate, as the range still halves each time - but nevertheless, it is
1279 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1280 // range, and "right" is outside).
1282 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1283 // and decrease left on the basis of the chars detected of the truncated tag
1284 // Even if the ^xrgb tag is not complete/correct, left is decreased
1285 // (sometimes too much but with a correct result)
1286 // it fixes also ^[0-9]
1287 while(left >= 1 && substring(theText, left-1, 1) == "^")
1290 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1292 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1294 ch = str2chr(theText, left-1);
1295 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1298 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1300 ch = str2chr(theText, left-2);
1301 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1303 ch = str2chr(theText, left-1);
1304 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1313 string find_last_color_code(string s)
1315 int start = strstrofs(s, "^", 0);
1316 if (start == -1) // no caret found
1318 int len = strlen(s)-1;
1320 for(i = len; i >= start; --i)
1322 if(substring(s, i, 1) != "^")
1326 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1329 // check if carets aren't all escaped
1333 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1334 return substring(s, i, 2);
1337 if(substring(s, i+1, 1) == "x")
1338 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1339 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1340 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1341 return substring(s, i, 5);
1343 i -= carets; // this also skips one char before the carets
1349 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1355 s = getWrappedLine_remaining;
1359 getWrappedLine_remaining = string_null;
1360 return s; // the line has no size ANYWAY, nothing would be displayed.
1363 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1364 if(cantake > 0 && cantake < strlen(s))
1367 while(take > 0 && substring(s, take, 1) != " ")
1371 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1372 if(getWrappedLine_remaining == "")
1373 getWrappedLine_remaining = string_null;
1374 else if (tw("^7", theFontSize) == 0)
1375 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1376 return substring(s, 0, cantake);
1380 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1381 if(getWrappedLine_remaining == "")
1382 getWrappedLine_remaining = string_null;
1383 else if (tw("^7", theFontSize) == 0)
1384 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1385 return substring(s, 0, take);
1390 getWrappedLine_remaining = string_null;
1395 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1401 s = getWrappedLine_remaining;
1405 getWrappedLine_remaining = string_null;
1406 return s; // the line has no size ANYWAY, nothing would be displayed.
1409 cantake = textLengthUpToLength(s, w, tw);
1410 if(cantake > 0 && cantake < strlen(s))
1413 while(take > 0 && substring(s, take, 1) != " ")
1417 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1418 if(getWrappedLine_remaining == "")
1419 getWrappedLine_remaining = string_null;
1420 else if (tw("^7") == 0)
1421 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1422 return substring(s, 0, cantake);
1426 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1427 if(getWrappedLine_remaining == "")
1428 getWrappedLine_remaining = string_null;
1429 else if (tw("^7") == 0)
1430 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1431 return substring(s, 0, take);
1436 getWrappedLine_remaining = string_null;
1441 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1443 if(tw(theText, theFontSize) <= maxWidth)
1446 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1449 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1451 if(tw(theText) <= maxWidth)
1454 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1457 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1459 string subpattern, subpattern2, subpattern3, subpattern4;
1460 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1462 subpattern2 = ",teams,";
1464 subpattern2 = ",noteams,";
1466 subpattern3 = ",teamspawns,";
1468 subpattern3 = ",noteamspawns,";
1469 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1470 subpattern4 = ",race,";
1472 subpattern4 = string_null;
1474 if(substring(pattern, 0, 1) == "-")
1476 pattern = substring(pattern, 1, strlen(pattern) - 1);
1477 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1479 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1481 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1483 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1488 if(substring(pattern, 0, 1) == "+")
1489 pattern = substring(pattern, 1, strlen(pattern) - 1);
1490 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1491 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1492 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1496 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1503 void shuffle(float n, swapfunc_t swap, entity pass)
1506 for(i = 1; i < n; ++i)
1508 // swap i-th item at a random position from 0 to i
1509 // proof for even distribution:
1512 // item n+1 gets at any position with chance 1/(n+1)
1513 // all others will get their 1/n chance reduced by factor n/(n+1)
1514 // to be on place n+1, their chance will be 1/(n+1)
1515 // 1/n * n/(n+1) = 1/(n+1)
1517 j = floor(random() * (i + 1));
1523 string substring_range(string s, float b, float e)
1525 return substring(s, b, e - b);
1528 string swapwords(string str, float i, float j)
1531 string s1, s2, s3, s4, s5;
1532 float si, ei, sj, ej, s0, en;
1533 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1534 si = argv_start_index(i);
1535 sj = argv_start_index(j);
1536 ei = argv_end_index(i);
1537 ej = argv_end_index(j);
1538 s0 = argv_start_index(0);
1539 en = argv_end_index(n-1);
1540 s1 = substring_range(str, s0, si);
1541 s2 = substring_range(str, si, ei);
1542 s3 = substring_range(str, ei, sj);
1543 s4 = substring_range(str, sj, ej);
1544 s5 = substring_range(str, ej, en);
1545 return strcat(s1, s4, s3, s2, s5);
1548 string _shufflewords_str;
1549 void _shufflewords_swapfunc(float i, float j, entity pass)
1551 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1553 string shufflewords(string str)
1556 _shufflewords_str = str;
1557 n = tokenizebyseparator(str, " ");
1558 shuffle(n, _shufflewords_swapfunc, world);
1559 str = _shufflewords_str;
1560 _shufflewords_str = string_null;
1564 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1580 // actually, every number solves the equation!
1591 if(a > 0) // put the smaller solution first
1593 v_x = ((-b)-D) / (2*a);
1594 v_y = ((-b)+D) / (2*a);
1598 v_x = (-b+D) / (2*a);
1599 v_y = (-b-D) / (2*a);
1605 // complex solutions!
1618 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1622 // make origin and speed relative
1627 // now solve for ret, ret normalized:
1628 // eorg + t * evel == t * ret * spd
1629 // or, rather, solve for t:
1630 // |eorg + t * evel| == t * spd
1631 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1632 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1633 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1634 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1635 // q = (eorg * eorg) / (evel * evel - spd * spd)
1636 if(!solution.z) // no real solution
1639 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1640 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1641 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1642 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1643 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1644 // spd < |evel| * sin angle(evel, eorg)
1647 else if(solution.x > 0)
1649 // both solutions > 0: take the smaller one
1650 // happens if p < 0 and q > 0
1651 ret = normalize(eorg + solution.x * evel);
1653 else if(solution.y > 0)
1655 // one solution > 0: take the larger one
1656 // happens if q < 0 or q == 0 and p < 0
1657 ret = normalize(eorg + solution.y * evel);
1661 // no solution > 0: reject
1662 // happens if p > 0 and q >= 0
1663 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1664 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1669 // "Enemy is moving away from me at more than spd"
1673 // NOTE: we always got a solution if spd > |evel|
1675 if(newton_style == 2)
1676 ret = normalize(ret * spd + myvel);
1681 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1686 if(newton_style == 2)
1688 // true Newtonian projectiles with automatic aim adjustment
1690 // solve: |outspeed * mydir - myvel| = spd
1691 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1692 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1696 // myvel^2 - (mydir * myvel)^2 > spd^2
1697 // velocity without mydir component > spd
1698 // fire at smallest possible spd that works?
1699 // |(mydir * myvel) * myvel - myvel| = spd
1701 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1705 outspeed = solution.y; // the larger one
1708 //outspeed = 0; // slowest possible shot
1709 outspeed = solution.x; // the real part (that is, the average!)
1710 //dprint("impossible shot, adjusting\n");
1713 outspeed = bound(spd * mi, outspeed, spd * ma);
1714 return mydir * outspeed;
1718 return myvel + spd * mydir;
1721 float compressShotOrigin(vector v)
1725 y = rint(v.y * 4) + 128;
1726 z = rint(v.z * 4) + 128;
1727 if(x > 255 || x < 0)
1729 print("shot origin ", vtos(v), " x out of bounds\n");
1730 x = bound(0, x, 255);
1732 if(y > 255 || y < 0)
1734 print("shot origin ", vtos(v), " y out of bounds\n");
1735 y = bound(0, y, 255);
1737 if(z > 255 || z < 0)
1739 print("shot origin ", vtos(v), " z out of bounds\n");
1740 z = bound(0, z, 255);
1742 return x * 0x10000 + y * 0x100 + z;
1744 vector decompressShotOrigin(int f)
1747 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1748 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1749 v_z = ((f & 0xFF) - 128) / 4;
1753 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1755 float start, end, root, child;
1758 start = floor((n - 2) / 2);
1761 // siftdown(start, count-1);
1763 while(root * 2 + 1 <= n-1)
1765 child = root * 2 + 1;
1767 if(cmp(child, child+1, pass) < 0)
1769 if(cmp(root, child, pass) < 0)
1771 swap(root, child, pass);
1787 // siftdown(0, end);
1789 while(root * 2 + 1 <= end)
1791 child = root * 2 + 1;
1792 if(child < end && cmp(child, child+1, pass) < 0)
1794 if(cmp(root, child, pass) < 0)
1796 swap(root, child, pass);
1806 void RandomSelection_Init()
1808 RandomSelection_totalweight = 0;
1809 RandomSelection_chosen_ent = world;
1810 RandomSelection_chosen_float = 0;
1811 RandomSelection_chosen_string = string_null;
1812 RandomSelection_best_priority = -1;
1814 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1816 if(priority > RandomSelection_best_priority)
1818 RandomSelection_best_priority = priority;
1819 RandomSelection_chosen_ent = e;
1820 RandomSelection_chosen_float = f;
1821 RandomSelection_chosen_string = s;
1822 RandomSelection_totalweight = weight;
1824 else if(priority == RandomSelection_best_priority)
1826 RandomSelection_totalweight += weight;
1827 if(random() * RandomSelection_totalweight <= weight)
1829 RandomSelection_chosen_ent = e;
1830 RandomSelection_chosen_float = f;
1831 RandomSelection_chosen_string = s;
1837 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1839 // NOTE: we'll always choose the SMALLER value...
1840 float healthdamage, armordamage, armorideal;
1841 if (deathtype == DEATH_DROWN) // Why should armor help here...
1844 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1845 armordamage = a + (h - 1); // damage we can take if we could use more armor
1846 armorideal = healthdamage * armorblock;
1848 if(armordamage < healthdamage)
1861 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1864 if (deathtype == DEATH_DROWN) // Why should armor help here...
1866 v_y = bound(0, damage * armorblock, a); // save
1867 v_x = bound(0, damage - v.y, damage); // take
1873 string getcurrentmod()
1877 m = cvar_string("fs_gamedir");
1878 n = tokenize_console(m);
1890 v = ReadShort() * 256; // note: this is signed
1891 v += ReadByte(); // note: this is unsigned
1894 vector ReadInt48_t()
1897 v_x = ReadInt24_t();
1898 v_y = ReadInt24_t();
1902 vector ReadInt72_t()
1905 v_x = ReadInt24_t();
1906 v_y = ReadInt24_t();
1907 v_z = ReadInt24_t();
1911 void WriteInt24_t(float dst, float val)
1914 WriteShort(dst, (v = floor(val / 256)));
1915 WriteByte(dst, val - v * 256); // 0..255
1917 void WriteInt48_t(float dst, vector val)
1919 WriteInt24_t(dst, val.x);
1920 WriteInt24_t(dst, val.y);
1922 void WriteInt72_t(float dst, vector val)
1924 WriteInt24_t(dst, val.x);
1925 WriteInt24_t(dst, val.y);
1926 WriteInt24_t(dst, val.z);
1931 float float2range11(float f)
1933 // continuous function mapping all reals into -1..1
1934 return f / (fabs(f) + 1);
1937 float float2range01(float f)
1939 // continuous function mapping all reals into 0..1
1940 return 0.5 + 0.5 * float2range11(f);
1943 // from the GNU Scientific Library
1944 float gsl_ran_gaussian_lastvalue;
1945 float gsl_ran_gaussian_lastvalue_set;
1946 float gsl_ran_gaussian(float sigma)
1949 if(gsl_ran_gaussian_lastvalue_set)
1951 gsl_ran_gaussian_lastvalue_set = 0;
1952 return sigma * gsl_ran_gaussian_lastvalue;
1956 a = random() * 2 * M_PI;
1957 b = sqrt(-2 * log(random()));
1958 gsl_ran_gaussian_lastvalue = cos(a) * b;
1959 gsl_ran_gaussian_lastvalue_set = 1;
1960 return sigma * sin(a) * b;
1964 string car(string s)
1967 o = strstrofs(s, " ", 0);
1970 return substring(s, 0, o);
1972 string cdr(string s)
1975 o = strstrofs(s, " ", 0);
1978 return substring(s, o + 1, strlen(s) - (o + 1));
1980 float matchacl(string acl, string str)
1987 t = car(acl); acl = cdr(acl);
1990 if(substring(t, 0, 1) == "-")
1993 t = substring(t, 1, strlen(t) - 1);
1995 else if(substring(t, 0, 1) == "+")
1996 t = substring(t, 1, strlen(t) - 1);
1998 if(substring(t, -1, 1) == "*")
2000 t = substring(t, 0, strlen(t) - 1);
2001 s = substring(str, 0, strlen(t));
2013 float startsWith(string haystack, string needle)
2015 return substring(haystack, 0, strlen(needle)) == needle;
2017 float startsWithNocase(string haystack, string needle)
2019 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2022 string get_model_datafilename(string m, float sk, string fil)
2027 m = "models/player/*_";
2029 m = strcat(m, ftos(sk));
2032 return strcat(m, ".", fil);
2035 float get_model_parameters(string m, float sk)
2040 get_model_parameters_modelname = string_null;
2041 get_model_parameters_modelskin = -1;
2042 get_model_parameters_name = string_null;
2043 get_model_parameters_species = -1;
2044 get_model_parameters_sex = string_null;
2045 get_model_parameters_weight = -1;
2046 get_model_parameters_age = -1;
2047 get_model_parameters_desc = string_null;
2048 get_model_parameters_bone_upperbody = string_null;
2049 get_model_parameters_bone_weapon = string_null;
2050 for(i = 0; i < MAX_AIM_BONES; ++i)
2052 get_model_parameters_bone_aim[i] = string_null;
2053 get_model_parameters_bone_aimweight[i] = 0;
2055 get_model_parameters_fixbone = 0;
2060 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2061 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2065 if(substring(m, -4, -1) != ".txt")
2067 if(substring(m, -6, 1) != "_")
2069 sk = stof(substring(m, -5, 1));
2070 m = substring(m, 0, -7);
2073 fn = get_model_datafilename(m, sk, "txt");
2074 fh = fopen(fn, FILE_READ);
2078 fn = get_model_datafilename(m, sk, "txt");
2079 fh = fopen(fn, FILE_READ);
2084 get_model_parameters_modelname = m;
2085 get_model_parameters_modelskin = sk;
2086 while((s = fgets(fh)))
2089 break; // next lines will be description
2093 get_model_parameters_name = s;
2097 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2098 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2099 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2100 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2101 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2102 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2103 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2106 get_model_parameters_sex = s;
2108 get_model_parameters_weight = stof(s);
2110 get_model_parameters_age = stof(s);
2111 if(c == "description")
2112 get_model_parameters_description = s;
2113 if(c == "bone_upperbody")
2114 get_model_parameters_bone_upperbody = s;
2115 if(c == "bone_weapon")
2116 get_model_parameters_bone_weapon = s;
2117 for(i = 0; i < MAX_AIM_BONES; ++i)
2118 if(c == strcat("bone_aim", ftos(i)))
2120 get_model_parameters_bone_aimweight[i] = stof(car(s));
2121 get_model_parameters_bone_aim[i] = cdr(s);
2124 get_model_parameters_fixbone = stof(s);
2127 while((s = fgets(fh)))
2129 if(get_model_parameters_desc)
2130 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2132 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2140 vector vec2(vector v)
2147 vector NearestPointOnBox(entity box, vector org)
2149 vector m1, m2, nearest;
2151 m1 = box.mins + box.origin;
2152 m2 = box.maxs + box.origin;
2154 nearest_x = bound(m1_x, org.x, m2_x);
2155 nearest_y = bound(m1_y, org.y, m2_y);
2156 nearest_z = bound(m1_z, org.z, m2_z);
2162 float vercmp_recursive(string v1, string v2)
2168 dot1 = strstrofs(v1, ".", 0);
2169 dot2 = strstrofs(v2, ".", 0);
2173 s1 = substring(v1, 0, dot1);
2177 s2 = substring(v2, 0, dot2);
2179 r = stof(s1) - stof(s2);
2183 r = strcasecmp(s1, s2);
2196 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2199 float vercmp(string v1, string v2)
2201 if(strcasecmp(v1, v2) == 0) // early out check
2210 return vercmp_recursive(v1, v2);
2213 float u8_strsize(string s)
2233 // translation helpers
2234 string language_filename(string s)
2239 if(fn == "" || fn == "dump")
2241 fn = strcat(s, ".", fn);
2242 if((fh = fopen(fn, FILE_READ)) >= 0)
2249 string CTX(string s)
2251 float p = strstrofs(s, "^", 0);
2254 return substring(s, p+1, -1);
2257 // x-encoding (encoding as zero length invisible string)
2258 const string XENCODE_2 = "xX";
2259 const string XENCODE_22 = "0123456789abcdefABCDEF";
2260 string xencode(int f)
2263 d = f % 22; f = floor(f / 22);
2264 c = f % 22; f = floor(f / 22);
2265 b = f % 22; f = floor(f / 22);
2266 a = f % 2; // f = floor(f / 2);
2269 substring(XENCODE_2, a, 1),
2270 substring(XENCODE_22, b, 1),
2271 substring(XENCODE_22, c, 1),
2272 substring(XENCODE_22, d, 1)
2275 float xdecode(string s)
2278 if(substring(s, 0, 1) != "^")
2282 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2283 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2284 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2285 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2286 if(a < 0 || b < 0 || c < 0 || d < 0)
2288 return ((a * 22 + b) * 22 + c) * 22 + d;
2291 float lowestbit(int f)
2302 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2304 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2307 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2310 // escape the string to make it safe for consoles
2311 string MakeConsoleSafe(string input)
2313 input = strreplace("\n", "", input);
2314 input = strreplace("\\", "\\\\", input);
2315 input = strreplace("$", "$$", input);
2316 input = strreplace("\"", "\\\"", input);
2321 // get true/false value of a string with multiple different inputs
2322 float InterpretBoolean(string input)
2324 switch(strtolower(input))
2336 default: return stof(input);
2342 entity ReadCSQCEntity()
2348 return findfloat(world, entnum, f);
2352 float shutdown_running;
2357 void CSQC_Shutdown()
2363 if(shutdown_running)
2365 print("Recursive shutdown detected! Only restoring cvars...\n");
2369 shutdown_running = 1;
2372 cvar_settemp_restore(); // this must be done LAST, but in any case
2375 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2376 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2377 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2378 // this will use the value:
2380 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2381 // accuracy at x is 1/derivative, i.e.
2382 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2384 void WriteApproxPastTime(float dst, float t)
2386 float dt = time - t;
2388 // warning: this is approximate; do not resend when you don't have to!
2389 // be careful with sendflags here!
2390 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2393 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2396 dt = rint(bound(0, dt, 255));
2402 float ReadApproxPastTime()
2404 float dt = ReadByte();
2406 // map from range...PPROXPASTTIME_MAX / 256
2407 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2409 return servertime - dt;
2414 .float skeleton_bones_index;
2415 void Skeleton_SetBones(entity e)
2417 // set skeleton_bones to the total number of bones on the model
2418 if(e.skeleton_bones_index == e.modelindex)
2419 return; // same model, nothing to update
2422 skelindex = skel_create(e.modelindex);
2423 e.skeleton_bones = skel_get_numbones(skelindex);
2424 skel_delete(skelindex);
2425 e.skeleton_bones_index = e.modelindex;
2429 string to_execute_next_frame;
2430 void execute_next_frame()
2432 if(to_execute_next_frame)
2434 localcmd("\n", to_execute_next_frame, "\n");
2435 strunzone(to_execute_next_frame);
2436 to_execute_next_frame = string_null;
2439 void queue_to_execute_next_frame(string s)
2441 if(to_execute_next_frame)
2443 s = strcat(s, "\n", to_execute_next_frame);
2444 strunzone(to_execute_next_frame);
2446 to_execute_next_frame = strzone(s);
2449 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2452 ((( startspeedfactor + endspeedfactor - 2
2453 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2454 ) * x + startspeedfactor
2458 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2460 if(startspeedfactor < 0 || endspeedfactor < 0)
2464 // if this is the case, the possible zeros of the first derivative are outside
2466 We can calculate this condition as condition
2471 // better, see below:
2472 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2475 // if this is the case, the first derivative has no zeros at all
2476 float se = startspeedfactor + endspeedfactor;
2477 float s_e = startspeedfactor - endspeedfactor;
2478 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2481 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2482 // we also get s_e <= 6 - se
2483 // 3 * (se - 4)^2 + (6 - se)^2
2484 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2485 // Therefore, above "better" check works!
2489 // known good cases:
2497 // (3.5, [0.2..2.3])
2502 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2504 s + e - 2 == 0: no inflection
2507 0 < inflection < 1 if:
2508 0 < 2s + e - 3 < 3s + 3e - 6
2509 2s + e > 3 and 2e + s > 3
2512 0 < inflection < 1 if:
2513 0 > 2s + e - 3 > 3s + 3e - 6
2514 2s + e < 3 and 2e + s < 3
2516 Therefore: there is an inflection point iff:
2517 e outside (3 - s)/2 .. 3 - s*2
2519 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)
2523 .float FindConnectedComponent_processing;
2524 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2526 entity queue_start, queue_end;
2528 // we build a queue of to-be-processed entities.
2529 // queue_start is the next entity to be checked for neighbors
2530 // queue_end is the last entity added
2532 if(e.FindConnectedComponent_processing)
2533 error("recursion or broken cleanup");
2535 // start with a 1-element queue
2536 queue_start = queue_end = e;
2537 queue_end.fld = world;
2538 queue_end.FindConnectedComponent_processing = 1;
2540 // for each queued item:
2541 for(0; queue_start; queue_start = queue_start.fld)
2543 // find all neighbors of queue_start
2545 for(t = world; (t = nxt(t, queue_start, pass)); )
2547 if(t.FindConnectedComponent_processing)
2549 if(iscon(t, queue_start, pass))
2551 // it is connected? ADD IT. It will look for neighbors soon too.
2554 queue_end.fld = world;
2555 queue_end.FindConnectedComponent_processing = 1;
2561 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2562 queue_start.FindConnectedComponent_processing = 0;
2566 vector combine_to_vector(float x, float y, float z)
2568 vector result; result_x = x; result_y = y; result_z = z;
2572 vector get_corner_position(entity box, float corner)
2576 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2577 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2578 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2579 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2580 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2581 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2582 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2583 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2584 default: return '0 0 0';
2589 // todo: this sucks, lets find a better way to do backtraces?
2590 void backtrace(string msg)
2594 dev = autocvar_developer;
2595 war = autocvar_prvm_backtraceforwarnings;
2597 dev = cvar("developer");
2598 war = cvar("prvm_backtraceforwarnings");
2600 cvar_set("developer", "1");
2601 cvar_set("prvm_backtraceforwarnings", "1");
2603 print("--- CUT HERE ---\nWARNING: ");
2606 remove(world); // isn't there any better way to cause a backtrace?
2607 print("\n--- CUT UNTIL HERE ---\n");
2608 cvar_set("developer", ftos(dev));
2609 cvar_set("prvm_backtraceforwarnings", ftos(war));
2612 // color code replace, place inside of sprintf and parse the string
2613 string CCR(string input)
2615 // See the autocvar declarations in util.qh for default values
2617 // foreground/normal colors
2618 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2619 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2620 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2621 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2624 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2625 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2626 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2628 // background colors
2629 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2630 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2634 vector vec3(float x, float y, float z)
2644 vector animfixfps(entity e, vector a, vector b)
2646 // multi-frame anim: keep as-is
2650 dur = frameduration(e.modelindex, a.x);
2654 dur = frameduration(e.modelindex, a.x);
2664 void dedicated_print(string input) // print(), but only print if the server is not local
2666 if(server_is_dedicated) { print(input); }
2671 float Announcer_PickNumber(float type, float num)
2679 case 10: return ANNCE_NUM_GAMESTART_10;
2680 case 9: return ANNCE_NUM_GAMESTART_9;
2681 case 8: return ANNCE_NUM_GAMESTART_8;
2682 case 7: return ANNCE_NUM_GAMESTART_7;
2683 case 6: return ANNCE_NUM_GAMESTART_6;
2684 case 5: return ANNCE_NUM_GAMESTART_5;
2685 case 4: return ANNCE_NUM_GAMESTART_4;
2686 case 3: return ANNCE_NUM_GAMESTART_3;
2687 case 2: return ANNCE_NUM_GAMESTART_2;
2688 case 1: return ANNCE_NUM_GAMESTART_1;
2696 case 10: return ANNCE_NUM_IDLE_10;
2697 case 9: return ANNCE_NUM_IDLE_9;
2698 case 8: return ANNCE_NUM_IDLE_8;
2699 case 7: return ANNCE_NUM_IDLE_7;
2700 case 6: return ANNCE_NUM_IDLE_6;
2701 case 5: return ANNCE_NUM_IDLE_5;
2702 case 4: return ANNCE_NUM_IDLE_4;
2703 case 3: return ANNCE_NUM_IDLE_3;
2704 case 2: return ANNCE_NUM_IDLE_2;
2705 case 1: return ANNCE_NUM_IDLE_1;
2713 case 10: return ANNCE_NUM_KILL_10;
2714 case 9: return ANNCE_NUM_KILL_9;
2715 case 8: return ANNCE_NUM_KILL_8;
2716 case 7: return ANNCE_NUM_KILL_7;
2717 case 6: return ANNCE_NUM_KILL_6;
2718 case 5: return ANNCE_NUM_KILL_5;
2719 case 4: return ANNCE_NUM_KILL_4;
2720 case 3: return ANNCE_NUM_KILL_3;
2721 case 2: return ANNCE_NUM_KILL_2;
2722 case 1: return ANNCE_NUM_KILL_1;
2730 case 10: return ANNCE_NUM_RESPAWN_10;
2731 case 9: return ANNCE_NUM_RESPAWN_9;
2732 case 8: return ANNCE_NUM_RESPAWN_8;
2733 case 7: return ANNCE_NUM_RESPAWN_7;
2734 case 6: return ANNCE_NUM_RESPAWN_6;
2735 case 5: return ANNCE_NUM_RESPAWN_5;
2736 case 4: return ANNCE_NUM_RESPAWN_4;
2737 case 3: return ANNCE_NUM_RESPAWN_3;
2738 case 2: return ANNCE_NUM_RESPAWN_2;
2739 case 1: return ANNCE_NUM_RESPAWN_1;
2743 case CNT_ROUNDSTART:
2747 case 10: return ANNCE_NUM_ROUNDSTART_10;
2748 case 9: return ANNCE_NUM_ROUNDSTART_9;
2749 case 8: return ANNCE_NUM_ROUNDSTART_8;
2750 case 7: return ANNCE_NUM_ROUNDSTART_7;
2751 case 6: return ANNCE_NUM_ROUNDSTART_6;
2752 case 5: return ANNCE_NUM_ROUNDSTART_5;
2753 case 4: return ANNCE_NUM_ROUNDSTART_4;
2754 case 3: return ANNCE_NUM_ROUNDSTART_3;
2755 case 2: return ANNCE_NUM_ROUNDSTART_2;
2756 case 1: return ANNCE_NUM_ROUNDSTART_1;
2764 case 10: return ANNCE_NUM_10;
2765 case 9: return ANNCE_NUM_9;
2766 case 8: return ANNCE_NUM_8;
2767 case 7: return ANNCE_NUM_7;
2768 case 6: return ANNCE_NUM_6;
2769 case 5: return ANNCE_NUM_5;
2770 case 4: return ANNCE_NUM_4;
2771 case 3: return ANNCE_NUM_3;
2772 case 2: return ANNCE_NUM_2;
2773 case 1: return ANNCE_NUM_1;
2778 return NOTIF_ABORT; // abort sending if none of these numbers were right
2783 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
2785 switch(nativecontents)
2790 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2792 return DPCONTENTS_WATER;
2794 return DPCONTENTS_SLIME;
2796 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2798 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2803 float Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2805 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2806 return CONTENT_SOLID;
2807 if(supercontents & DPCONTENTS_SKY)
2809 if(supercontents & DPCONTENTS_LAVA)
2810 return CONTENT_LAVA;
2811 if(supercontents & DPCONTENTS_SLIME)
2812 return CONTENT_SLIME;
2813 if(supercontents & DPCONTENTS_WATER)
2814 return CONTENT_WATER;
2815 return CONTENT_EMPTY;
2819 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2822 (c - 2 * b + a) * (t * t) +
2827 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2830 (c - 2 * b + a) * (2 * t) +