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 if not(tmp_cvar || tmp_value)
865 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
869 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
870 if(e.netname == tmp_cvar)
871 goto saved; // skip creation
873 // creating a new entity to keep track of this cvar
875 e.classname = "saved_cvar_value";
876 e.netname = strzone(tmp_cvar);
877 e.message = strzone(cvar_string(tmp_cvar));
878 created_saved_value = TRUE;
880 // an entity for this cvar already exists
883 // update the cvar to the value given
884 cvar_set(tmp_cvar, tmp_value);
886 return created_saved_value;
889 float cvar_settemp_restore()
893 while((e = find(world, classname, "saved_cvar_value")))
895 cvar_set(e.netname, e.message);
902 float almost_equals(float a, float b)
905 eps = (max(a, -a) + max(b, -b)) * 0.001;
906 if(a - b < eps && b - a < eps)
911 float almost_in_bounds(float a, float b, float c)
914 eps = (max(a, -a) + max(c, -c)) * 0.001;
917 return b == median(a - eps, b, c + eps);
920 float power2of(float e)
924 float log2of(float x)
926 // NOTE: generated code
999 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1003 else if(ma == rgb_x)
1006 return (rgb_y - rgb_z) / (ma - mi);
1008 return (rgb_y - rgb_z) / (ma - mi) + 6;
1010 else if(ma == rgb_y)
1011 return (rgb_z - rgb_x) / (ma - mi) + 2;
1012 else // if(ma == rgb_z)
1013 return (rgb_x - rgb_y) / (ma - mi) + 4;
1016 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1020 hue -= 6 * floor(hue / 6);
1022 //else if(ma == rgb_x)
1023 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1027 rgb_y = hue * (ma - mi) + mi;
1030 //else if(ma == rgb_y)
1031 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1034 rgb_x = (2 - hue) * (ma - mi) + mi;
1042 rgb_z = (hue - 2) * (ma - mi) + mi;
1044 //else // if(ma == rgb_z)
1045 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1049 rgb_y = (4 - hue) * (ma - mi) + mi;
1054 rgb_x = (hue - 4) * (ma - mi) + mi;
1058 //else if(ma == rgb_x)
1059 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1060 else // if(hue <= 6)
1064 rgb_z = (6 - hue) * (ma - mi) + mi;
1070 vector rgb_to_hsv(vector rgb)
1075 mi = min(rgb_x, rgb_y, rgb_z);
1076 ma = max(rgb_x, rgb_y, rgb_z);
1078 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1089 vector hsv_to_rgb(vector hsv)
1091 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1094 vector rgb_to_hsl(vector rgb)
1099 mi = min(rgb_x, rgb_y, rgb_z);
1100 ma = max(rgb_x, rgb_y, rgb_z);
1102 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1104 hsl_z = 0.5 * (mi + ma);
1107 else if(hsl_z <= 0.5)
1108 hsl_y = (ma - mi) / (2*hsl_z);
1109 else // if(hsl_z > 0.5)
1110 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1115 vector hsl_to_rgb(vector hsl)
1117 float mi, ma, maminusmi;
1120 maminusmi = hsl_y * 2 * hsl_z;
1122 maminusmi = hsl_y * (2 - 2 * hsl_z);
1124 // hsl_z = 0.5 * mi + 0.5 * ma
1125 // maminusmi = - mi + ma
1126 mi = hsl_z - 0.5 * maminusmi;
1127 ma = hsl_z + 0.5 * maminusmi;
1129 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1132 string rgb_to_hexcolor(vector rgb)
1137 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1138 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1139 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1143 // requires that m2>m1 in all coordinates, and that m4>m3
1144 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;}
1146 // requires the same, but is a stronger condition
1147 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;}
1152 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1155 // The following function is SLOW.
1156 // For your safety and for the protection of those around you...
1157 // DO NOT CALL THIS AT HOME.
1158 // No really, don't.
1159 if(w(theText, theSize) <= maxWidth)
1160 return strlen(theText); // yeah!
1162 // binary search for right place to cut string
1164 float left, right, middle; // this always works
1166 right = strlen(theText); // this always fails
1169 middle = floor((left + right) / 2);
1170 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1175 while(left < right - 1);
1177 if(w("^7", theSize) == 0) // detect color codes support in the width function
1179 // NOTE: when color codes are involved, this binary search is,
1180 // mathematically, BROKEN. However, it is obviously guaranteed to
1181 // terminate, as the range still halves each time - but nevertheless, it is
1182 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1183 // range, and "right" is outside).
1185 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1186 // and decrease left on the basis of the chars detected of the truncated tag
1187 // Even if the ^xrgb tag is not complete/correct, left is decreased
1188 // (sometimes too much but with a correct result)
1189 // it fixes also ^[0-9]
1190 while(left >= 1 && substring(theText, left-1, 1) == "^")
1193 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1195 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1197 ch = str2chr(theText, left-1);
1198 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1201 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1203 ch = str2chr(theText, left-2);
1204 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1206 ch = str2chr(theText, left-1);
1207 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1216 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1219 // The following function is SLOW.
1220 // For your safety and for the protection of those around you...
1221 // DO NOT CALL THIS AT HOME.
1222 // No really, don't.
1223 if(w(theText) <= maxWidth)
1224 return strlen(theText); // yeah!
1226 // binary search for right place to cut string
1228 float left, right, middle; // this always works
1230 right = strlen(theText); // this always fails
1233 middle = floor((left + right) / 2);
1234 if(w(substring(theText, 0, middle)) <= maxWidth)
1239 while(left < right - 1);
1241 if(w("^7") == 0) // detect color codes support in the width function
1243 // NOTE: when color codes are involved, this binary search is,
1244 // mathematically, BROKEN. However, it is obviously guaranteed to
1245 // terminate, as the range still halves each time - but nevertheless, it is
1246 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1247 // range, and "right" is outside).
1249 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1250 // and decrease left on the basis of the chars detected of the truncated tag
1251 // Even if the ^xrgb tag is not complete/correct, left is decreased
1252 // (sometimes too much but with a correct result)
1253 // it fixes also ^[0-9]
1254 while(left >= 1 && substring(theText, left-1, 1) == "^")
1257 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1259 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1261 ch = str2chr(theText, left-1);
1262 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1265 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1267 ch = str2chr(theText, left-2);
1268 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1270 ch = str2chr(theText, left-1);
1271 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1280 string find_last_color_code(string s)
1282 float start, len, i, carets;
1283 start = strstrofs(s, "^", 0);
1284 if (start == -1) // no caret found
1287 for(i = len; i >= start; --i)
1289 if(substring(s, i, 1) != "^")
1293 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1296 // check if carets aren't all escaped
1297 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1300 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1301 return substring(s, i, 2);
1304 if(substring(s, i+1, 1) == "x")
1305 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1306 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1307 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1308 return substring(s, i, 5);
1310 i -= carets; // this also skips one char before the carets
1316 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1322 s = getWrappedLine_remaining;
1326 getWrappedLine_remaining = string_null;
1327 return s; // the line has no size ANYWAY, nothing would be displayed.
1330 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1331 if(cantake > 0 && cantake < strlen(s))
1334 while(take > 0 && substring(s, take, 1) != " ")
1338 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1339 if(getWrappedLine_remaining == "")
1340 getWrappedLine_remaining = string_null;
1341 else if (tw("^7", theFontSize) == 0)
1342 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1343 return substring(s, 0, cantake);
1347 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1348 if(getWrappedLine_remaining == "")
1349 getWrappedLine_remaining = string_null;
1350 else if (tw("^7", theFontSize) == 0)
1351 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1352 return substring(s, 0, take);
1357 getWrappedLine_remaining = string_null;
1362 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1368 s = getWrappedLine_remaining;
1372 getWrappedLine_remaining = string_null;
1373 return s; // the line has no size ANYWAY, nothing would be displayed.
1376 cantake = textLengthUpToLength(s, w, tw);
1377 if(cantake > 0 && cantake < strlen(s))
1380 while(take > 0 && substring(s, take, 1) != " ")
1384 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1385 if(getWrappedLine_remaining == "")
1386 getWrappedLine_remaining = string_null;
1387 else if (tw("^7") == 0)
1388 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1389 return substring(s, 0, cantake);
1393 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1394 if(getWrappedLine_remaining == "")
1395 getWrappedLine_remaining = string_null;
1396 else if (tw("^7") == 0)
1397 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1398 return substring(s, 0, take);
1403 getWrappedLine_remaining = string_null;
1408 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1410 if(tw(theText, theFontSize) <= maxWidth)
1413 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1416 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1418 if(tw(theText) <= maxWidth)
1421 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1424 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1426 string subpattern, subpattern2, subpattern3, subpattern4;
1427 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1429 subpattern2 = ",teams,";
1431 subpattern2 = ",noteams,";
1433 subpattern3 = ",teamspawns,";
1435 subpattern3 = ",noteamspawns,";
1436 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1437 subpattern4 = ",race,";
1439 subpattern4 = string_null;
1441 if(substring(pattern, 0, 1) == "-")
1443 pattern = substring(pattern, 1, strlen(pattern) - 1);
1444 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1446 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1448 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1450 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1455 if(substring(pattern, 0, 1) == "+")
1456 pattern = substring(pattern, 1, strlen(pattern) - 1);
1457 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1458 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1459 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1460 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1466 void shuffle(float n, swapfunc_t swap, entity pass)
1469 for(i = 1; i < n; ++i)
1471 // swap i-th item at a random position from 0 to i
1472 // proof for even distribution:
1475 // item n+1 gets at any position with chance 1/(n+1)
1476 // all others will get their 1/n chance reduced by factor n/(n+1)
1477 // to be on place n+1, their chance will be 1/(n+1)
1478 // 1/n * n/(n+1) = 1/(n+1)
1480 j = floor(random() * (i + 1));
1486 string substring_range(string s, float b, float e)
1488 return substring(s, b, e - b);
1491 string swapwords(string str, float i, float j)
1494 string s1, s2, s3, s4, s5;
1495 float si, ei, sj, ej, s0, en;
1496 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1497 si = argv_start_index(i);
1498 sj = argv_start_index(j);
1499 ei = argv_end_index(i);
1500 ej = argv_end_index(j);
1501 s0 = argv_start_index(0);
1502 en = argv_end_index(n-1);
1503 s1 = substring_range(str, s0, si);
1504 s2 = substring_range(str, si, ei);
1505 s3 = substring_range(str, ei, sj);
1506 s4 = substring_range(str, sj, ej);
1507 s5 = substring_range(str, ej, en);
1508 return strcat(s1, s4, s3, s2, s5);
1511 string _shufflewords_str;
1512 void _shufflewords_swapfunc(float i, float j, entity pass)
1514 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1516 string shufflewords(string str)
1519 _shufflewords_str = str;
1520 n = tokenizebyseparator(str, " ");
1521 shuffle(n, _shufflewords_swapfunc, world);
1522 str = _shufflewords_str;
1523 _shufflewords_str = string_null;
1527 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1543 // actually, every number solves the equation!
1554 if(a > 0) // put the smaller solution first
1556 v_x = ((-b)-D) / (2*a);
1557 v_y = ((-b)+D) / (2*a);
1561 v_x = (-b+D) / (2*a);
1562 v_y = (-b-D) / (2*a);
1568 // complex solutions!
1581 void check_unacceptable_compiler_bugs()
1583 if(cvar("_allow_unacceptable_compiler_bugs"))
1585 tokenize_console("foo bar");
1586 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1587 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.");
1591 error("The empty string counts as false. We do not want that!");
1594 float compressShotOrigin(vector v)
1598 y = rint(v_y * 4) + 128;
1599 z = rint(v_z * 4) + 128;
1600 if(x > 255 || x < 0)
1602 print("shot origin ", vtos(v), " x out of bounds\n");
1603 x = bound(0, x, 255);
1605 if(y > 255 || y < 0)
1607 print("shot origin ", vtos(v), " y out of bounds\n");
1608 y = bound(0, y, 255);
1610 if(z > 255 || z < 0)
1612 print("shot origin ", vtos(v), " z out of bounds\n");
1613 z = bound(0, z, 255);
1615 return x * 0x10000 + y * 0x100 + z;
1617 vector decompressShotOrigin(float f)
1620 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1621 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1622 v_z = ((f & 0xFF) - 128) / 4;
1626 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1628 float start, end, root, child;
1631 start = floor((n - 2) / 2);
1634 // siftdown(start, count-1);
1636 while(root * 2 + 1 <= n-1)
1638 child = root * 2 + 1;
1640 if(cmp(child, child+1, pass) < 0)
1642 if(cmp(root, child, pass) < 0)
1644 swap(root, child, pass);
1660 // siftdown(0, end);
1662 while(root * 2 + 1 <= end)
1664 child = root * 2 + 1;
1665 if(child < end && cmp(child, child+1, pass) < 0)
1667 if(cmp(root, child, pass) < 0)
1669 swap(root, child, pass);
1679 void RandomSelection_Init()
1681 RandomSelection_totalweight = 0;
1682 RandomSelection_chosen_ent = world;
1683 RandomSelection_chosen_float = 0;
1684 RandomSelection_chosen_string = string_null;
1685 RandomSelection_best_priority = -1;
1687 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1689 if(priority > RandomSelection_best_priority)
1691 RandomSelection_best_priority = priority;
1692 RandomSelection_chosen_ent = e;
1693 RandomSelection_chosen_float = f;
1694 RandomSelection_chosen_string = s;
1695 RandomSelection_totalweight = weight;
1697 else if(priority == RandomSelection_best_priority)
1699 RandomSelection_totalweight += weight;
1700 if(random() * RandomSelection_totalweight <= weight)
1702 RandomSelection_chosen_ent = e;
1703 RandomSelection_chosen_float = f;
1704 RandomSelection_chosen_string = s;
1709 vector healtharmor_maxdamage(float h, float a, float armorblock)
1711 // NOTE: we'll always choose the SMALLER value...
1712 float healthdamage, armordamage, armorideal;
1714 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1715 armordamage = a + (h - 1); // damage we can take if we could use more armor
1716 armorideal = healthdamage * armorblock;
1718 if(armordamage < healthdamage)
1731 vector healtharmor_applydamage(float a, float armorblock, float damage)
1734 v_y = bound(0, damage * armorblock, a); // save
1735 v_x = bound(0, damage - v_y, damage); // take
1740 string getcurrentmod()
1744 m = cvar_string("fs_gamedir");
1745 n = tokenize_console(m);
1757 v = ReadShort() * 256; // note: this is signed
1758 v += ReadByte(); // note: this is unsigned
1762 void WriteInt24_t(float dst, float val)
1765 WriteShort(dst, (v = floor(val / 256)));
1766 WriteByte(dst, val - v * 256); // 0..255
1771 float float2range11(float f)
1773 // continuous function mapping all reals into -1..1
1774 return f / (fabs(f) + 1);
1777 float float2range01(float f)
1779 // continuous function mapping all reals into 0..1
1780 return 0.5 + 0.5 * float2range11(f);
1783 // from the GNU Scientific Library
1784 float gsl_ran_gaussian_lastvalue;
1785 float gsl_ran_gaussian_lastvalue_set;
1786 float gsl_ran_gaussian(float sigma)
1789 if(gsl_ran_gaussian_lastvalue_set)
1791 gsl_ran_gaussian_lastvalue_set = 0;
1792 return sigma * gsl_ran_gaussian_lastvalue;
1796 a = random() * 2 * M_PI;
1797 b = sqrt(-2 * log(random()));
1798 gsl_ran_gaussian_lastvalue = cos(a) * b;
1799 gsl_ran_gaussian_lastvalue_set = 1;
1800 return sigma * sin(a) * b;
1804 string car(string s)
1807 o = strstrofs(s, " ", 0);
1810 return substring(s, 0, o);
1812 string cdr(string s)
1815 o = strstrofs(s, " ", 0);
1818 return substring(s, o + 1, strlen(s) - (o + 1));
1820 float matchacl(string acl, string str)
1827 t = car(acl); acl = cdr(acl);
1829 if(substring(t, 0, 1) == "-")
1832 t = substring(t, 1, strlen(t) - 1);
1834 else if(substring(t, 0, 1) == "+")
1835 t = substring(t, 1, strlen(t) - 1);
1836 if(substring(t, -1, 1) == "*")
1838 t = substring(t, 0, strlen(t) - 1);
1839 s = substring(s, 0, strlen(t));
1851 float startsWith(string haystack, string needle)
1853 return substring(haystack, 0, strlen(needle)) == needle;
1855 float startsWithNocase(string haystack, string needle)
1857 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1860 string get_model_datafilename(string m, float sk, string fil)
1865 m = "models/player/*_";
1867 m = strcat(m, ftos(sk));
1870 return strcat(m, ".", fil);
1873 float get_model_parameters(string m, float sk)
1878 get_model_parameters_modelname = string_null;
1879 get_model_parameters_modelskin = -1;
1880 get_model_parameters_name = string_null;
1881 get_model_parameters_species = -1;
1882 get_model_parameters_sex = string_null;
1883 get_model_parameters_weight = -1;
1884 get_model_parameters_age = -1;
1885 get_model_parameters_desc = string_null;
1891 if(substring(m, -4, -1) != ".txt")
1893 if(substring(m, -6, 1) != "_")
1895 sk = stof(substring(m, -5, 1));
1896 m = substring(m, 0, -7);
1899 fn = get_model_datafilename(m, sk, "txt");
1900 fh = fopen(fn, FILE_READ);
1904 fn = get_model_datafilename(m, sk, "txt");
1905 fh = fopen(fn, FILE_READ);
1910 get_model_parameters_modelname = m;
1911 get_model_parameters_modelskin = sk;
1912 while((s = fgets(fh)))
1915 break; // next lines will be description
1919 get_model_parameters_name = s;
1923 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1924 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1925 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1926 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1927 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1928 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1929 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1932 get_model_parameters_sex = s;
1934 get_model_parameters_weight = stof(s);
1936 get_model_parameters_age = stof(s);
1939 while((s = fgets(fh)))
1941 if(get_model_parameters_desc)
1942 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1944 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1952 vector vec2(vector v)
1959 vector NearestPointOnBox(entity box, vector org)
1961 vector m1, m2, nearest;
1963 m1 = box.mins + box.origin;
1964 m2 = box.maxs + box.origin;
1966 nearest_x = bound(m1_x, org_x, m2_x);
1967 nearest_y = bound(m1_y, org_y, m2_y);
1968 nearest_z = bound(m1_z, org_z, m2_z);
1974 float vercmp_recursive(string v1, string v2)
1980 dot1 = strstrofs(v1, ".", 0);
1981 dot2 = strstrofs(v2, ".", 0);
1985 s1 = substring(v1, 0, dot1);
1989 s2 = substring(v2, 0, dot2);
1991 r = stof(s1) - stof(s2);
1995 r = strcasecmp(s1, s2);
2008 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2011 float vercmp(string v1, string v2)
2013 if(strcasecmp(v1, v2) == 0) // early out check
2022 return vercmp_recursive(v1, v2);
2025 float u8_strsize(string s)
2045 // translation helpers
2046 string language_filename(string s)
2051 if(fn == "" || fn == "dump")
2053 fn = strcat(s, ".", fn);
2054 if((fh = fopen(fn, FILE_READ)) >= 0)
2061 string CTX(string s)
2063 float p = strstrofs(s, "^", 0);
2066 return substring(s, p+1, -1);
2069 // x-encoding (encoding as zero length invisible string)
2070 const string XENCODE_2 = "xX";
2071 const string XENCODE_22 = "0123456789abcdefABCDEF";
2072 string xencode(float f)
2075 d = mod(f, 22); f = floor(f / 22);
2076 c = mod(f, 22); f = floor(f / 22);
2077 b = mod(f, 22); f = floor(f / 22);
2078 a = mod(f, 2); // f = floor(f / 2);
2081 substring(XENCODE_2, a, 1),
2082 substring(XENCODE_22, b, 1),
2083 substring(XENCODE_22, c, 1),
2084 substring(XENCODE_22, d, 1)
2087 float xdecode(string s)
2090 if(substring(s, 0, 1) != "^")
2094 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2095 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2096 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2097 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2098 if(a < 0 || b < 0 || c < 0 || d < 0)
2100 return ((a * 22 + b) * 22 + c) * 22 + d;
2103 float lowestbit(float f)
2114 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2116 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2119 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2122 // escape the string to make it safe for consoles
2123 string MakeConsoleSafe(string input)
2125 input = strreplace("\n", "", input);
2126 input = strreplace("\\", "\\\\", input);
2127 input = strreplace("$", "$$", input);
2128 input = strreplace("\"", "\\\"", input);
2133 // get true/false value of a string with multiple different inputs
2134 float InterpretBoolean(string input)
2136 switch(strtolower(input))
2148 default: return stof(input);
2154 entity ReadCSQCEntity()
2160 return findfloat(world, entnum, f);
2164 float shutdown_running;
2169 void CSQC_Shutdown()
2175 if(shutdown_running)
2177 print("Recursive shutdown detected! Only restoring cvars...\n");
2181 shutdown_running = 1;
2184 cvar_settemp_restore(); // this must be done LAST, but in any case
2187 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2188 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2189 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2190 // this will use the value:
2192 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2193 // accuracy at x is 1/derivative, i.e.
2194 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2196 void WriteApproxPastTime(float dst, float t)
2198 float dt = time - t;
2200 // warning: this is approximate; do not resend when you don't have to!
2201 // be careful with sendflags here!
2202 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2205 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2208 dt = rint(bound(0, dt, 255));
2214 float ReadApproxPastTime()
2216 float dt = ReadByte();
2218 // map from range...PPROXPASTTIME_MAX / 256
2219 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2221 return servertime - dt;
2226 .float skeleton_bones_index;
2227 void Skeleton_SetBones(entity e)
2229 // set skeleton_bones to the total number of bones on the model
2230 if(e.skeleton_bones_index == e.modelindex)
2231 return; // same model, nothing to update
2234 skelindex = skel_create(e.modelindex);
2235 e.skeleton_bones = skel_get_numbones(skelindex);
2236 skel_delete(skelindex);
2237 e.skeleton_bones_index = e.modelindex;
2241 string to_execute_next_frame;
2242 void execute_next_frame()
2244 if(to_execute_next_frame)
2246 localcmd("\n", to_execute_next_frame, "\n");
2247 strunzone(to_execute_next_frame);
2248 to_execute_next_frame = string_null;
2251 void queue_to_execute_next_frame(string s)
2253 if(to_execute_next_frame)
2255 s = strcat(s, "\n", to_execute_next_frame);
2256 strunzone(to_execute_next_frame);
2258 to_execute_next_frame = strzone(s);