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 bool fexists(string f)
253 int fh = fopen(f, FILE_READ);
260 // Databases (hash tables)
261 const float DB_BUCKETS = 8192;
262 void db_save(float db, string pFilename)
265 fh = fopen(pFilename, FILE_WRITE);
268 print(strcat("^1Can't write DB to ", pFilename));
272 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
273 for(i = 0; i < n; ++i)
274 fputs(fh, strcat(bufstr_get(db, i), "\n"));
283 float db_load(string pFilename)
285 float db, fh, i, j, n;
290 fh = fopen(pFilename, FILE_READ);
294 if(stof(l) == DB_BUCKETS)
297 while((l = fgets(fh)))
300 bufstr_set(db, i, l);
306 // different count of buckets, or a dump?
307 // need to reorganize the database then (SLOW)
309 // note: we also parse the first line (l) in case the DB file is
310 // missing the bucket count
313 n = tokenizebyseparator(l, "\\");
314 for(j = 2; j < n; j += 2)
315 db_put(db, argv(j-1), uri_unescape(argv(j)));
317 while((l = fgets(fh)));
323 void db_dump(float db, string pFilename)
325 float fh, i, j, n, m;
326 fh = fopen(pFilename, FILE_WRITE);
328 error(strcat("Can't dump DB to ", pFilename));
331 for(i = 0; i < n; ++i)
333 m = tokenizebyseparator(bufstr_get(db, i), "\\");
334 for(j = 2; j < m; j += 2)
335 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
340 void db_close(float db)
345 string db_get(float db, string pKey)
348 h = crc16(false, pKey) % DB_BUCKETS;
349 return uri_unescape(infoget(bufstr_get(db, h), pKey));
352 void db_put(float db, string pKey, string pValue)
355 h = crc16(false, pKey) % DB_BUCKETS;
356 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
363 db = db_load("foo.db");
364 print("LOADED. FILL...\n");
365 for(i = 0; i < DB_BUCKETS; ++i)
366 db_put(db, ftos(random()), "X");
367 print("FILLED. SAVE...\n");
368 db_save(db, "foo.db");
369 print("SAVED. CLOSE...\n");
374 // Multiline text file buffers
375 float buf_load(string pFilename)
382 fh = fopen(pFilename, FILE_READ);
389 while((l = fgets(fh)))
391 bufstr_set(buf, i, l);
398 void buf_save(float buf, string pFilename)
401 fh = fopen(pFilename, FILE_WRITE);
403 error(strcat("Can't write buf to ", pFilename));
404 n = buf_getsize(buf);
405 for(i = 0; i < n; ++i)
406 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
410 string format_time(float seconds)
412 float days, hours, minutes;
413 seconds = floor(seconds + 0.5);
414 days = floor(seconds / 864000);
415 seconds -= days * 864000;
416 hours = floor(seconds / 36000);
417 seconds -= hours * 36000;
418 minutes = floor(seconds / 600);
419 seconds -= minutes * 600;
421 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
423 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
426 string mmsss(float tenths)
430 tenths = floor(tenths + 0.5);
431 minutes = floor(tenths / 600);
432 tenths -= minutes * 600;
433 s = ftos(1000 + tenths);
434 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
437 string mmssss(float hundredths)
441 hundredths = floor(hundredths + 0.5);
442 minutes = floor(hundredths / 6000);
443 hundredths -= minutes * 6000;
444 s = ftos(10000 + hundredths);
445 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
448 string ScoreString(int pFlags, float pValue)
453 pValue = floor(pValue + 0.5); // round
455 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
457 else if(pFlags & SFL_RANK)
459 valstr = ftos(pValue);
461 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
462 valstr = strcat(valstr, "th");
463 else if(substring(valstr, l - 1, 1) == "1")
464 valstr = strcat(valstr, "st");
465 else if(substring(valstr, l - 1, 1) == "2")
466 valstr = strcat(valstr, "nd");
467 else if(substring(valstr, l - 1, 1) == "3")
468 valstr = strcat(valstr, "rd");
470 valstr = strcat(valstr, "th");
472 else if(pFlags & SFL_TIME)
473 valstr = TIME_ENCODED_TOSTRING(pValue);
475 valstr = ftos(pValue);
480 float dotproduct(vector a, vector b)
482 return a.x * b.x + a.y * b.y + a.z * b.z;
485 vector cross(vector a, vector b)
488 '1 0 0' * (a.y * b.z - a.z * b.y)
489 + '0 1 0' * (a.z * b.x - a.x * b.z)
490 + '0 0 1' * (a.x * b.y - a.y * b.x);
493 // compressed vector format:
494 // like MD3, just even shorter
495 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
496 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
497 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
498 // length = 2^(length_encoded/8) / 8
499 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
500 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
501 // the special value 0 indicates the zero vector
503 float lengthLogTable[128];
505 float invertLengthLog(float x)
509 if(x >= lengthLogTable[127])
511 if(x <= lengthLogTable[0])
519 m = floor((l + r) / 2);
520 if(lengthLogTable[m] < x)
526 // now: r is >=, l is <
527 float lerr = (x - lengthLogTable[l]);
528 float rerr = (lengthLogTable[r] - x);
534 vector decompressShortVector(int data)
539 float p = (data & 0xF000) / 0x1000;
540 float y = (data & 0x0F80) / 0x80;
541 int len = (data & 0x007F);
543 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
556 y = .19634954084936207740 * y;
557 p = .19634954084936207740 * p - 1.57079632679489661922;
558 out_x = cos(y) * cos(p);
559 out_y = sin(y) * cos(p);
563 //print("decompressed: ", vtos(out), "\n");
565 return out * lengthLogTable[len];
568 float compressShortVector(vector vec)
574 //print("compress: ", vtos(vec), "\n");
575 ang = vectoangles(vec);
579 if(ang.x < -90 && ang.x > +90)
580 error("BOGUS vectoangles");
581 //print("angles: ", vtos(ang), "\n");
583 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
592 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
593 len = invertLengthLog(vlen(vec));
595 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
597 return (p * 0x1000) + (y * 0x80) + len;
600 void compressShortVector_init()
603 float f = pow(2, 1/8);
605 for(i = 0; i < 128; ++i)
607 lengthLogTable[i] = l;
611 if(cvar("developer"))
613 print("Verifying vector compression table...\n");
614 for(i = 0x0F00; i < 0xFFFF; ++i)
615 if(i != compressShortVector(decompressShortVector(i)))
617 print("BROKEN vector compression: ", ftos(i));
618 print(" -> ", vtos(decompressShortVector(i)));
619 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
628 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
630 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
631 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
632 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
633 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
634 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
635 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
636 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
637 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
638 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
639 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
640 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
641 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
646 string fixPriorityList(string order, float from, float to, float subtract, float complete)
651 n = tokenize_console(order);
653 for(i = 0; i < n; ++i)
658 if(w >= from && w <= to)
659 neworder = strcat(neworder, ftos(w), " ");
663 if(w >= from && w <= to)
664 neworder = strcat(neworder, ftos(w), " ");
671 n = tokenize_console(neworder);
672 for(w = to; w >= from; --w)
674 for(i = 0; i < n; ++i)
675 if(stof(argv(i)) == w)
677 if(i == n) // not found
678 neworder = strcat(neworder, ftos(w), " ");
682 return substring(neworder, 0, strlen(neworder) - 1);
685 string mapPriorityList(string order, string(string) mapfunc)
690 n = tokenize_console(order);
692 for(i = 0; i < n; ++i)
693 neworder = strcat(neworder, mapfunc(argv(i)), " ");
695 return substring(neworder, 0, strlen(neworder) - 1);
698 string swapInPriorityList(string order, float i, float j)
703 n = tokenize_console(order);
705 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
708 for(w = 0; w < n; ++w)
711 s = strcat(s, argv(j), " ");
713 s = strcat(s, argv(i), " ");
715 s = strcat(s, argv(w), " ");
717 return substring(s, 0, strlen(s) - 1);
723 float cvar_value_issafe(string s)
725 if(strstrofs(s, "\"", 0) >= 0)
727 if(strstrofs(s, "\\", 0) >= 0)
729 if(strstrofs(s, ";", 0) >= 0)
731 if(strstrofs(s, "$", 0) >= 0)
733 if(strstrofs(s, "\r", 0) >= 0)
735 if(strstrofs(s, "\n", 0) >= 0)
741 void get_mi_min_max(float mode)
746 strunzone(mi_shortname);
747 mi_shortname = mapname;
748 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
749 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
750 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
751 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
752 mi_shortname = strzone(mi_shortname);
764 MapInfo_Get_ByName(mi_shortname, 0, 0);
765 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
767 mi_min = MapInfo_Map_mins;
768 mi_max = MapInfo_Map_maxs;
776 tracebox('1 0 0' * mi.x,
777 '0 1 0' * mi.y + '0 0 1' * mi.z,
778 '0 1 0' * ma.y + '0 0 1' * ma.z,
782 if(!trace_startsolid)
783 mi_min_x = trace_endpos.x;
785 tracebox('0 1 0' * mi.y,
786 '1 0 0' * mi.x + '0 0 1' * mi.z,
787 '1 0 0' * ma.x + '0 0 1' * ma.z,
791 if(!trace_startsolid)
792 mi_min_y = trace_endpos.y;
794 tracebox('0 0 1' * mi.z,
795 '1 0 0' * mi.x + '0 1 0' * mi.y,
796 '1 0 0' * ma.x + '0 1 0' * ma.y,
800 if(!trace_startsolid)
801 mi_min_z = trace_endpos.z;
803 tracebox('1 0 0' * ma.x,
804 '0 1 0' * mi.y + '0 0 1' * mi.z,
805 '0 1 0' * ma.y + '0 0 1' * ma.z,
809 if(!trace_startsolid)
810 mi_max_x = trace_endpos.x;
812 tracebox('0 1 0' * ma.y,
813 '1 0 0' * mi.x + '0 0 1' * mi.z,
814 '1 0 0' * ma.x + '0 0 1' * ma.z,
818 if(!trace_startsolid)
819 mi_max_y = trace_endpos.y;
821 tracebox('0 0 1' * ma.z,
822 '1 0 0' * mi.x + '0 1 0' * mi.y,
823 '1 0 0' * ma.x + '0 1 0' * ma.y,
827 if(!trace_startsolid)
828 mi_max_z = trace_endpos.z;
833 void get_mi_min_max_texcoords(float mode)
837 get_mi_min_max(mode);
842 // extend mi_picmax to get a square aspect ratio
843 // center the map in that area
844 extend = mi_picmax - mi_picmin;
845 if(extend.y > extend.x)
847 mi_picmin.x -= (extend.y - extend.x) * 0.5;
848 mi_picmax.x += (extend.y - extend.x) * 0.5;
852 mi_picmin.y -= (extend.x - extend.y) * 0.5;
853 mi_picmax.y += (extend.x - extend.y) * 0.5;
856 // add another some percent
857 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
861 // calculate the texcoords
862 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
863 // first the two corners of the origin
864 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
865 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
866 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
867 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
868 // then the other corners
869 mi_pictexcoord1_x = mi_pictexcoord0_x;
870 mi_pictexcoord1_y = mi_pictexcoord2_y;
871 mi_pictexcoord3_x = mi_pictexcoord2_x;
872 mi_pictexcoord3_y = mi_pictexcoord0_y;
876 float cvar_settemp(string tmp_cvar, string tmp_value)
878 float created_saved_value;
881 created_saved_value = 0;
883 if (!(tmp_cvar || tmp_value))
885 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
889 if(!cvar_type(tmp_cvar))
891 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
895 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
896 if(e.netname == tmp_cvar)
897 created_saved_value = -1; // skip creation
899 if(created_saved_value != -1)
901 // creating a new entity to keep track of this cvar
903 e.classname = "saved_cvar_value";
904 e.netname = strzone(tmp_cvar);
905 e.message = strzone(cvar_string(tmp_cvar));
906 created_saved_value = 1;
909 // update the cvar to the value given
910 cvar_set(tmp_cvar, tmp_value);
912 return created_saved_value;
915 float cvar_settemp_restore()
919 while((e = find(e, classname, "saved_cvar_value")))
921 if(cvar_type(e.netname))
923 cvar_set(e.netname, e.message);
928 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
934 float almost_equals(float a, float b)
937 eps = (max(a, -a) + max(b, -b)) * 0.001;
938 if(a - b < eps && b - a < eps)
943 float almost_in_bounds(float a, float b, float c)
946 eps = (max(a, -a) + max(c, -c)) * 0.001;
949 return b == median(a - eps, b, c + eps);
952 float power2of(float e)
956 float log2of(float x)
958 // NOTE: generated code
1031 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1035 else if(ma == rgb.x)
1038 return (rgb.y - rgb.z) / (ma - mi);
1040 return (rgb.y - rgb.z) / (ma - mi) + 6;
1042 else if(ma == rgb.y)
1043 return (rgb.z - rgb.x) / (ma - mi) + 2;
1044 else // if(ma == rgb_z)
1045 return (rgb.x - rgb.y) / (ma - mi) + 4;
1048 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1052 hue -= 6 * floor(hue / 6);
1054 //else if(ma == rgb_x)
1055 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1059 rgb_y = hue * (ma - mi) + mi;
1062 //else if(ma == rgb_y)
1063 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1066 rgb_x = (2 - hue) * (ma - mi) + mi;
1074 rgb_z = (hue - 2) * (ma - mi) + mi;
1076 //else // if(ma == rgb_z)
1077 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1081 rgb_y = (4 - hue) * (ma - mi) + mi;
1086 rgb_x = (hue - 4) * (ma - mi) + mi;
1090 //else if(ma == rgb_x)
1091 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1092 else // if(hue <= 6)
1096 rgb_z = (6 - hue) * (ma - mi) + mi;
1102 vector rgb_to_hsv(vector rgb)
1107 mi = min(rgb.x, rgb.y, rgb.z);
1108 ma = max(rgb.x, rgb.y, rgb.z);
1110 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1121 vector hsv_to_rgb(vector hsv)
1123 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1126 vector rgb_to_hsl(vector rgb)
1131 mi = min(rgb.x, rgb.y, rgb.z);
1132 ma = max(rgb.x, rgb.y, rgb.z);
1134 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1136 hsl_z = 0.5 * (mi + ma);
1139 else if(hsl.z <= 0.5)
1140 hsl_y = (ma - mi) / (2*hsl.z);
1141 else // if(hsl_z > 0.5)
1142 hsl_y = (ma - mi) / (2 - 2*hsl.z);
1147 vector hsl_to_rgb(vector hsl)
1149 float mi, ma, maminusmi;
1152 maminusmi = hsl.y * 2 * hsl.z;
1154 maminusmi = hsl.y * (2 - 2 * hsl.z);
1156 // hsl_z = 0.5 * mi + 0.5 * ma
1157 // maminusmi = - mi + ma
1158 mi = hsl.z - 0.5 * maminusmi;
1159 ma = hsl.z + 0.5 * maminusmi;
1161 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1164 string rgb_to_hexcolor(vector rgb)
1169 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1170 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1171 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1175 // requires that m2>m1 in all coordinates, and that m4>m3
1176 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;}
1178 // requires the same, but is a stronger condition
1179 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;}
1184 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1187 // The following function is SLOW.
1188 // For your safety and for the protection of those around you...
1189 // DO NOT CALL THIS AT HOME.
1190 // No really, don't.
1191 if(w(theText, theSize) <= maxWidth)
1192 return strlen(theText); // yeah!
1194 // binary search for right place to cut string
1196 float left, right, middle; // this always works
1198 right = strlen(theText); // this always fails
1201 middle = floor((left + right) / 2);
1202 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1207 while(left < right - 1);
1209 if(w("^7", theSize) == 0) // detect color codes support in the width function
1211 // NOTE: when color codes are involved, this binary search is,
1212 // mathematically, BROKEN. However, it is obviously guaranteed to
1213 // terminate, as the range still halves each time - but nevertheless, it is
1214 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1215 // range, and "right" is outside).
1217 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1218 // and decrease left on the basis of the chars detected of the truncated tag
1219 // Even if the ^xrgb tag is not complete/correct, left is decreased
1220 // (sometimes too much but with a correct result)
1221 // it fixes also ^[0-9]
1222 while(left >= 1 && substring(theText, left-1, 1) == "^")
1225 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1227 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1229 ch = str2chr(theText, left-1);
1230 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1233 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1235 ch = str2chr(theText, left-2);
1236 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1238 ch = str2chr(theText, left-1);
1239 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1248 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1251 // The following function is SLOW.
1252 // For your safety and for the protection of those around you...
1253 // DO NOT CALL THIS AT HOME.
1254 // No really, don't.
1255 if(w(theText) <= maxWidth)
1256 return strlen(theText); // yeah!
1258 // binary search for right place to cut string
1260 float left, right, middle; // this always works
1262 right = strlen(theText); // this always fails
1265 middle = floor((left + right) / 2);
1266 if(w(substring(theText, 0, middle)) <= maxWidth)
1271 while(left < right - 1);
1273 if(w("^7") == 0) // detect color codes support in the width function
1275 // NOTE: when color codes are involved, this binary search is,
1276 // mathematically, BROKEN. However, it is obviously guaranteed to
1277 // terminate, as the range still halves each time - but nevertheless, it is
1278 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1279 // range, and "right" is outside).
1281 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1282 // and decrease left on the basis of the chars detected of the truncated tag
1283 // Even if the ^xrgb tag is not complete/correct, left is decreased
1284 // (sometimes too much but with a correct result)
1285 // it fixes also ^[0-9]
1286 while(left >= 1 && substring(theText, left-1, 1) == "^")
1289 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1291 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1293 ch = str2chr(theText, left-1);
1294 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1297 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1299 ch = str2chr(theText, left-2);
1300 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1302 ch = str2chr(theText, left-1);
1303 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1312 string find_last_color_code(string s)
1314 int start = strstrofs(s, "^", 0);
1315 if (start == -1) // no caret found
1317 int len = strlen(s)-1;
1319 for(i = len; i >= start; --i)
1321 if(substring(s, i, 1) != "^")
1325 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1328 // check if carets aren't all escaped
1332 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1333 return substring(s, i, 2);
1336 if(substring(s, i+1, 1) == "x")
1337 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1338 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1339 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1340 return substring(s, i, 5);
1342 i -= carets; // this also skips one char before the carets
1348 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1354 s = getWrappedLine_remaining;
1358 getWrappedLine_remaining = string_null;
1359 return s; // the line has no size ANYWAY, nothing would be displayed.
1362 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1363 if(cantake > 0 && cantake < strlen(s))
1366 while(take > 0 && substring(s, take, 1) != " ")
1370 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1371 if(getWrappedLine_remaining == "")
1372 getWrappedLine_remaining = string_null;
1373 else if (tw("^7", theFontSize) == 0)
1374 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1375 return substring(s, 0, cantake);
1379 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1380 if(getWrappedLine_remaining == "")
1381 getWrappedLine_remaining = string_null;
1382 else if (tw("^7", theFontSize) == 0)
1383 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1384 return substring(s, 0, take);
1389 getWrappedLine_remaining = string_null;
1394 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1400 s = getWrappedLine_remaining;
1404 getWrappedLine_remaining = string_null;
1405 return s; // the line has no size ANYWAY, nothing would be displayed.
1408 cantake = textLengthUpToLength(s, w, tw);
1409 if(cantake > 0 && cantake < strlen(s))
1412 while(take > 0 && substring(s, take, 1) != " ")
1416 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1417 if(getWrappedLine_remaining == "")
1418 getWrappedLine_remaining = string_null;
1419 else if (tw("^7") == 0)
1420 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1421 return substring(s, 0, cantake);
1425 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1426 if(getWrappedLine_remaining == "")
1427 getWrappedLine_remaining = string_null;
1428 else if (tw("^7") == 0)
1429 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1430 return substring(s, 0, take);
1435 getWrappedLine_remaining = string_null;
1440 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1442 if(tw(theText, theFontSize) <= maxWidth)
1445 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1448 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1450 if(tw(theText) <= maxWidth)
1453 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1456 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1458 string subpattern, subpattern2, subpattern3, subpattern4;
1459 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1461 subpattern2 = ",teams,";
1463 subpattern2 = ",noteams,";
1465 subpattern3 = ",teamspawns,";
1467 subpattern3 = ",noteamspawns,";
1468 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1469 subpattern4 = ",race,";
1471 subpattern4 = string_null;
1473 if(substring(pattern, 0, 1) == "-")
1475 pattern = substring(pattern, 1, strlen(pattern) - 1);
1476 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1478 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1480 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1482 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1487 if(substring(pattern, 0, 1) == "+")
1488 pattern = substring(pattern, 1, strlen(pattern) - 1);
1489 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1490 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1491 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1495 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1502 void shuffle(float n, swapfunc_t swap, entity pass)
1505 for(i = 1; i < n; ++i)
1507 // swap i-th item at a random position from 0 to i
1508 // proof for even distribution:
1511 // item n+1 gets at any position with chance 1/(n+1)
1512 // all others will get their 1/n chance reduced by factor n/(n+1)
1513 // to be on place n+1, their chance will be 1/(n+1)
1514 // 1/n * n/(n+1) = 1/(n+1)
1516 j = floor(random() * (i + 1));
1522 string substring_range(string s, float b, float e)
1524 return substring(s, b, e - b);
1527 string swapwords(string str, float i, float j)
1530 string s1, s2, s3, s4, s5;
1531 float si, ei, sj, ej, s0, en;
1532 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1533 si = argv_start_index(i);
1534 sj = argv_start_index(j);
1535 ei = argv_end_index(i);
1536 ej = argv_end_index(j);
1537 s0 = argv_start_index(0);
1538 en = argv_end_index(n-1);
1539 s1 = substring_range(str, s0, si);
1540 s2 = substring_range(str, si, ei);
1541 s3 = substring_range(str, ei, sj);
1542 s4 = substring_range(str, sj, ej);
1543 s5 = substring_range(str, ej, en);
1544 return strcat(s1, s4, s3, s2, s5);
1547 string _shufflewords_str;
1548 void _shufflewords_swapfunc(float i, float j, entity pass)
1550 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1552 string shufflewords(string str)
1555 _shufflewords_str = str;
1556 n = tokenizebyseparator(str, " ");
1557 shuffle(n, _shufflewords_swapfunc, world);
1558 str = _shufflewords_str;
1559 _shufflewords_str = string_null;
1563 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1579 // actually, every number solves the equation!
1590 if(a > 0) // put the smaller solution first
1592 v_x = ((-b)-D) / (2*a);
1593 v_y = ((-b)+D) / (2*a);
1597 v_x = (-b+D) / (2*a);
1598 v_y = (-b-D) / (2*a);
1604 // complex solutions!
1617 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1621 // make origin and speed relative
1626 // now solve for ret, ret normalized:
1627 // eorg + t * evel == t * ret * spd
1628 // or, rather, solve for t:
1629 // |eorg + t * evel| == t * spd
1630 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1631 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1632 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1633 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1634 // q = (eorg * eorg) / (evel * evel - spd * spd)
1635 if(!solution.z) // no real solution
1638 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1639 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1640 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1641 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1642 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1643 // spd < |evel| * sin angle(evel, eorg)
1646 else if(solution.x > 0)
1648 // both solutions > 0: take the smaller one
1649 // happens if p < 0 and q > 0
1650 ret = normalize(eorg + solution.x * evel);
1652 else if(solution.y > 0)
1654 // one solution > 0: take the larger one
1655 // happens if q < 0 or q == 0 and p < 0
1656 ret = normalize(eorg + solution.y * evel);
1660 // no solution > 0: reject
1661 // happens if p > 0 and q >= 0
1662 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1663 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1668 // "Enemy is moving away from me at more than spd"
1672 // NOTE: we always got a solution if spd > |evel|
1674 if(newton_style == 2)
1675 ret = normalize(ret * spd + myvel);
1680 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1685 if(newton_style == 2)
1687 // true Newtonian projectiles with automatic aim adjustment
1689 // solve: |outspeed * mydir - myvel| = spd
1690 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1691 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1695 // myvel^2 - (mydir * myvel)^2 > spd^2
1696 // velocity without mydir component > spd
1697 // fire at smallest possible spd that works?
1698 // |(mydir * myvel) * myvel - myvel| = spd
1700 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1704 outspeed = solution.y; // the larger one
1707 //outspeed = 0; // slowest possible shot
1708 outspeed = solution.x; // the real part (that is, the average!)
1709 //dprint("impossible shot, adjusting\n");
1712 outspeed = bound(spd * mi, outspeed, spd * ma);
1713 return mydir * outspeed;
1717 return myvel + spd * mydir;
1720 float compressShotOrigin(vector v)
1724 y = rint(v.y * 4) + 128;
1725 z = rint(v.z * 4) + 128;
1726 if(x > 255 || x < 0)
1728 print("shot origin ", vtos(v), " x out of bounds\n");
1729 x = bound(0, x, 255);
1731 if(y > 255 || y < 0)
1733 print("shot origin ", vtos(v), " y out of bounds\n");
1734 y = bound(0, y, 255);
1736 if(z > 255 || z < 0)
1738 print("shot origin ", vtos(v), " z out of bounds\n");
1739 z = bound(0, z, 255);
1741 return x * 0x10000 + y * 0x100 + z;
1743 vector decompressShotOrigin(int f)
1746 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1747 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1748 v_z = ((f & 0xFF) - 128) / 4;
1752 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1754 float start, end, root, child;
1757 start = floor((n - 2) / 2);
1760 // siftdown(start, count-1);
1762 while(root * 2 + 1 <= n-1)
1764 child = root * 2 + 1;
1766 if(cmp(child, child+1, pass) < 0)
1768 if(cmp(root, child, pass) < 0)
1770 swap(root, child, pass);
1786 // siftdown(0, end);
1788 while(root * 2 + 1 <= end)
1790 child = root * 2 + 1;
1791 if(child < end && cmp(child, child+1, pass) < 0)
1793 if(cmp(root, child, pass) < 0)
1795 swap(root, child, pass);
1805 void RandomSelection_Init()
1807 RandomSelection_totalweight = 0;
1808 RandomSelection_chosen_ent = world;
1809 RandomSelection_chosen_float = 0;
1810 RandomSelection_chosen_string = string_null;
1811 RandomSelection_best_priority = -1;
1813 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1815 if(priority > RandomSelection_best_priority)
1817 RandomSelection_best_priority = priority;
1818 RandomSelection_chosen_ent = e;
1819 RandomSelection_chosen_float = f;
1820 RandomSelection_chosen_string = s;
1821 RandomSelection_totalweight = weight;
1823 else if(priority == RandomSelection_best_priority)
1825 RandomSelection_totalweight += weight;
1826 if(random() * RandomSelection_totalweight <= weight)
1828 RandomSelection_chosen_ent = e;
1829 RandomSelection_chosen_float = f;
1830 RandomSelection_chosen_string = s;
1836 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1838 // NOTE: we'll always choose the SMALLER value...
1839 float healthdamage, armordamage, armorideal;
1840 if (deathtype == DEATH_DROWN) // Why should armor help here...
1843 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1844 armordamage = a + (h - 1); // damage we can take if we could use more armor
1845 armorideal = healthdamage * armorblock;
1847 if(armordamage < healthdamage)
1860 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1863 if (deathtype == DEATH_DROWN) // Why should armor help here...
1865 v_y = bound(0, damage * armorblock, a); // save
1866 v_x = bound(0, damage - v.y, damage); // take
1872 string getcurrentmod()
1876 m = cvar_string("fs_gamedir");
1877 n = tokenize_console(m);
1889 v = ReadShort() * 256; // note: this is signed
1890 v += ReadByte(); // note: this is unsigned
1893 vector ReadInt48_t()
1896 v_x = ReadInt24_t();
1897 v_y = ReadInt24_t();
1901 vector ReadInt72_t()
1904 v_x = ReadInt24_t();
1905 v_y = ReadInt24_t();
1906 v_z = ReadInt24_t();
1910 void WriteInt24_t(float dst, float val)
1913 WriteShort(dst, (v = floor(val / 256)));
1914 WriteByte(dst, val - v * 256); // 0..255
1916 void WriteInt48_t(float dst, vector val)
1918 WriteInt24_t(dst, val.x);
1919 WriteInt24_t(dst, val.y);
1921 void WriteInt72_t(float dst, vector val)
1923 WriteInt24_t(dst, val.x);
1924 WriteInt24_t(dst, val.y);
1925 WriteInt24_t(dst, val.z);
1930 float float2range11(float f)
1932 // continuous function mapping all reals into -1..1
1933 return f / (fabs(f) + 1);
1936 float float2range01(float f)
1938 // continuous function mapping all reals into 0..1
1939 return 0.5 + 0.5 * float2range11(f);
1942 // from the GNU Scientific Library
1943 float gsl_ran_gaussian_lastvalue;
1944 float gsl_ran_gaussian_lastvalue_set;
1945 float gsl_ran_gaussian(float sigma)
1948 if(gsl_ran_gaussian_lastvalue_set)
1950 gsl_ran_gaussian_lastvalue_set = 0;
1951 return sigma * gsl_ran_gaussian_lastvalue;
1955 a = random() * 2 * M_PI;
1956 b = sqrt(-2 * log(random()));
1957 gsl_ran_gaussian_lastvalue = cos(a) * b;
1958 gsl_ran_gaussian_lastvalue_set = 1;
1959 return sigma * sin(a) * b;
1963 string car(string s)
1966 o = strstrofs(s, " ", 0);
1969 return substring(s, 0, o);
1971 string cdr(string s)
1974 o = strstrofs(s, " ", 0);
1977 return substring(s, o + 1, strlen(s) - (o + 1));
1979 float matchacl(string acl, string str)
1986 t = car(acl); acl = cdr(acl);
1989 if(substring(t, 0, 1) == "-")
1992 t = substring(t, 1, strlen(t) - 1);
1994 else if(substring(t, 0, 1) == "+")
1995 t = substring(t, 1, strlen(t) - 1);
1997 if(substring(t, -1, 1) == "*")
1999 t = substring(t, 0, strlen(t) - 1);
2000 s = substring(str, 0, strlen(t));
2012 float startsWith(string haystack, string needle)
2014 return substring(haystack, 0, strlen(needle)) == needle;
2016 float startsWithNocase(string haystack, string needle)
2018 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2021 string get_model_datafilename(string m, float sk, string fil)
2026 m = "models/player/*_";
2028 m = strcat(m, ftos(sk));
2031 return strcat(m, ".", fil);
2034 float get_model_parameters(string m, float sk)
2039 get_model_parameters_modelname = string_null;
2040 get_model_parameters_modelskin = -1;
2041 get_model_parameters_name = string_null;
2042 get_model_parameters_species = -1;
2043 get_model_parameters_sex = string_null;
2044 get_model_parameters_weight = -1;
2045 get_model_parameters_age = -1;
2046 get_model_parameters_desc = string_null;
2047 get_model_parameters_bone_upperbody = string_null;
2048 get_model_parameters_bone_weapon = string_null;
2049 for(i = 0; i < MAX_AIM_BONES; ++i)
2051 get_model_parameters_bone_aim[i] = string_null;
2052 get_model_parameters_bone_aimweight[i] = 0;
2054 get_model_parameters_fixbone = 0;
2059 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2060 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2064 if(substring(m, -4, -1) != ".txt")
2066 if(substring(m, -6, 1) != "_")
2068 sk = stof(substring(m, -5, 1));
2069 m = substring(m, 0, -7);
2072 fn = get_model_datafilename(m, sk, "txt");
2073 fh = fopen(fn, FILE_READ);
2077 fn = get_model_datafilename(m, sk, "txt");
2078 fh = fopen(fn, FILE_READ);
2083 get_model_parameters_modelname = m;
2084 get_model_parameters_modelskin = sk;
2085 while((s = fgets(fh)))
2088 break; // next lines will be description
2092 get_model_parameters_name = s;
2096 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2097 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2098 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2099 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2100 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2101 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2102 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2105 get_model_parameters_sex = s;
2107 get_model_parameters_weight = stof(s);
2109 get_model_parameters_age = stof(s);
2110 if(c == "description")
2111 get_model_parameters_description = s;
2112 if(c == "bone_upperbody")
2113 get_model_parameters_bone_upperbody = s;
2114 if(c == "bone_weapon")
2115 get_model_parameters_bone_weapon = s;
2116 for(i = 0; i < MAX_AIM_BONES; ++i)
2117 if(c == strcat("bone_aim", ftos(i)))
2119 get_model_parameters_bone_aimweight[i] = stof(car(s));
2120 get_model_parameters_bone_aim[i] = cdr(s);
2123 get_model_parameters_fixbone = stof(s);
2126 while((s = fgets(fh)))
2128 if(get_model_parameters_desc)
2129 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2131 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2139 vector vec2(vector v)
2146 vector NearestPointOnBox(entity box, vector org)
2148 vector m1, m2, nearest;
2150 m1 = box.mins + box.origin;
2151 m2 = box.maxs + box.origin;
2153 nearest_x = bound(m1_x, org.x, m2_x);
2154 nearest_y = bound(m1_y, org.y, m2_y);
2155 nearest_z = bound(m1_z, org.z, m2_z);
2161 float vercmp_recursive(string v1, string v2)
2167 dot1 = strstrofs(v1, ".", 0);
2168 dot2 = strstrofs(v2, ".", 0);
2172 s1 = substring(v1, 0, dot1);
2176 s2 = substring(v2, 0, dot2);
2178 r = stof(s1) - stof(s2);
2182 r = strcasecmp(s1, s2);
2195 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2198 float vercmp(string v1, string v2)
2200 if(strcasecmp(v1, v2) == 0) // early out check
2209 return vercmp_recursive(v1, v2);
2212 float u8_strsize(string s)
2232 // translation helpers
2233 string language_filename(string s)
2238 if(fn == "" || fn == "dump")
2240 fn = strcat(s, ".", fn);
2241 if((fh = fopen(fn, FILE_READ)) >= 0)
2248 string CTX(string s)
2250 float p = strstrofs(s, "^", 0);
2253 return substring(s, p+1, -1);
2256 // x-encoding (encoding as zero length invisible string)
2257 const string XENCODE_2 = "xX";
2258 const string XENCODE_22 = "0123456789abcdefABCDEF";
2259 string xencode(int f)
2262 d = f % 22; f = floor(f / 22);
2263 c = f % 22; f = floor(f / 22);
2264 b = f % 22; f = floor(f / 22);
2265 a = f % 2; // f = floor(f / 2);
2268 substring(XENCODE_2, a, 1),
2269 substring(XENCODE_22, b, 1),
2270 substring(XENCODE_22, c, 1),
2271 substring(XENCODE_22, d, 1)
2274 float xdecode(string s)
2277 if(substring(s, 0, 1) != "^")
2281 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2282 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2283 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2284 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2285 if(a < 0 || b < 0 || c < 0 || d < 0)
2287 return ((a * 22 + b) * 22 + c) * 22 + d;
2290 float lowestbit(int f)
2301 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2303 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2306 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2309 // escape the string to make it safe for consoles
2310 string MakeConsoleSafe(string input)
2312 input = strreplace("\n", "", input);
2313 input = strreplace("\\", "\\\\", input);
2314 input = strreplace("$", "$$", input);
2315 input = strreplace("\"", "\\\"", input);
2320 // get true/false value of a string with multiple different inputs
2321 float InterpretBoolean(string input)
2323 switch(strtolower(input))
2335 default: return stof(input);
2341 entity ReadCSQCEntity()
2347 return findfloat(world, entnum, f);
2351 float shutdown_running;
2356 void CSQC_Shutdown()
2362 if(shutdown_running)
2364 print("Recursive shutdown detected! Only restoring cvars...\n");
2368 shutdown_running = 1;
2371 cvar_settemp_restore(); // this must be done LAST, but in any case
2374 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2375 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2376 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2377 // this will use the value:
2379 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2380 // accuracy at x is 1/derivative, i.e.
2381 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2383 void WriteApproxPastTime(float dst, float t)
2385 float dt = time - t;
2387 // warning: this is approximate; do not resend when you don't have to!
2388 // be careful with sendflags here!
2389 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2392 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2395 dt = rint(bound(0, dt, 255));
2401 float ReadApproxPastTime()
2403 float dt = ReadByte();
2405 // map from range...PPROXPASTTIME_MAX / 256
2406 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2408 return servertime - dt;
2413 .float skeleton_bones_index;
2414 void Skeleton_SetBones(entity e)
2416 // set skeleton_bones to the total number of bones on the model
2417 if(e.skeleton_bones_index == e.modelindex)
2418 return; // same model, nothing to update
2421 skelindex = skel_create(e.modelindex);
2422 e.skeleton_bones = skel_get_numbones(skelindex);
2423 skel_delete(skelindex);
2424 e.skeleton_bones_index = e.modelindex;
2428 string to_execute_next_frame;
2429 void execute_next_frame()
2431 if(to_execute_next_frame)
2433 localcmd("\n", to_execute_next_frame, "\n");
2434 strunzone(to_execute_next_frame);
2435 to_execute_next_frame = string_null;
2438 void queue_to_execute_next_frame(string s)
2440 if(to_execute_next_frame)
2442 s = strcat(s, "\n", to_execute_next_frame);
2443 strunzone(to_execute_next_frame);
2445 to_execute_next_frame = strzone(s);
2448 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2451 ((( startspeedfactor + endspeedfactor - 2
2452 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2453 ) * x + startspeedfactor
2457 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2459 if(startspeedfactor < 0 || endspeedfactor < 0)
2463 // if this is the case, the possible zeros of the first derivative are outside
2465 We can calculate this condition as condition
2470 // better, see below:
2471 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2474 // if this is the case, the first derivative has no zeros at all
2475 float se = startspeedfactor + endspeedfactor;
2476 float s_e = startspeedfactor - endspeedfactor;
2477 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2480 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2481 // we also get s_e <= 6 - se
2482 // 3 * (se - 4)^2 + (6 - se)^2
2483 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2484 // Therefore, above "better" check works!
2488 // known good cases:
2496 // (3.5, [0.2..2.3])
2501 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2503 s + e - 2 == 0: no inflection
2506 0 < inflection < 1 if:
2507 0 < 2s + e - 3 < 3s + 3e - 6
2508 2s + e > 3 and 2e + s > 3
2511 0 < inflection < 1 if:
2512 0 > 2s + e - 3 > 3s + 3e - 6
2513 2s + e < 3 and 2e + s < 3
2515 Therefore: there is an inflection point iff:
2516 e outside (3 - s)/2 .. 3 - s*2
2518 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)
2522 .float FindConnectedComponent_processing;
2523 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2525 entity queue_start, queue_end;
2527 // we build a queue of to-be-processed entities.
2528 // queue_start is the next entity to be checked for neighbors
2529 // queue_end is the last entity added
2531 if(e.FindConnectedComponent_processing)
2532 error("recursion or broken cleanup");
2534 // start with a 1-element queue
2535 queue_start = queue_end = e;
2536 queue_end.fld = world;
2537 queue_end.FindConnectedComponent_processing = 1;
2539 // for each queued item:
2540 for(0; queue_start; queue_start = queue_start.fld)
2542 // find all neighbors of queue_start
2544 for(t = world; (t = nxt(t, queue_start, pass)); )
2546 if(t.FindConnectedComponent_processing)
2548 if(iscon(t, queue_start, pass))
2550 // it is connected? ADD IT. It will look for neighbors soon too.
2553 queue_end.fld = world;
2554 queue_end.FindConnectedComponent_processing = 1;
2560 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2561 queue_start.FindConnectedComponent_processing = 0;
2565 vector combine_to_vector(float x, float y, float z)
2567 vector result; result_x = x; result_y = y; result_z = z;
2571 vector get_corner_position(entity box, float corner)
2575 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2576 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2577 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2578 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2579 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2580 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2581 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2582 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2583 default: return '0 0 0';
2588 // todo: this sucks, lets find a better way to do backtraces?
2589 void backtrace(string msg)
2593 dev = autocvar_developer;
2594 war = autocvar_prvm_backtraceforwarnings;
2596 dev = cvar("developer");
2597 war = cvar("prvm_backtraceforwarnings");
2599 cvar_set("developer", "1");
2600 cvar_set("prvm_backtraceforwarnings", "1");
2602 print("--- CUT HERE ---\nWARNING: ");
2605 remove(world); // isn't there any better way to cause a backtrace?
2606 print("\n--- CUT UNTIL HERE ---\n");
2607 cvar_set("developer", ftos(dev));
2608 cvar_set("prvm_backtraceforwarnings", ftos(war));
2611 // color code replace, place inside of sprintf and parse the string
2612 string CCR(string input)
2614 // See the autocvar declarations in util.qh for default values
2616 // foreground/normal colors
2617 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2618 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2619 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2620 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2623 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2624 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2625 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2627 // background colors
2628 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2629 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2633 vector vec3(float x, float y, float z)
2643 vector animfixfps(entity e, vector a, vector b)
2645 // multi-frame anim: keep as-is
2649 dur = frameduration(e.modelindex, a.x);
2653 dur = frameduration(e.modelindex, a.x);
2663 void dedicated_print(string input) // print(), but only print if the server is not local
2665 if(server_is_dedicated) { print(input); }
2670 float Announcer_PickNumber(float type, float num)
2678 case 10: return ANNCE_NUM_GAMESTART_10;
2679 case 9: return ANNCE_NUM_GAMESTART_9;
2680 case 8: return ANNCE_NUM_GAMESTART_8;
2681 case 7: return ANNCE_NUM_GAMESTART_7;
2682 case 6: return ANNCE_NUM_GAMESTART_6;
2683 case 5: return ANNCE_NUM_GAMESTART_5;
2684 case 4: return ANNCE_NUM_GAMESTART_4;
2685 case 3: return ANNCE_NUM_GAMESTART_3;
2686 case 2: return ANNCE_NUM_GAMESTART_2;
2687 case 1: return ANNCE_NUM_GAMESTART_1;
2695 case 10: return ANNCE_NUM_IDLE_10;
2696 case 9: return ANNCE_NUM_IDLE_9;
2697 case 8: return ANNCE_NUM_IDLE_8;
2698 case 7: return ANNCE_NUM_IDLE_7;
2699 case 6: return ANNCE_NUM_IDLE_6;
2700 case 5: return ANNCE_NUM_IDLE_5;
2701 case 4: return ANNCE_NUM_IDLE_4;
2702 case 3: return ANNCE_NUM_IDLE_3;
2703 case 2: return ANNCE_NUM_IDLE_2;
2704 case 1: return ANNCE_NUM_IDLE_1;
2712 case 10: return ANNCE_NUM_KILL_10;
2713 case 9: return ANNCE_NUM_KILL_9;
2714 case 8: return ANNCE_NUM_KILL_8;
2715 case 7: return ANNCE_NUM_KILL_7;
2716 case 6: return ANNCE_NUM_KILL_6;
2717 case 5: return ANNCE_NUM_KILL_5;
2718 case 4: return ANNCE_NUM_KILL_4;
2719 case 3: return ANNCE_NUM_KILL_3;
2720 case 2: return ANNCE_NUM_KILL_2;
2721 case 1: return ANNCE_NUM_KILL_1;
2729 case 10: return ANNCE_NUM_RESPAWN_10;
2730 case 9: return ANNCE_NUM_RESPAWN_9;
2731 case 8: return ANNCE_NUM_RESPAWN_8;
2732 case 7: return ANNCE_NUM_RESPAWN_7;
2733 case 6: return ANNCE_NUM_RESPAWN_6;
2734 case 5: return ANNCE_NUM_RESPAWN_5;
2735 case 4: return ANNCE_NUM_RESPAWN_4;
2736 case 3: return ANNCE_NUM_RESPAWN_3;
2737 case 2: return ANNCE_NUM_RESPAWN_2;
2738 case 1: return ANNCE_NUM_RESPAWN_1;
2742 case CNT_ROUNDSTART:
2746 case 10: return ANNCE_NUM_ROUNDSTART_10;
2747 case 9: return ANNCE_NUM_ROUNDSTART_9;
2748 case 8: return ANNCE_NUM_ROUNDSTART_8;
2749 case 7: return ANNCE_NUM_ROUNDSTART_7;
2750 case 6: return ANNCE_NUM_ROUNDSTART_6;
2751 case 5: return ANNCE_NUM_ROUNDSTART_5;
2752 case 4: return ANNCE_NUM_ROUNDSTART_4;
2753 case 3: return ANNCE_NUM_ROUNDSTART_3;
2754 case 2: return ANNCE_NUM_ROUNDSTART_2;
2755 case 1: return ANNCE_NUM_ROUNDSTART_1;
2763 case 10: return ANNCE_NUM_10;
2764 case 9: return ANNCE_NUM_9;
2765 case 8: return ANNCE_NUM_8;
2766 case 7: return ANNCE_NUM_7;
2767 case 6: return ANNCE_NUM_6;
2768 case 5: return ANNCE_NUM_5;
2769 case 4: return ANNCE_NUM_4;
2770 case 3: return ANNCE_NUM_3;
2771 case 2: return ANNCE_NUM_2;
2772 case 1: return ANNCE_NUM_1;
2777 return NOTIF_ABORT; // abort sending if none of these numbers were right
2782 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2784 switch(nativecontents)
2789 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2791 return DPCONTENTS_WATER;
2793 return DPCONTENTS_SLIME;
2795 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2797 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2802 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2804 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2805 return CONTENT_SOLID;
2806 if(supercontents & DPCONTENTS_SKY)
2808 if(supercontents & DPCONTENTS_LAVA)
2809 return CONTENT_LAVA;
2810 if(supercontents & DPCONTENTS_SLIME)
2811 return CONTENT_SLIME;
2812 if(supercontents & DPCONTENTS_WATER)
2813 return CONTENT_WATER;
2814 return CONTENT_EMPTY;
2818 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2821 (c - 2 * b + a) * (t * t) +
2826 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2829 (c - 2 * b + a) * (2 * t) +