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);
207 vector colormapPaletteColor(float c, float isPants)
211 case 0: return '1.000000 1.000000 1.000000';
212 case 1: return '1.000000 0.333333 0.000000';
213 case 2: return '0.000000 1.000000 0.501961';
214 case 3: return '0.000000 1.000000 0.000000';
215 case 4: return '1.000000 0.000000 0.000000';
216 case 5: return '0.000000 0.666667 1.000000';
217 case 6: return '0.000000 1.000000 1.000000';
218 case 7: return '0.501961 1.000000 0.000000';
219 case 8: return '0.501961 0.000000 1.000000';
220 case 9: return '1.000000 0.000000 1.000000';
221 case 10: return '1.000000 0.000000 0.501961';
222 case 11: return '0.000000 0.000000 1.000000';
223 case 12: return '1.000000 1.000000 0.000000';
224 case 13: return '0.000000 0.333333 1.000000';
225 case 14: return '1.000000 0.666667 0.000000';
229 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
230 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
231 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
234 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
235 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
236 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
237 default: return '0.000 0.000 0.000';
241 // unzone the string, and return it as tempstring. Safe to be called on string_null
242 string fstrunzone(string s)
252 float fexists(string f)
255 fh = fopen(f, FILE_READ);
262 // Databases (hash tables)
263 #define DB_BUCKETS 8192
264 void db_save(float db, string pFilename)
267 fh = fopen(pFilename, FILE_WRITE);
270 print(strcat("^1Can't write DB to ", pFilename));
274 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
275 for(i = 0; i < n; ++i)
276 fputs(fh, strcat(bufstr_get(db, i), "\n"));
285 float db_load(string pFilename)
287 float db, fh, i, j, n;
292 fh = fopen(pFilename, FILE_READ);
296 if(stof(l) == DB_BUCKETS)
299 while((l = fgets(fh)))
302 bufstr_set(db, i, l);
308 // different count of buckets, or a dump?
309 // need to reorganize the database then (SLOW)
311 // note: we also parse the first line (l) in case the DB file is
312 // missing the bucket count
315 n = tokenizebyseparator(l, "\\");
316 for(j = 2; j < n; j += 2)
317 db_put(db, argv(j-1), uri_unescape(argv(j)));
319 while((l = fgets(fh)));
325 void db_dump(float db, string pFilename)
327 float fh, i, j, n, m;
328 fh = fopen(pFilename, FILE_WRITE);
330 error(strcat("Can't dump DB to ", pFilename));
333 for(i = 0; i < n; ++i)
335 m = tokenizebyseparator(bufstr_get(db, i), "\\");
336 for(j = 2; j < m; j += 2)
337 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
342 void db_close(float db)
347 string db_get(float db, string pKey)
350 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
351 return uri_unescape(infoget(bufstr_get(db, h), pKey));
354 void db_put(float db, string pKey, string pValue)
357 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
358 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
365 db = db_load("foo.db");
366 print("LOADED. FILL...\n");
367 for(i = 0; i < DB_BUCKETS; ++i)
368 db_put(db, ftos(random()), "X");
369 print("FILLED. SAVE...\n");
370 db_save(db, "foo.db");
371 print("SAVED. CLOSE...\n");
376 // Multiline text file buffers
377 float buf_load(string pFilename)
384 fh = fopen(pFilename, FILE_READ);
391 while((l = fgets(fh)))
393 bufstr_set(buf, i, l);
400 void buf_save(float buf, string pFilename)
403 fh = fopen(pFilename, FILE_WRITE);
405 error(strcat("Can't write buf to ", pFilename));
406 n = buf_getsize(buf);
407 for(i = 0; i < n; ++i)
408 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
412 string format_time(float seconds)
414 float days, hours, minutes;
415 seconds = floor(seconds + 0.5);
416 days = floor(seconds / 864000);
417 seconds -= days * 864000;
418 hours = floor(seconds / 36000);
419 seconds -= hours * 36000;
420 minutes = floor(seconds / 600);
421 seconds -= minutes * 600;
423 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
425 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
428 string mmsss(float tenths)
432 tenths = floor(tenths + 0.5);
433 minutes = floor(tenths / 600);
434 tenths -= minutes * 600;
435 s = ftos(1000 + tenths);
436 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
439 string mmssss(float hundredths)
443 hundredths = floor(hundredths + 0.5);
444 minutes = floor(hundredths / 6000);
445 hundredths -= minutes * 6000;
446 s = ftos(10000 + hundredths);
447 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
450 string ScoreString(float pFlags, float pValue)
455 pValue = floor(pValue + 0.5); // round
457 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
459 else if(pFlags & SFL_RANK)
461 valstr = ftos(pValue);
463 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
464 valstr = strcat(valstr, "th");
465 else if(substring(valstr, l - 1, 1) == "1")
466 valstr = strcat(valstr, "st");
467 else if(substring(valstr, l - 1, 1) == "2")
468 valstr = strcat(valstr, "nd");
469 else if(substring(valstr, l - 1, 1) == "3")
470 valstr = strcat(valstr, "rd");
472 valstr = strcat(valstr, "th");
474 else if(pFlags & SFL_TIME)
475 valstr = TIME_ENCODED_TOSTRING(pValue);
477 valstr = ftos(pValue);
482 float dotproduct(vector a, vector b)
484 return a_x * b_x + a_y * b_y + a_z * b_z;
487 vector cross(vector a, vector b)
490 '1 0 0' * (a_y * b_z - a_z * b_y)
491 + '0 1 0' * (a_z * b_x - a_x * b_z)
492 + '0 0 1' * (a_x * b_y - a_y * b_x);
495 // compressed vector format:
496 // like MD3, just even shorter
497 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
498 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
499 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
500 // length = 2^(length_encoded/8) / 8
501 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
502 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
503 // the special value 0 indicates the zero vector
505 float lengthLogTable[128];
507 float invertLengthLog(float x)
509 float l, r, m, lerr, rerr;
511 if(x >= lengthLogTable[127])
513 if(x <= lengthLogTable[0])
521 m = floor((l + r) / 2);
522 if(lengthLogTable[m] < x)
528 // now: r is >=, l is <
529 lerr = (x - lengthLogTable[l]);
530 rerr = (lengthLogTable[r] - x);
536 vector decompressShortVector(float data)
542 p = (data & 0xF000) / 0x1000;
543 y = (data & 0x0F80) / 0x80;
544 len = (data & 0x007F);
546 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
559 y = .19634954084936207740 * y;
560 p = .19634954084936207740 * p - 1.57079632679489661922;
561 out_x = cos(y) * cos(p);
562 out_y = sin(y) * cos(p);
566 //print("decompressed: ", vtos(out), "\n");
568 return out * lengthLogTable[len];
571 float compressShortVector(vector vec)
577 //print("compress: ", vtos(vec), "\n");
578 ang = vectoangles(vec);
582 if(ang_x < -90 && ang_x > +90)
583 error("BOGUS vectoangles");
584 //print("angles: ", vtos(ang), "\n");
586 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
595 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
596 len = invertLengthLog(vlen(vec));
598 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
600 return (p * 0x1000) + (y * 0x80) + len;
603 void compressShortVector_init()
608 for(i = 0; i < 128; ++i)
610 lengthLogTable[i] = l;
614 if(cvar("developer"))
616 print("Verifying vector compression table...\n");
617 for(i = 0x0F00; i < 0xFFFF; ++i)
618 if(i != compressShortVector(decompressShortVector(i)))
620 print("BROKEN vector compression: ", ftos(i));
621 print(" -> ", vtos(decompressShortVector(i)));
622 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
631 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
633 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
634 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
635 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
636 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
637 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
638 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
639 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
640 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
641 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
642 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
643 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
644 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
649 string fixPriorityList(string order, float from, float to, float subtract, float complete)
654 n = tokenize_console(order);
656 for(i = 0; i < n; ++i)
661 if(w >= from && w <= to)
662 neworder = strcat(neworder, ftos(w), " ");
666 if(w >= from && w <= to)
667 neworder = strcat(neworder, ftos(w), " ");
674 n = tokenize_console(neworder);
675 for(w = to; w >= from; --w)
677 for(i = 0; i < n; ++i)
678 if(stof(argv(i)) == w)
680 if(i == n) // not found
681 neworder = strcat(neworder, ftos(w), " ");
685 return substring(neworder, 0, strlen(neworder) - 1);
688 string mapPriorityList(string order, string(string) mapfunc)
693 n = tokenize_console(order);
695 for(i = 0; i < n; ++i)
696 neworder = strcat(neworder, mapfunc(argv(i)), " ");
698 return substring(neworder, 0, strlen(neworder) - 1);
701 string swapInPriorityList(string order, float i, float j)
706 n = tokenize_console(order);
708 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
711 for(w = 0; w < n; ++w)
714 s = strcat(s, argv(j), " ");
716 s = strcat(s, argv(i), " ");
718 s = strcat(s, argv(w), " ");
720 return substring(s, 0, strlen(s) - 1);
726 float cvar_value_issafe(string s)
728 if(strstrofs(s, "\"", 0) >= 0)
730 if(strstrofs(s, "\\", 0) >= 0)
732 if(strstrofs(s, ";", 0) >= 0)
734 if(strstrofs(s, "$", 0) >= 0)
736 if(strstrofs(s, "\r", 0) >= 0)
738 if(strstrofs(s, "\n", 0) >= 0)
744 void get_mi_min_max(float mode)
749 strunzone(mi_shortname);
750 mi_shortname = mapname;
751 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
752 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
753 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
754 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
755 mi_shortname = strzone(mi_shortname);
767 MapInfo_Get_ByName(mi_shortname, 0, 0);
768 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
770 mi_min = MapInfo_Map_mins;
771 mi_max = MapInfo_Map_maxs;
779 tracebox('1 0 0' * mi_x,
780 '0 1 0' * mi_y + '0 0 1' * mi_z,
781 '0 1 0' * ma_y + '0 0 1' * ma_z,
785 if(!trace_startsolid)
786 mi_min_x = trace_endpos_x;
788 tracebox('0 1 0' * mi_y,
789 '1 0 0' * mi_x + '0 0 1' * mi_z,
790 '1 0 0' * ma_x + '0 0 1' * ma_z,
794 if(!trace_startsolid)
795 mi_min_y = trace_endpos_y;
797 tracebox('0 0 1' * mi_z,
798 '1 0 0' * mi_x + '0 1 0' * mi_y,
799 '1 0 0' * ma_x + '0 1 0' * ma_y,
803 if(!trace_startsolid)
804 mi_min_z = trace_endpos_z;
806 tracebox('1 0 0' * ma_x,
807 '0 1 0' * mi_y + '0 0 1' * mi_z,
808 '0 1 0' * ma_y + '0 0 1' * ma_z,
812 if(!trace_startsolid)
813 mi_max_x = trace_endpos_x;
815 tracebox('0 1 0' * ma_y,
816 '1 0 0' * mi_x + '0 0 1' * mi_z,
817 '1 0 0' * ma_x + '0 0 1' * ma_z,
821 if(!trace_startsolid)
822 mi_max_y = trace_endpos_y;
824 tracebox('0 0 1' * ma_z,
825 '1 0 0' * mi_x + '0 1 0' * mi_y,
826 '1 0 0' * ma_x + '0 1 0' * ma_y,
830 if(!trace_startsolid)
831 mi_max_z = trace_endpos_z;
836 void get_mi_min_max_texcoords(float mode)
840 get_mi_min_max(mode);
845 // extend mi_picmax to get a square aspect ratio
846 // center the map in that area
847 extend = mi_picmax - mi_picmin;
848 if(extend_y > extend_x)
850 mi_picmin_x -= (extend_y - extend_x) * 0.5;
851 mi_picmax_x += (extend_y - extend_x) * 0.5;
855 mi_picmin_y -= (extend_x - extend_y) * 0.5;
856 mi_picmax_y += (extend_x - extend_y) * 0.5;
859 // add another some percent
860 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
864 // calculate the texcoords
865 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
866 // first the two corners of the origin
867 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
868 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
869 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
870 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
871 // then the other corners
872 mi_pictexcoord1_x = mi_pictexcoord0_x;
873 mi_pictexcoord1_y = mi_pictexcoord2_y;
874 mi_pictexcoord3_x = mi_pictexcoord2_x;
875 mi_pictexcoord3_y = mi_pictexcoord0_y;
879 float cvar_settemp(string tmp_cvar, string tmp_value)
881 float created_saved_value;
884 created_saved_value = 0;
886 if not(tmp_cvar || tmp_value)
888 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
892 if(!cvar_type(tmp_cvar))
894 print(sprintf("Error: cvar %s doesn't exist!\n", tmp_cvar));
898 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
899 if(e.netname == tmp_cvar)
900 created_saved_value = -1; // skip creation
902 if(created_saved_value != -1)
904 // creating a new entity to keep track of this cvar
906 e.classname = "saved_cvar_value";
907 e.netname = strzone(tmp_cvar);
908 e.message = strzone(cvar_string(tmp_cvar));
909 created_saved_value = 1;
912 // update the cvar to the value given
913 cvar_set(tmp_cvar, tmp_value);
915 return created_saved_value;
918 float cvar_settemp_restore()
922 while((e = find(e, classname, "saved_cvar_value")))
924 if(cvar_type(e.netname))
926 cvar_set(e.netname, e.message);
931 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname));
937 float almost_equals(float a, float b)
940 eps = (max(a, -a) + max(b, -b)) * 0.001;
941 if(a - b < eps && b - a < eps)
946 float almost_in_bounds(float a, float b, float c)
949 eps = (max(a, -a) + max(c, -c)) * 0.001;
952 return b == median(a - eps, b, c + eps);
955 float power2of(float e)
959 float log2of(float x)
961 // NOTE: generated code
1034 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1038 else if(ma == rgb_x)
1041 return (rgb_y - rgb_z) / (ma - mi);
1043 return (rgb_y - rgb_z) / (ma - mi) + 6;
1045 else if(ma == rgb_y)
1046 return (rgb_z - rgb_x) / (ma - mi) + 2;
1047 else // if(ma == rgb_z)
1048 return (rgb_x - rgb_y) / (ma - mi) + 4;
1051 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1055 hue -= 6 * floor(hue / 6);
1057 //else if(ma == rgb_x)
1058 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1062 rgb_y = hue * (ma - mi) + mi;
1065 //else if(ma == rgb_y)
1066 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1069 rgb_x = (2 - hue) * (ma - mi) + mi;
1077 rgb_z = (hue - 2) * (ma - mi) + mi;
1079 //else // if(ma == rgb_z)
1080 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1084 rgb_y = (4 - hue) * (ma - mi) + mi;
1089 rgb_x = (hue - 4) * (ma - mi) + mi;
1093 //else if(ma == rgb_x)
1094 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1095 else // if(hue <= 6)
1099 rgb_z = (6 - hue) * (ma - mi) + mi;
1105 vector rgb_to_hsv(vector rgb)
1110 mi = min(rgb_x, rgb_y, rgb_z);
1111 ma = max(rgb_x, rgb_y, rgb_z);
1113 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1124 vector hsv_to_rgb(vector hsv)
1126 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1129 vector rgb_to_hsl(vector rgb)
1134 mi = min(rgb_x, rgb_y, rgb_z);
1135 ma = max(rgb_x, rgb_y, rgb_z);
1137 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1139 hsl_z = 0.5 * (mi + ma);
1142 else if(hsl_z <= 0.5)
1143 hsl_y = (ma - mi) / (2*hsl_z);
1144 else // if(hsl_z > 0.5)
1145 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1150 vector hsl_to_rgb(vector hsl)
1152 float mi, ma, maminusmi;
1155 maminusmi = hsl_y * 2 * hsl_z;
1157 maminusmi = hsl_y * (2 - 2 * hsl_z);
1159 // hsl_z = 0.5 * mi + 0.5 * ma
1160 // maminusmi = - mi + ma
1161 mi = hsl_z - 0.5 * maminusmi;
1162 ma = hsl_z + 0.5 * maminusmi;
1164 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1167 string rgb_to_hexcolor(vector rgb)
1172 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1173 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1174 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1178 // requires that m2>m1 in all coordinates, and that m4>m3
1179 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;}
1181 // requires the same, but is a stronger condition
1182 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;}
1187 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1190 // The following function is SLOW.
1191 // For your safety and for the protection of those around you...
1192 // DO NOT CALL THIS AT HOME.
1193 // No really, don't.
1194 if(w(theText, theSize) <= maxWidth)
1195 return strlen(theText); // yeah!
1197 // binary search for right place to cut string
1199 float left, right, middle; // this always works
1201 right = strlen(theText); // this always fails
1204 middle = floor((left + right) / 2);
1205 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1210 while(left < right - 1);
1212 if(w("^7", theSize) == 0) // detect color codes support in the width function
1214 // NOTE: when color codes are involved, this binary search is,
1215 // mathematically, BROKEN. However, it is obviously guaranteed to
1216 // terminate, as the range still halves each time - but nevertheless, it is
1217 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1218 // range, and "right" is outside).
1220 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1221 // and decrease left on the basis of the chars detected of the truncated tag
1222 // Even if the ^xrgb tag is not complete/correct, left is decreased
1223 // (sometimes too much but with a correct result)
1224 // it fixes also ^[0-9]
1225 while(left >= 1 && substring(theText, left-1, 1) == "^")
1228 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1230 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1232 ch = str2chr(theText, left-1);
1233 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1236 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1238 ch = str2chr(theText, left-2);
1239 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1241 ch = str2chr(theText, left-1);
1242 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1251 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1254 // The following function is SLOW.
1255 // For your safety and for the protection of those around you...
1256 // DO NOT CALL THIS AT HOME.
1257 // No really, don't.
1258 if(w(theText) <= maxWidth)
1259 return strlen(theText); // yeah!
1261 // binary search for right place to cut string
1263 float left, right, middle; // this always works
1265 right = strlen(theText); // this always fails
1268 middle = floor((left + right) / 2);
1269 if(w(substring(theText, 0, middle)) <= maxWidth)
1274 while(left < right - 1);
1276 if(w("^7") == 0) // detect color codes support in the width function
1278 // NOTE: when color codes are involved, this binary search is,
1279 // mathematically, BROKEN. However, it is obviously guaranteed to
1280 // terminate, as the range still halves each time - but nevertheless, it is
1281 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1282 // range, and "right" is outside).
1284 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1285 // and decrease left on the basis of the chars detected of the truncated tag
1286 // Even if the ^xrgb tag is not complete/correct, left is decreased
1287 // (sometimes too much but with a correct result)
1288 // it fixes also ^[0-9]
1289 while(left >= 1 && substring(theText, left-1, 1) == "^")
1292 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1294 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1296 ch = str2chr(theText, left-1);
1297 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1300 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1302 ch = str2chr(theText, left-2);
1303 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1305 ch = str2chr(theText, left-1);
1306 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1315 string find_last_color_code(string s)
1317 float start, len, i, carets;
1318 start = strstrofs(s, "^", 0);
1319 if (start == -1) // no caret found
1322 for(i = len; i >= start; --i)
1324 if(substring(s, i, 1) != "^")
1328 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1331 // check if carets aren't all escaped
1332 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1335 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1336 return substring(s, i, 2);
1339 if(substring(s, i+1, 1) == "x")
1340 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1341 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1342 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1343 return substring(s, i, 5);
1345 i -= carets; // this also skips one char before the carets
1351 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1357 s = getWrappedLine_remaining;
1361 getWrappedLine_remaining = string_null;
1362 return s; // the line has no size ANYWAY, nothing would be displayed.
1365 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1366 if(cantake > 0 && cantake < strlen(s))
1369 while(take > 0 && substring(s, take, 1) != " ")
1373 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1374 if(getWrappedLine_remaining == "")
1375 getWrappedLine_remaining = string_null;
1376 else if (tw("^7", theFontSize) == 0)
1377 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1378 return substring(s, 0, cantake);
1382 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1383 if(getWrappedLine_remaining == "")
1384 getWrappedLine_remaining = string_null;
1385 else if (tw("^7", theFontSize) == 0)
1386 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1387 return substring(s, 0, take);
1392 getWrappedLine_remaining = string_null;
1397 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1403 s = getWrappedLine_remaining;
1407 getWrappedLine_remaining = string_null;
1408 return s; // the line has no size ANYWAY, nothing would be displayed.
1411 cantake = textLengthUpToLength(s, w, tw);
1412 if(cantake > 0 && cantake < strlen(s))
1415 while(take > 0 && substring(s, take, 1) != " ")
1419 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1420 if(getWrappedLine_remaining == "")
1421 getWrappedLine_remaining = string_null;
1422 else if (tw("^7") == 0)
1423 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1424 return substring(s, 0, cantake);
1428 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1429 if(getWrappedLine_remaining == "")
1430 getWrappedLine_remaining = string_null;
1431 else if (tw("^7") == 0)
1432 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1433 return substring(s, 0, take);
1438 getWrappedLine_remaining = string_null;
1443 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1445 if(tw(theText, theFontSize) <= maxWidth)
1448 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1451 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1453 if(tw(theText) <= maxWidth)
1456 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1459 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1461 string subpattern, subpattern2, subpattern3, subpattern4;
1462 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1464 subpattern2 = ",teams,";
1466 subpattern2 = ",noteams,";
1468 subpattern3 = ",teamspawns,";
1470 subpattern3 = ",noteamspawns,";
1471 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1472 subpattern4 = ",race,";
1474 subpattern4 = string_null;
1476 if(substring(pattern, 0, 1) == "-")
1478 pattern = substring(pattern, 1, strlen(pattern) - 1);
1479 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1481 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1483 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1485 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1490 if(substring(pattern, 0, 1) == "+")
1491 pattern = substring(pattern, 1, strlen(pattern) - 1);
1492 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1493 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1494 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1498 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1505 void shuffle(float n, swapfunc_t swap, entity pass)
1508 for(i = 1; i < n; ++i)
1510 // swap i-th item at a random position from 0 to i
1511 // proof for even distribution:
1514 // item n+1 gets at any position with chance 1/(n+1)
1515 // all others will get their 1/n chance reduced by factor n/(n+1)
1516 // to be on place n+1, their chance will be 1/(n+1)
1517 // 1/n * n/(n+1) = 1/(n+1)
1519 j = floor(random() * (i + 1));
1525 string substring_range(string s, float b, float e)
1527 return substring(s, b, e - b);
1530 string swapwords(string str, float i, float j)
1533 string s1, s2, s3, s4, s5;
1534 float si, ei, sj, ej, s0, en;
1535 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1536 si = argv_start_index(i);
1537 sj = argv_start_index(j);
1538 ei = argv_end_index(i);
1539 ej = argv_end_index(j);
1540 s0 = argv_start_index(0);
1541 en = argv_end_index(n-1);
1542 s1 = substring_range(str, s0, si);
1543 s2 = substring_range(str, si, ei);
1544 s3 = substring_range(str, ei, sj);
1545 s4 = substring_range(str, sj, ej);
1546 s5 = substring_range(str, ej, en);
1547 return strcat(s1, s4, s3, s2, s5);
1550 string _shufflewords_str;
1551 void _shufflewords_swapfunc(float i, float j, entity pass)
1553 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1555 string shufflewords(string str)
1558 _shufflewords_str = str;
1559 n = tokenizebyseparator(str, " ");
1560 shuffle(n, _shufflewords_swapfunc, world);
1561 str = _shufflewords_str;
1562 _shufflewords_str = string_null;
1566 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1582 // actually, every number solves the equation!
1593 if(a > 0) // put the smaller solution first
1595 v_x = ((-b)-D) / (2*a);
1596 v_y = ((-b)+D) / (2*a);
1600 v_x = (-b+D) / (2*a);
1601 v_y = (-b-D) / (2*a);
1607 // complex solutions!
1620 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1624 // make origin and speed relative
1629 // now solve for ret, ret normalized:
1630 // eorg + t * evel == t * ret * spd
1631 // or, rather, solve for t:
1632 // |eorg + t * evel| == t * spd
1633 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1634 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1635 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1636 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1637 // q = (eorg * eorg) / (evel * evel - spd * spd)
1638 if(!solution_z) // no real solution
1641 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1642 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1643 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1644 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1645 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1646 // spd < |evel| * sin angle(evel, eorg)
1649 else if(solution_x > 0)
1651 // both solutions > 0: take the smaller one
1652 // happens if p < 0 and q > 0
1653 ret = normalize(eorg + solution_x * evel);
1655 else if(solution_y > 0)
1657 // one solution > 0: take the larger one
1658 // happens if q < 0 or q == 0 and p < 0
1659 ret = normalize(eorg + solution_y * evel);
1663 // no solution > 0: reject
1664 // happens if p > 0 and q >= 0
1665 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1666 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1671 // "Enemy is moving away from me at more than spd"
1675 // NOTE: we always got a solution if spd > |evel|
1677 if(newton_style == 2)
1678 ret = normalize(ret * spd + myvel);
1683 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1688 if(newton_style == 2)
1690 // true Newtonian projectiles with automatic aim adjustment
1692 // solve: |outspeed * mydir - myvel| = spd
1693 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1694 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1698 // myvel^2 - (mydir * myvel)^2 > spd^2
1699 // velocity without mydir component > spd
1700 // fire at smallest possible spd that works?
1701 // |(mydir * myvel) * myvel - myvel| = spd
1703 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1707 outspeed = solution_y; // the larger one
1710 //outspeed = 0; // slowest possible shot
1711 outspeed = solution_x; // the real part (that is, the average!)
1712 //dprint("impossible shot, adjusting\n");
1715 outspeed = bound(spd * mi, outspeed, spd * ma);
1716 return mydir * outspeed;
1720 return myvel + spd * mydir;
1723 void check_unacceptable_compiler_bugs()
1725 if(cvar("_allow_unacceptable_compiler_bugs"))
1727 tokenize_console("foo bar");
1728 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1729 error("fteqcc bug introduced with revision 3178 detected. Please upgrade fteqcc to a later revision, downgrade fteqcc to revision 3177, or pester Spike until he fixes it. You can set _allow_unacceptable_compiler_bugs 1 to skip this check, but expect stuff to be horribly broken then.");
1733 error("The empty string counts as false. We do not want that!");
1736 float compressShotOrigin(vector v)
1740 y = rint(v_y * 4) + 128;
1741 z = rint(v_z * 4) + 128;
1742 if(x > 255 || x < 0)
1744 print("shot origin ", vtos(v), " x out of bounds\n");
1745 x = bound(0, x, 255);
1747 if(y > 255 || y < 0)
1749 print("shot origin ", vtos(v), " y out of bounds\n");
1750 y = bound(0, y, 255);
1752 if(z > 255 || z < 0)
1754 print("shot origin ", vtos(v), " z out of bounds\n");
1755 z = bound(0, z, 255);
1757 return x * 0x10000 + y * 0x100 + z;
1759 vector decompressShotOrigin(float f)
1762 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1763 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1764 v_z = ((f & 0xFF) - 128) / 4;
1768 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1770 float start, end, root, child;
1773 start = floor((n - 2) / 2);
1776 // siftdown(start, count-1);
1778 while(root * 2 + 1 <= n-1)
1780 child = root * 2 + 1;
1782 if(cmp(child, child+1, pass) < 0)
1784 if(cmp(root, child, pass) < 0)
1786 swap(root, child, pass);
1802 // siftdown(0, end);
1804 while(root * 2 + 1 <= end)
1806 child = root * 2 + 1;
1807 if(child < end && cmp(child, child+1, pass) < 0)
1809 if(cmp(root, child, pass) < 0)
1811 swap(root, child, pass);
1821 void RandomSelection_Init()
1823 RandomSelection_totalweight = 0;
1824 RandomSelection_chosen_ent = world;
1825 RandomSelection_chosen_float = 0;
1826 RandomSelection_chosen_string = string_null;
1827 RandomSelection_best_priority = -1;
1829 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1831 if(priority > RandomSelection_best_priority)
1833 RandomSelection_best_priority = priority;
1834 RandomSelection_chosen_ent = e;
1835 RandomSelection_chosen_float = f;
1836 RandomSelection_chosen_string = s;
1837 RandomSelection_totalweight = weight;
1839 else if(priority == RandomSelection_best_priority)
1841 RandomSelection_totalweight += weight;
1842 if(random() * RandomSelection_totalweight <= weight)
1844 RandomSelection_chosen_ent = e;
1845 RandomSelection_chosen_float = f;
1846 RandomSelection_chosen_string = s;
1851 vector healtharmor_maxdamage(float h, float a, float armorblock)
1853 // NOTE: we'll always choose the SMALLER value...
1854 float healthdamage, armordamage, armorideal;
1856 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1857 armordamage = a + (h - 1); // damage we can take if we could use more armor
1858 armorideal = healthdamage * armorblock;
1860 if(armordamage < healthdamage)
1873 vector healtharmor_applydamage(float a, float armorblock, float damage)
1876 v_y = bound(0, damage * armorblock, a); // save
1877 v_x = bound(0, damage - v_y, damage); // take
1882 string getcurrentmod()
1886 m = cvar_string("fs_gamedir");
1887 n = tokenize_console(m);
1899 v = ReadShort() * 256; // note: this is signed
1900 v += ReadByte(); // note: this is unsigned
1904 void WriteInt24_t(float dst, float val)
1907 WriteShort(dst, (v = floor(val / 256)));
1908 WriteByte(dst, val - v * 256); // 0..255
1913 float float2range11(float f)
1915 // continuous function mapping all reals into -1..1
1916 return f / (fabs(f) + 1);
1919 float float2range01(float f)
1921 // continuous function mapping all reals into 0..1
1922 return 0.5 + 0.5 * float2range11(f);
1925 // from the GNU Scientific Library
1926 float gsl_ran_gaussian_lastvalue;
1927 float gsl_ran_gaussian_lastvalue_set;
1928 float gsl_ran_gaussian(float sigma)
1931 if(gsl_ran_gaussian_lastvalue_set)
1933 gsl_ran_gaussian_lastvalue_set = 0;
1934 return sigma * gsl_ran_gaussian_lastvalue;
1938 a = random() * 2 * M_PI;
1939 b = sqrt(-2 * log(random()));
1940 gsl_ran_gaussian_lastvalue = cos(a) * b;
1941 gsl_ran_gaussian_lastvalue_set = 1;
1942 return sigma * sin(a) * b;
1946 string car(string s)
1949 o = strstrofs(s, " ", 0);
1952 return substring(s, 0, o);
1954 string cdr(string s)
1957 o = strstrofs(s, " ", 0);
1960 return substring(s, o + 1, strlen(s) - (o + 1));
1962 float matchacl(string acl, string str)
1969 t = car(acl); acl = cdr(acl);
1972 if(substring(t, 0, 1) == "-")
1975 t = substring(t, 1, strlen(t) - 1);
1977 else if(substring(t, 0, 1) == "+")
1978 t = substring(t, 1, strlen(t) - 1);
1980 if(substring(t, -1, 1) == "*")
1982 t = substring(t, 0, strlen(t) - 1);
1983 s = substring(str, 0, strlen(t));
1995 float startsWith(string haystack, string needle)
1997 return substring(haystack, 0, strlen(needle)) == needle;
1999 float startsWithNocase(string haystack, string needle)
2001 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2004 string get_model_datafilename(string m, float sk, string fil)
2009 m = "models/player/*_";
2011 m = strcat(m, ftos(sk));
2014 return strcat(m, ".", fil);
2017 float get_model_parameters(string m, float sk)
2022 get_model_parameters_modelname = string_null;
2023 get_model_parameters_modelskin = -1;
2024 get_model_parameters_name = string_null;
2025 get_model_parameters_species = -1;
2026 get_model_parameters_sex = string_null;
2027 get_model_parameters_weight = -1;
2028 get_model_parameters_age = -1;
2029 get_model_parameters_desc = string_null;
2030 get_model_parameters_bone_upperbody = string_null;
2031 get_model_parameters_bone_weapon = string_null;
2032 for(i = 0; i < MAX_AIM_BONES; ++i)
2034 get_model_parameters_bone_aim[i] = string_null;
2035 get_model_parameters_bone_aimweight[i] = 0;
2037 get_model_parameters_fixbone = 0;
2042 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2043 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2047 if(substring(m, -4, -1) != ".txt")
2049 if(substring(m, -6, 1) != "_")
2051 sk = stof(substring(m, -5, 1));
2052 m = substring(m, 0, -7);
2055 fn = get_model_datafilename(m, sk, "txt");
2056 fh = fopen(fn, FILE_READ);
2060 fn = get_model_datafilename(m, sk, "txt");
2061 fh = fopen(fn, FILE_READ);
2066 get_model_parameters_modelname = m;
2067 get_model_parameters_modelskin = sk;
2068 while((s = fgets(fh)))
2071 break; // next lines will be description
2075 get_model_parameters_name = s;
2079 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2080 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2081 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2082 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2083 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2084 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2085 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2088 get_model_parameters_sex = s;
2090 get_model_parameters_weight = stof(s);
2092 get_model_parameters_age = stof(s);
2093 if(c == "bone_upperbody")
2094 get_model_parameters_bone_upperbody = s;
2095 if(c == "bone_weapon")
2096 get_model_parameters_bone_weapon = s;
2097 for(i = 0; i < MAX_AIM_BONES; ++i)
2098 if(c == strcat("bone_aim", ftos(i)))
2100 get_model_parameters_bone_aimweight[i] = stof(car(s));
2101 get_model_parameters_bone_aim[i] = cdr(s);
2104 get_model_parameters_fixbone = stof(s);
2107 while((s = fgets(fh)))
2109 if(get_model_parameters_desc)
2110 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2112 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2120 vector vec2(vector v)
2127 vector NearestPointOnBox(entity box, vector org)
2129 vector m1, m2, nearest;
2131 m1 = box.mins + box.origin;
2132 m2 = box.maxs + box.origin;
2134 nearest_x = bound(m1_x, org_x, m2_x);
2135 nearest_y = bound(m1_y, org_y, m2_y);
2136 nearest_z = bound(m1_z, org_z, m2_z);
2142 float vercmp_recursive(string v1, string v2)
2148 dot1 = strstrofs(v1, ".", 0);
2149 dot2 = strstrofs(v2, ".", 0);
2153 s1 = substring(v1, 0, dot1);
2157 s2 = substring(v2, 0, dot2);
2159 r = stof(s1) - stof(s2);
2163 r = strcasecmp(s1, s2);
2176 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2179 float vercmp(string v1, string v2)
2181 if(strcasecmp(v1, v2) == 0) // early out check
2190 return vercmp_recursive(v1, v2);
2193 float u8_strsize(string s)
2213 // translation helpers
2214 string language_filename(string s)
2219 if(fn == "" || fn == "dump")
2221 fn = strcat(s, ".", fn);
2222 if((fh = fopen(fn, FILE_READ)) >= 0)
2229 string CTX(string s)
2231 float p = strstrofs(s, "^", 0);
2234 return substring(s, p+1, -1);
2237 // x-encoding (encoding as zero length invisible string)
2238 const string XENCODE_2 = "xX";
2239 const string XENCODE_22 = "0123456789abcdefABCDEF";
2240 string xencode(float f)
2243 d = mod(f, 22); f = floor(f / 22);
2244 c = mod(f, 22); f = floor(f / 22);
2245 b = mod(f, 22); f = floor(f / 22);
2246 a = mod(f, 2); // f = floor(f / 2);
2249 substring(XENCODE_2, a, 1),
2250 substring(XENCODE_22, b, 1),
2251 substring(XENCODE_22, c, 1),
2252 substring(XENCODE_22, d, 1)
2255 float xdecode(string s)
2258 if(substring(s, 0, 1) != "^")
2262 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2263 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2264 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2265 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2266 if(a < 0 || b < 0 || c < 0 || d < 0)
2268 return ((a * 22 + b) * 22 + c) * 22 + d;
2271 float lowestbit(float f)
2282 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2284 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2287 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2290 // escape the string to make it safe for consoles
2291 string MakeConsoleSafe(string input)
2293 input = strreplace("\n", "", input);
2294 input = strreplace("\\", "\\\\", input);
2295 input = strreplace("$", "$$", input);
2296 input = strreplace("\"", "\\\"", input);
2301 // get true/false value of a string with multiple different inputs
2302 float InterpretBoolean(string input)
2304 switch(strtolower(input))
2316 default: return stof(input);
2322 entity ReadCSQCEntity()
2328 return findfloat(world, entnum, f);
2332 float shutdown_running;
2337 void CSQC_Shutdown()
2343 if(shutdown_running)
2345 print("Recursive shutdown detected! Only restoring cvars...\n");
2349 shutdown_running = 1;
2352 cvar_settemp_restore(); // this must be done LAST, but in any case
2355 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2356 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2357 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2358 // this will use the value:
2360 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2361 // accuracy at x is 1/derivative, i.e.
2362 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2364 void WriteApproxPastTime(float dst, float t)
2366 float dt = time - t;
2368 // warning: this is approximate; do not resend when you don't have to!
2369 // be careful with sendflags here!
2370 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2373 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2376 dt = rint(bound(0, dt, 255));
2382 float ReadApproxPastTime()
2384 float dt = ReadByte();
2386 // map from range...PPROXPASTTIME_MAX / 256
2387 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2389 return servertime - dt;
2394 .float skeleton_bones_index;
2395 void Skeleton_SetBones(entity e)
2397 // set skeleton_bones to the total number of bones on the model
2398 if(e.skeleton_bones_index == e.modelindex)
2399 return; // same model, nothing to update
2402 skelindex = skel_create(e.modelindex);
2403 e.skeleton_bones = skel_get_numbones(skelindex);
2404 skel_delete(skelindex);
2405 e.skeleton_bones_index = e.modelindex;
2409 string to_execute_next_frame;
2410 void execute_next_frame()
2412 if(to_execute_next_frame)
2414 localcmd("\n", to_execute_next_frame, "\n");
2415 strunzone(to_execute_next_frame);
2416 to_execute_next_frame = string_null;
2419 void queue_to_execute_next_frame(string s)
2421 if(to_execute_next_frame)
2423 s = strcat(s, "\n", to_execute_next_frame);
2424 strunzone(to_execute_next_frame);
2426 to_execute_next_frame = strzone(s);
2429 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2432 ((( startspeedfactor + endspeedfactor - 2
2433 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2434 ) * x + startspeedfactor
2438 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2440 if(startspeedfactor < 0 || endspeedfactor < 0)
2444 // if this is the case, the possible zeros of the first derivative are outside
2446 We can calculate this condition as condition
2451 // better, see below:
2452 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2455 // if this is the case, the first derivative has no zeros at all
2456 float se = startspeedfactor + endspeedfactor;
2457 float s_e = startspeedfactor - endspeedfactor;
2458 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2461 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2462 // we also get s_e <= 6 - se
2463 // 3 * (se - 4)^2 + (6 - se)^2
2464 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2465 // Therefore, above "better" check works!
2469 // known good cases:
2477 // (3.5, [0.2..2.3])
2481 .float FindConnectedComponent_processing;
2482 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2484 entity queue_start, queue_end;
2486 // we build a queue of to-be-processed entities.
2487 // queue_start is the next entity to be checked for neighbors
2488 // queue_end is the last entity added
2490 if(e.FindConnectedComponent_processing)
2491 error("recursion or broken cleanup");
2493 // start with a 1-element queue
2494 queue_start = queue_end = e;
2495 queue_end.fld = world;
2496 queue_end.FindConnectedComponent_processing = 1;
2498 // for each queued item:
2499 for(; queue_start; queue_start = queue_start.fld)
2501 // find all neighbors of queue_start
2503 for(t = world; (t = nxt(t, queue_start, pass)); )
2505 if(t.FindConnectedComponent_processing)
2507 if(iscon(t, queue_start, pass))
2509 // it is connected? ADD IT. It will look for neighbors soon too.
2512 queue_end.fld = world;
2513 queue_end.FindConnectedComponent_processing = 1;
2519 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2520 queue_start.FindConnectedComponent_processing = 0;
2523 // todo: this sucks, lets find a better way to do backtraces?
2525 void backtrace(string msg)
2529 dev = autocvar_developer;
2530 war = autocvar_prvm_backtraceforwarnings;
2532 dev = cvar("developer");
2533 war = cvar("prvm_backtraceforwarnings");
2535 cvar_set("developer", "1");
2536 cvar_set("prvm_backtraceforwarnings", "1");
2538 print("--- CUT HERE ---\nWARNING: ");
2541 remove(world); // isn't there any better way to cause a backtrace?
2542 print("\n--- CUT UNTIL HERE ---\n");
2543 cvar_set("developer", ftos(dev));
2544 cvar_set("prvm_backtraceforwarnings", ftos(war));
2548 // color code replace, place inside of sprintf and parse the string
2549 string CCR(string input)
2551 // See the autocvar declarations in util.qh for default values
2553 // foreground/normal colors
2554 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2555 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2556 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2557 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2560 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2561 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2562 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2564 // background colors
2565 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2566 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2570 vector vec3(float x, float y, float z)
2580 vector animfixfps(entity e, vector a, vector b)
2582 // multi-frame anim: keep as-is
2586 dur = frameduration(e.modelindex, a_x);
2590 dur = frameduration(e.modelindex, a_x);
2600 void dedicated_print(string input) // print(), but only print if the server is not local
2602 if(server_is_dedicated) { print(input); }
2607 float Announcer_PickNumber(float num)
2611 case 10: num = ANNCE_NUM_10; break;
2612 case 9: num = ANNCE_NUM_9; break;
2613 case 8: num = ANNCE_NUM_8; break;
2614 case 7: num = ANNCE_NUM_7; break;
2615 case 6: num = ANNCE_NUM_6; break;
2616 case 5: num = ANNCE_NUM_5; break;
2617 case 4: num = ANNCE_NUM_4; break;
2618 case 3: num = ANNCE_NUM_3; break;
2619 case 2: num = ANNCE_NUM_2; break;
2620 case 1: num = ANNCE_NUM_1; break;