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 mmsss(float tenths)
416 tenths = floor(tenths + 0.5);
417 minutes = floor(tenths / 600);
418 tenths -= minutes * 600;
419 s = ftos(1000 + tenths);
420 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
423 string mmssss(float hundredths)
427 hundredths = floor(hundredths + 0.5);
428 minutes = floor(hundredths / 6000);
429 hundredths -= minutes * 6000;
430 s = ftos(10000 + hundredths);
431 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
434 string ScoreString(float pFlags, float pValue)
439 pValue = floor(pValue + 0.5); // round
441 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
443 else if(pFlags & SFL_RANK)
445 valstr = ftos(pValue);
447 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
448 valstr = strcat(valstr, "th");
449 else if(substring(valstr, l - 1, 1) == "1")
450 valstr = strcat(valstr, "st");
451 else if(substring(valstr, l - 1, 1) == "2")
452 valstr = strcat(valstr, "nd");
453 else if(substring(valstr, l - 1, 1) == "3")
454 valstr = strcat(valstr, "rd");
456 valstr = strcat(valstr, "th");
458 else if(pFlags & SFL_TIME)
459 valstr = TIME_ENCODED_TOSTRING(pValue);
461 valstr = ftos(pValue);
466 vector cross(vector a, vector b)
469 '1 0 0' * (a_y * b_z - a_z * b_y)
470 + '0 1 0' * (a_z * b_x - a_x * b_z)
471 + '0 0 1' * (a_x * b_y - a_y * b_x);
474 // compressed vector format:
475 // like MD3, just even shorter
476 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
477 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
478 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
479 // length = 2^(length_encoded/8) / 8
480 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
481 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
482 // the special value 0 indicates the zero vector
484 float lengthLogTable[128];
486 float invertLengthLog(float x)
488 float l, r, m, lerr, rerr;
490 if(x >= lengthLogTable[127])
492 if(x <= lengthLogTable[0])
500 m = floor((l + r) / 2);
501 if(lengthLogTable[m] < x)
507 // now: r is >=, l is <
508 lerr = (x - lengthLogTable[l]);
509 rerr = (lengthLogTable[r] - x);
515 vector decompressShortVector(float data)
521 p = (data & 0xF000) / 0x1000;
522 y = (data & 0x0F80) / 0x80;
523 len = (data & 0x007F);
525 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
538 y = .19634954084936207740 * y;
539 p = .19634954084936207740 * p - 1.57079632679489661922;
540 out_x = cos(y) * cos(p);
541 out_y = sin(y) * cos(p);
545 //print("decompressed: ", vtos(out), "\n");
547 return out * lengthLogTable[len];
550 float compressShortVector(vector vec)
556 //print("compress: ", vtos(vec), "\n");
557 ang = vectoangles(vec);
561 if(ang_x < -90 && ang_x > +90)
562 error("BOGUS vectoangles");
563 //print("angles: ", vtos(ang), "\n");
565 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
574 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
575 len = invertLengthLog(vlen(vec));
577 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
579 return (p * 0x1000) + (y * 0x80) + len;
582 void compressShortVector_init()
587 for(i = 0; i < 128; ++i)
589 lengthLogTable[i] = l;
593 if(cvar("developer"))
595 print("Verifying vector compression table...\n");
596 for(i = 0x0F00; i < 0xFFFF; ++i)
597 if(i != compressShortVector(decompressShortVector(i)))
599 print("BROKEN vector compression: ", ftos(i));
600 print(" -> ", vtos(decompressShortVector(i)));
601 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
610 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
612 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
619 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
620 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
628 string fixPriorityList(string order, float from, float to, float subtract, float complete)
633 n = tokenize_console(order);
635 for(i = 0; i < n; ++i)
640 if(w >= from && w <= to)
641 neworder = strcat(neworder, ftos(w), " ");
645 if(w >= from && w <= to)
646 neworder = strcat(neworder, ftos(w), " ");
653 n = tokenize_console(neworder);
654 for(w = to; w >= from; --w)
656 for(i = 0; i < n; ++i)
657 if(stof(argv(i)) == w)
659 if(i == n) // not found
660 neworder = strcat(neworder, ftos(w), " ");
664 return substring(neworder, 0, strlen(neworder) - 1);
667 string mapPriorityList(string order, string(string) mapfunc)
672 n = tokenize_console(order);
674 for(i = 0; i < n; ++i)
675 neworder = strcat(neworder, mapfunc(argv(i)), " ");
677 return substring(neworder, 0, strlen(neworder) - 1);
680 string swapInPriorityList(string order, float i, float j)
685 n = tokenize_console(order);
687 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
690 for(w = 0; w < n; ++w)
693 s = strcat(s, argv(j), " ");
695 s = strcat(s, argv(i), " ");
697 s = strcat(s, argv(w), " ");
699 return substring(s, 0, strlen(s) - 1);
705 float cvar_value_issafe(string s)
707 if(strstrofs(s, "\"", 0) >= 0)
709 if(strstrofs(s, "\\", 0) >= 0)
711 if(strstrofs(s, ";", 0) >= 0)
713 if(strstrofs(s, "$", 0) >= 0)
715 if(strstrofs(s, "\r", 0) >= 0)
717 if(strstrofs(s, "\n", 0) >= 0)
723 void get_mi_min_max(float mode)
728 strunzone(mi_shortname);
729 mi_shortname = mapname;
730 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
731 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
732 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
733 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
734 mi_shortname = strzone(mi_shortname);
746 MapInfo_Get_ByName(mi_shortname, 0, 0);
747 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
749 mi_min = MapInfo_Map_mins;
750 mi_max = MapInfo_Map_maxs;
758 tracebox('1 0 0' * mi_x,
759 '0 1 0' * mi_y + '0 0 1' * mi_z,
760 '0 1 0' * ma_y + '0 0 1' * ma_z,
764 if(!trace_startsolid)
765 mi_min_x = trace_endpos_x;
767 tracebox('0 1 0' * mi_y,
768 '1 0 0' * mi_x + '0 0 1' * mi_z,
769 '1 0 0' * ma_x + '0 0 1' * ma_z,
773 if(!trace_startsolid)
774 mi_min_y = trace_endpos_y;
776 tracebox('0 0 1' * mi_z,
777 '1 0 0' * mi_x + '0 1 0' * mi_y,
778 '1 0 0' * ma_x + '0 1 0' * ma_y,
782 if(!trace_startsolid)
783 mi_min_z = trace_endpos_z;
785 tracebox('1 0 0' * ma_x,
786 '0 1 0' * mi_y + '0 0 1' * mi_z,
787 '0 1 0' * ma_y + '0 0 1' * ma_z,
791 if(!trace_startsolid)
792 mi_max_x = trace_endpos_x;
794 tracebox('0 1 0' * ma_y,
795 '1 0 0' * mi_x + '0 0 1' * mi_z,
796 '1 0 0' * ma_x + '0 0 1' * ma_z,
800 if(!trace_startsolid)
801 mi_max_y = trace_endpos_y;
803 tracebox('0 0 1' * ma_z,
804 '1 0 0' * mi_x + '0 1 0' * mi_y,
805 '1 0 0' * ma_x + '0 1 0' * ma_y,
809 if(!trace_startsolid)
810 mi_max_z = trace_endpos_z;
815 void get_mi_min_max_texcoords(float mode)
819 get_mi_min_max(mode);
824 // extend mi_picmax to get a square aspect ratio
825 // center the map in that area
826 extend = mi_picmax - mi_picmin;
827 if(extend_y > extend_x)
829 mi_picmin_x -= (extend_y - extend_x) * 0.5;
830 mi_picmax_x += (extend_y - extend_x) * 0.5;
834 mi_picmin_y -= (extend_x - extend_y) * 0.5;
835 mi_picmax_y += (extend_x - extend_y) * 0.5;
838 // add another some percent
839 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
843 // calculate the texcoords
844 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
845 // first the two corners of the origin
846 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
847 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
848 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
849 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
850 // then the other corners
851 mi_pictexcoord1_x = mi_pictexcoord0_x;
852 mi_pictexcoord1_y = mi_pictexcoord2_y;
853 mi_pictexcoord3_x = mi_pictexcoord2_x;
854 mi_pictexcoord3_y = mi_pictexcoord0_y;
858 float cvar_settemp(string tmp_cvar, string tmp_value)
860 float created_saved_value;
863 created_saved_value = FALSE;
865 if not(tmp_cvar || tmp_value)
867 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
871 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
872 if(e.netname == tmp_cvar)
873 goto saved; // skip creation
875 // creating a new entity to keep track of this cvar
877 e.classname = "saved_cvar_value";
878 e.netname = strzone(tmp_cvar);
879 e.message = strzone(cvar_string(tmp_cvar));
880 created_saved_value = TRUE;
882 // an entity for this cvar already exists
885 // update the cvar to the value given
886 cvar_set(tmp_cvar, tmp_value);
888 return created_saved_value;
891 float cvar_settemp_restore()
895 while((e = find(world, classname, "saved_cvar_value")))
897 cvar_set(e.netname, e.message);
904 float almost_equals(float a, float b)
907 eps = (max(a, -a) + max(b, -b)) * 0.001;
908 if(a - b < eps && b - a < eps)
913 float almost_in_bounds(float a, float b, float c)
916 eps = (max(a, -a) + max(c, -c)) * 0.001;
919 return b == median(a - eps, b, c + eps);
922 float power2of(float e)
926 float log2of(float x)
928 // NOTE: generated code
1001 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1005 else if(ma == rgb_x)
1008 return (rgb_y - rgb_z) / (ma - mi);
1010 return (rgb_y - rgb_z) / (ma - mi) + 6;
1012 else if(ma == rgb_y)
1013 return (rgb_z - rgb_x) / (ma - mi) + 2;
1014 else // if(ma == rgb_z)
1015 return (rgb_x - rgb_y) / (ma - mi) + 4;
1018 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1022 hue -= 6 * floor(hue / 6);
1024 //else if(ma == rgb_x)
1025 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1029 rgb_y = hue * (ma - mi) + mi;
1032 //else if(ma == rgb_y)
1033 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1036 rgb_x = (2 - hue) * (ma - mi) + mi;
1044 rgb_z = (hue - 2) * (ma - mi) + mi;
1046 //else // if(ma == rgb_z)
1047 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1051 rgb_y = (4 - hue) * (ma - mi) + mi;
1056 rgb_x = (hue - 4) * (ma - mi) + mi;
1060 //else if(ma == rgb_x)
1061 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1062 else // if(hue <= 6)
1066 rgb_z = (6 - hue) * (ma - mi) + mi;
1072 vector rgb_to_hsv(vector rgb)
1077 mi = min(rgb_x, rgb_y, rgb_z);
1078 ma = max(rgb_x, rgb_y, rgb_z);
1080 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1091 vector hsv_to_rgb(vector hsv)
1093 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1096 vector rgb_to_hsl(vector rgb)
1101 mi = min(rgb_x, rgb_y, rgb_z);
1102 ma = max(rgb_x, rgb_y, rgb_z);
1104 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1106 hsl_z = 0.5 * (mi + ma);
1109 else if(hsl_z <= 0.5)
1110 hsl_y = (ma - mi) / (2*hsl_z);
1111 else // if(hsl_z > 0.5)
1112 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1117 vector hsl_to_rgb(vector hsl)
1119 float mi, ma, maminusmi;
1122 maminusmi = hsl_y * 2 * hsl_z;
1124 maminusmi = hsl_y * (2 - 2 * hsl_z);
1126 // hsl_z = 0.5 * mi + 0.5 * ma
1127 // maminusmi = - mi + ma
1128 mi = hsl_z - 0.5 * maminusmi;
1129 ma = hsl_z + 0.5 * maminusmi;
1131 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1134 string rgb_to_hexcolor(vector rgb)
1139 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1140 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1141 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1145 // requires that m2>m1 in all coordinates, and that m4>m3
1146 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;}
1148 // requires the same, but is a stronger condition
1149 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;}
1154 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1157 // The following function is SLOW.
1158 // For your safety and for the protection of those around you...
1159 // DO NOT CALL THIS AT HOME.
1160 // No really, don't.
1161 if(w(theText, theSize) <= maxWidth)
1162 return strlen(theText); // yeah!
1164 // binary search for right place to cut string
1166 float left, right, middle; // this always works
1168 right = strlen(theText); // this always fails
1171 middle = floor((left + right) / 2);
1172 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1177 while(left < right - 1);
1179 if(w("^7", theSize) == 0) // detect color codes support in the width function
1181 // NOTE: when color codes are involved, this binary search is,
1182 // mathematically, BROKEN. However, it is obviously guaranteed to
1183 // terminate, as the range still halves each time - but nevertheless, it is
1184 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1185 // range, and "right" is outside).
1187 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1188 // and decrease left on the basis of the chars detected of the truncated tag
1189 // Even if the ^xrgb tag is not complete/correct, left is decreased
1190 // (sometimes too much but with a correct result)
1191 // it fixes also ^[0-9]
1192 while(left >= 1 && substring(theText, left-1, 1) == "^")
1195 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1197 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1199 ch = str2chr(theText, left-1);
1200 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1203 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1205 ch = str2chr(theText, left-2);
1206 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1208 ch = str2chr(theText, left-1);
1209 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1218 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1221 // The following function is SLOW.
1222 // For your safety and for the protection of those around you...
1223 // DO NOT CALL THIS AT HOME.
1224 // No really, don't.
1225 if(w(theText) <= maxWidth)
1226 return strlen(theText); // yeah!
1228 // binary search for right place to cut string
1230 float left, right, middle; // this always works
1232 right = strlen(theText); // this always fails
1235 middle = floor((left + right) / 2);
1236 if(w(substring(theText, 0, middle)) <= maxWidth)
1241 while(left < right - 1);
1243 if(w("^7") == 0) // detect color codes support in the width function
1245 // NOTE: when color codes are involved, this binary search is,
1246 // mathematically, BROKEN. However, it is obviously guaranteed to
1247 // terminate, as the range still halves each time - but nevertheless, it is
1248 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1249 // range, and "right" is outside).
1251 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1252 // and decrease left on the basis of the chars detected of the truncated tag
1253 // Even if the ^xrgb tag is not complete/correct, left is decreased
1254 // (sometimes too much but with a correct result)
1255 // it fixes also ^[0-9]
1256 while(left >= 1 && substring(theText, left-1, 1) == "^")
1259 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1261 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1263 ch = str2chr(theText, left-1);
1264 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1267 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1269 ch = str2chr(theText, left-2);
1270 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1272 ch = str2chr(theText, left-1);
1273 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1282 string find_last_color_code(string s)
1284 float start, len, i, carets;
1285 start = strstrofs(s, "^", 0);
1286 if (start == -1) // no caret found
1289 for(i = len; i >= start; --i)
1291 if(substring(s, i, 1) != "^")
1295 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1298 // check if carets aren't all escaped
1299 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1302 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1303 return substring(s, i, 2);
1306 if(substring(s, i+1, 1) == "x")
1307 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1308 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1309 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1310 return substring(s, i, 5);
1312 i -= carets; // this also skips one char before the carets
1318 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1324 s = getWrappedLine_remaining;
1328 getWrappedLine_remaining = string_null;
1329 return s; // the line has no size ANYWAY, nothing would be displayed.
1332 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1333 if(cantake > 0 && cantake < strlen(s))
1336 while(take > 0 && substring(s, take, 1) != " ")
1340 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1341 if(getWrappedLine_remaining == "")
1342 getWrappedLine_remaining = string_null;
1343 else if (tw("^7", theFontSize) == 0)
1344 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1345 return substring(s, 0, cantake);
1349 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1350 if(getWrappedLine_remaining == "")
1351 getWrappedLine_remaining = string_null;
1352 else if (tw("^7", theFontSize) == 0)
1353 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1354 return substring(s, 0, take);
1359 getWrappedLine_remaining = string_null;
1364 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1370 s = getWrappedLine_remaining;
1374 getWrappedLine_remaining = string_null;
1375 return s; // the line has no size ANYWAY, nothing would be displayed.
1378 cantake = textLengthUpToLength(s, w, tw);
1379 if(cantake > 0 && cantake < strlen(s))
1382 while(take > 0 && substring(s, take, 1) != " ")
1386 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1387 if(getWrappedLine_remaining == "")
1388 getWrappedLine_remaining = string_null;
1389 else if (tw("^7") == 0)
1390 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1391 return substring(s, 0, cantake);
1395 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1396 if(getWrappedLine_remaining == "")
1397 getWrappedLine_remaining = string_null;
1398 else if (tw("^7") == 0)
1399 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1400 return substring(s, 0, take);
1405 getWrappedLine_remaining = string_null;
1410 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1412 if(tw(theText, theFontSize) <= maxWidth)
1415 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1418 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1420 if(tw(theText) <= maxWidth)
1423 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1426 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1428 string subpattern, subpattern2, subpattern3, subpattern4;
1429 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1431 subpattern2 = ",teams,";
1433 subpattern2 = ",noteams,";
1435 subpattern3 = ",teamspawns,";
1437 subpattern3 = ",noteamspawns,";
1438 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1439 subpattern4 = ",race,";
1441 subpattern4 = string_null;
1443 if(substring(pattern, 0, 1) == "-")
1445 pattern = substring(pattern, 1, strlen(pattern) - 1);
1446 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1448 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1450 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1452 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1457 if(substring(pattern, 0, 1) == "+")
1458 pattern = substring(pattern, 1, strlen(pattern) - 1);
1459 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1460 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1461 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1462 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1468 void shuffle(float n, swapfunc_t swap, entity pass)
1471 for(i = 1; i < n; ++i)
1473 // swap i-th item at a random position from 0 to i
1474 // proof for even distribution:
1477 // item n+1 gets at any position with chance 1/(n+1)
1478 // all others will get their 1/n chance reduced by factor n/(n+1)
1479 // to be on place n+1, their chance will be 1/(n+1)
1480 // 1/n * n/(n+1) = 1/(n+1)
1482 j = floor(random() * (i + 1));
1488 string substring_range(string s, float b, float e)
1490 return substring(s, b, e - b);
1493 string swapwords(string str, float i, float j)
1496 string s1, s2, s3, s4, s5;
1497 float si, ei, sj, ej, s0, en;
1498 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1499 si = argv_start_index(i);
1500 sj = argv_start_index(j);
1501 ei = argv_end_index(i);
1502 ej = argv_end_index(j);
1503 s0 = argv_start_index(0);
1504 en = argv_end_index(n-1);
1505 s1 = substring_range(str, s0, si);
1506 s2 = substring_range(str, si, ei);
1507 s3 = substring_range(str, ei, sj);
1508 s4 = substring_range(str, sj, ej);
1509 s5 = substring_range(str, ej, en);
1510 return strcat(s1, s4, s3, s2, s5);
1513 string _shufflewords_str;
1514 void _shufflewords_swapfunc(float i, float j, entity pass)
1516 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1518 string shufflewords(string str)
1521 _shufflewords_str = str;
1522 n = tokenizebyseparator(str, " ");
1523 shuffle(n, _shufflewords_swapfunc, world);
1524 str = _shufflewords_str;
1525 _shufflewords_str = string_null;
1529 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1545 // actually, every number solves the equation!
1556 if(a > 0) // put the smaller solution first
1558 v_x = ((-b)-D) / (2*a);
1559 v_y = ((-b)+D) / (2*a);
1563 v_x = (-b+D) / (2*a);
1564 v_y = (-b-D) / (2*a);
1570 // complex solutions!
1583 void check_unacceptable_compiler_bugs()
1585 if(cvar("_allow_unacceptable_compiler_bugs"))
1587 tokenize_console("foo bar");
1588 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1589 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.");
1593 error("The empty string counts as false. We do not want that!");
1596 float compressShotOrigin(vector v)
1600 y = rint(v_y * 4) + 128;
1601 z = rint(v_z * 4) + 128;
1602 if(x > 255 || x < 0)
1604 print("shot origin ", vtos(v), " x out of bounds\n");
1605 x = bound(0, x, 255);
1607 if(y > 255 || y < 0)
1609 print("shot origin ", vtos(v), " y out of bounds\n");
1610 y = bound(0, y, 255);
1612 if(z > 255 || z < 0)
1614 print("shot origin ", vtos(v), " z out of bounds\n");
1615 z = bound(0, z, 255);
1617 return x * 0x10000 + y * 0x100 + z;
1619 vector decompressShotOrigin(float f)
1622 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1623 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1624 v_z = ((f & 0xFF) - 128) / 4;
1628 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1630 float start, end, root, child;
1633 start = floor((n - 2) / 2);
1636 // siftdown(start, count-1);
1638 while(root * 2 + 1 <= n-1)
1640 child = root * 2 + 1;
1642 if(cmp(child, child+1, pass) < 0)
1644 if(cmp(root, child, pass) < 0)
1646 swap(root, child, pass);
1662 // siftdown(0, end);
1664 while(root * 2 + 1 <= end)
1666 child = root * 2 + 1;
1667 if(child < end && cmp(child, child+1, pass) < 0)
1669 if(cmp(root, child, pass) < 0)
1671 swap(root, child, pass);
1681 void RandomSelection_Init()
1683 RandomSelection_totalweight = 0;
1684 RandomSelection_chosen_ent = world;
1685 RandomSelection_chosen_float = 0;
1686 RandomSelection_chosen_string = string_null;
1687 RandomSelection_best_priority = -1;
1689 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1691 if(priority > RandomSelection_best_priority)
1693 RandomSelection_best_priority = priority;
1694 RandomSelection_chosen_ent = e;
1695 RandomSelection_chosen_float = f;
1696 RandomSelection_chosen_string = s;
1697 RandomSelection_totalweight = weight;
1699 else if(priority == RandomSelection_best_priority)
1701 RandomSelection_totalweight += weight;
1702 if(random() * RandomSelection_totalweight <= weight)
1704 RandomSelection_chosen_ent = e;
1705 RandomSelection_chosen_float = f;
1706 RandomSelection_chosen_string = s;
1711 vector healtharmor_maxdamage(float h, float a, float armorblock)
1713 // NOTE: we'll always choose the SMALLER value...
1714 float healthdamage, armordamage, armorideal;
1716 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1717 armordamage = a + (h - 1); // damage we can take if we could use more armor
1718 armorideal = healthdamage * armorblock;
1720 if(armordamage < healthdamage)
1733 vector healtharmor_applydamage(float a, float armorblock, float damage)
1736 v_y = bound(0, damage * armorblock, a); // save
1737 v_x = bound(0, damage - v_y, damage); // take
1742 string getcurrentmod()
1746 m = cvar_string("fs_gamedir");
1747 n = tokenize_console(m);
1759 v = ReadShort() * 256; // note: this is signed
1760 v += ReadByte(); // note: this is unsigned
1764 void WriteInt24_t(float dst, float val)
1767 WriteShort(dst, (v = floor(val / 256)));
1768 WriteByte(dst, val - v * 256); // 0..255
1773 float float2range11(float f)
1775 // continuous function mapping all reals into -1..1
1776 return f / (fabs(f) + 1);
1779 float float2range01(float f)
1781 // continuous function mapping all reals into 0..1
1782 return 0.5 + 0.5 * float2range11(f);
1785 // from the GNU Scientific Library
1786 float gsl_ran_gaussian_lastvalue;
1787 float gsl_ran_gaussian_lastvalue_set;
1788 float gsl_ran_gaussian(float sigma)
1791 if(gsl_ran_gaussian_lastvalue_set)
1793 gsl_ran_gaussian_lastvalue_set = 0;
1794 return sigma * gsl_ran_gaussian_lastvalue;
1798 a = random() * 2 * M_PI;
1799 b = sqrt(-2 * log(random()));
1800 gsl_ran_gaussian_lastvalue = cos(a) * b;
1801 gsl_ran_gaussian_lastvalue_set = 1;
1802 return sigma * sin(a) * b;
1806 string car(string s)
1809 o = strstrofs(s, " ", 0);
1812 return substring(s, 0, o);
1814 string cdr(string s)
1817 o = strstrofs(s, " ", 0);
1820 return substring(s, o + 1, strlen(s) - (o + 1));
1822 float matchacl(string acl, string str)
1829 t = car(acl); acl = cdr(acl);
1832 if(substring(t, 0, 1) == "-")
1835 t = substring(t, 1, strlen(t) - 1);
1837 else if(substring(t, 0, 1) == "+")
1838 t = substring(t, 1, strlen(t) - 1);
1840 if(substring(t, -1, 1) == "*")
1842 t = substring(t, 0, strlen(t) - 1);
1843 s = substring(str, 0, strlen(t));
1855 float startsWith(string haystack, string needle)
1857 return substring(haystack, 0, strlen(needle)) == needle;
1859 float startsWithNocase(string haystack, string needle)
1861 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1864 string get_model_datafilename(string m, float sk, string fil)
1869 m = "models/player/*_";
1871 m = strcat(m, ftos(sk));
1874 return strcat(m, ".", fil);
1877 float get_model_parameters(string m, float sk)
1882 get_model_parameters_modelname = string_null;
1883 get_model_parameters_modelskin = -1;
1884 get_model_parameters_name = string_null;
1885 get_model_parameters_species = -1;
1886 get_model_parameters_sex = string_null;
1887 get_model_parameters_weight = -1;
1888 get_model_parameters_age = -1;
1889 get_model_parameters_desc = string_null;
1895 if(substring(m, -4, -1) != ".txt")
1897 if(substring(m, -6, 1) != "_")
1899 sk = stof(substring(m, -5, 1));
1900 m = substring(m, 0, -7);
1903 fn = get_model_datafilename(m, sk, "txt");
1904 fh = fopen(fn, FILE_READ);
1908 fn = get_model_datafilename(m, sk, "txt");
1909 fh = fopen(fn, FILE_READ);
1914 get_model_parameters_modelname = m;
1915 get_model_parameters_modelskin = sk;
1916 while((s = fgets(fh)))
1919 break; // next lines will be description
1923 get_model_parameters_name = s;
1927 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1928 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1929 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1930 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1931 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1932 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1933 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1936 get_model_parameters_sex = s;
1938 get_model_parameters_weight = stof(s);
1940 get_model_parameters_age = stof(s);
1943 while((s = fgets(fh)))
1945 if(get_model_parameters_desc)
1946 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1948 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1956 vector vec2(vector v)
1963 vector NearestPointOnBox(entity box, vector org)
1965 vector m1, m2, nearest;
1967 m1 = box.mins + box.origin;
1968 m2 = box.maxs + box.origin;
1970 nearest_x = bound(m1_x, org_x, m2_x);
1971 nearest_y = bound(m1_y, org_y, m2_y);
1972 nearest_z = bound(m1_z, org_z, m2_z);
1978 float vercmp_recursive(string v1, string v2)
1984 dot1 = strstrofs(v1, ".", 0);
1985 dot2 = strstrofs(v2, ".", 0);
1989 s1 = substring(v1, 0, dot1);
1993 s2 = substring(v2, 0, dot2);
1995 r = stof(s1) - stof(s2);
1999 r = strcasecmp(s1, s2);
2012 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2015 float vercmp(string v1, string v2)
2017 if(strcasecmp(v1, v2) == 0) // early out check
2026 return vercmp_recursive(v1, v2);
2029 float u8_strsize(string s)
2049 // translation helpers
2050 string language_filename(string s)
2055 if(fn == "" || fn == "dump")
2057 fn = strcat(s, ".", fn);
2058 if((fh = fopen(fn, FILE_READ)) >= 0)
2065 string CTX(string s)
2067 float p = strstrofs(s, "^", 0);
2070 return substring(s, p+1, -1);
2073 // x-encoding (encoding as zero length invisible string)
2074 const string XENCODE_2 = "xX";
2075 const string XENCODE_22 = "0123456789abcdefABCDEF";
2076 string xencode(float f)
2079 d = mod(f, 22); f = floor(f / 22);
2080 c = mod(f, 22); f = floor(f / 22);
2081 b = mod(f, 22); f = floor(f / 22);
2082 a = mod(f, 2); // f = floor(f / 2);
2085 substring(XENCODE_2, a, 1),
2086 substring(XENCODE_22, b, 1),
2087 substring(XENCODE_22, c, 1),
2088 substring(XENCODE_22, d, 1)
2091 float xdecode(string s)
2094 if(substring(s, 0, 1) != "^")
2098 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2099 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2100 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2101 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2102 if(a < 0 || b < 0 || c < 0 || d < 0)
2104 return ((a * 22 + b) * 22 + c) * 22 + d;
2107 float lowestbit(float f)
2118 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2120 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2123 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2126 // escape the string to make it safe for consoles
2127 string MakeConsoleSafe(string input)
2129 input = strreplace("\n", "", input);
2130 input = strreplace("\\", "\\\\", input);
2131 input = strreplace("$", "$$", input);
2132 input = strreplace("\"", "\\\"", input);
2137 // get true/false value of a string with multiple different inputs
2138 float InterpretBoolean(string input)
2140 switch(strtolower(input))
2152 default: return stof(input);
2158 entity ReadCSQCEntity()
2164 return findfloat(world, entnum, f);
2168 float shutdown_running;
2173 void CSQC_Shutdown()
2179 if(shutdown_running)
2181 print("Recursive shutdown detected! Only restoring cvars...\n");
2185 shutdown_running = 1;
2188 cvar_settemp_restore(); // this must be done LAST, but in any case
2191 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2192 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2193 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2194 // this will use the value:
2196 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2197 // accuracy at x is 1/derivative, i.e.
2198 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2200 void WriteApproxPastTime(float dst, float t)
2202 float dt = time - t;
2204 // warning: this is approximate; do not resend when you don't have to!
2205 // be careful with sendflags here!
2206 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2209 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2212 dt = rint(bound(0, dt, 255));
2218 float ReadApproxPastTime()
2220 float dt = ReadByte();
2222 // map from range...PPROXPASTTIME_MAX / 256
2223 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2225 return servertime - dt;
2230 .float skeleton_bones_index;
2231 void Skeleton_SetBones(entity e)
2233 // set skeleton_bones to the total number of bones on the model
2234 if(e.skeleton_bones_index == e.modelindex)
2235 return; // same model, nothing to update
2238 skelindex = skel_create(e.modelindex);
2239 e.skeleton_bones = skel_get_numbones(skelindex);
2240 skel_delete(skelindex);
2241 e.skeleton_bones_index = e.modelindex;
2245 string to_execute_next_frame;
2246 void execute_next_frame()
2248 if(to_execute_next_frame)
2250 localcmd("\n", to_execute_next_frame, "\n");
2251 strunzone(to_execute_next_frame);
2252 to_execute_next_frame = string_null;
2255 void queue_to_execute_next_frame(string s)
2257 if(to_execute_next_frame)
2259 s = strcat(s, "\n", to_execute_next_frame);
2260 strunzone(to_execute_next_frame);
2262 to_execute_next_frame = strzone(s);