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 // we have sprintf...
200 return sprintf("%.*f", decimals, number);
204 vector colormapPaletteColor(float c, float isPants)
208 case 0: return '1.000000 1.000000 1.000000';
209 case 1: return '1.000000 0.333333 0.000000';
210 case 2: return '0.000000 1.000000 0.501961';
211 case 3: return '0.000000 1.000000 0.000000';
212 case 4: return '1.000000 0.000000 0.000000';
213 case 5: return '0.000000 0.666667 1.000000';
214 case 6: return '0.000000 1.000000 1.000000';
215 case 7: return '0.501961 1.000000 0.000000';
216 case 8: return '0.501961 0.000000 1.000000';
217 case 9: return '1.000000 0.000000 1.000000';
218 case 10: return '1.000000 0.000000 0.501961';
219 case 11: return '0.000000 0.000000 1.000000';
220 case 12: return '1.000000 1.000000 0.000000';
221 case 13: return '0.000000 0.333333 1.000000';
222 case 14: return '1.000000 0.666667 0.000000';
226 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
227 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
228 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
231 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
232 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
233 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
234 default: return '0.000 0.000 0.000';
238 // unzone the string, and return it as tempstring. Safe to be called on string_null
239 string fstrunzone(string s)
249 float fexists(string f)
252 fh = fopen(f, FILE_READ);
259 // Databases (hash tables)
260 #define DB_BUCKETS 8192
261 void db_save(float db, string pFilename)
264 fh = fopen(pFilename, FILE_WRITE);
267 print(strcat("^1Can't write DB to ", pFilename));
271 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
272 for(i = 0; i < n; ++i)
273 fputs(fh, strcat(bufstr_get(db, i), "\n"));
282 float db_load(string pFilename)
284 float db, fh, i, j, n;
289 fh = fopen(pFilename, FILE_READ);
293 if(stof(l) == DB_BUCKETS)
296 while((l = fgets(fh)))
299 bufstr_set(db, i, l);
305 // different count of buckets, or a dump?
306 // need to reorganize the database then (SLOW)
308 // note: we also parse the first line (l) in case the DB file is
309 // missing the bucket count
312 n = tokenizebyseparator(l, "\\");
313 for(j = 2; j < n; j += 2)
314 db_put(db, argv(j-1), uri_unescape(argv(j)));
316 while((l = fgets(fh)));
322 void db_dump(float db, string pFilename)
324 float fh, i, j, n, m;
325 fh = fopen(pFilename, FILE_WRITE);
327 error(strcat("Can't dump DB to ", pFilename));
330 for(i = 0; i < n; ++i)
332 m = tokenizebyseparator(bufstr_get(db, i), "\\");
333 for(j = 2; j < m; j += 2)
334 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
339 void db_close(float db)
344 string db_get(float db, string pKey)
347 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
348 return uri_unescape(infoget(bufstr_get(db, h), pKey));
351 void db_put(float db, string pKey, string pValue)
354 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
355 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
362 db = db_load("foo.db");
363 print("LOADED. FILL...\n");
364 for(i = 0; i < DB_BUCKETS; ++i)
365 db_put(db, ftos(random()), "X");
366 print("FILLED. SAVE...\n");
367 db_save(db, "foo.db");
368 print("SAVED. CLOSE...\n");
373 // Multiline text file buffers
374 float buf_load(string pFilename)
381 fh = fopen(pFilename, FILE_READ);
388 while((l = fgets(fh)))
390 bufstr_set(buf, i, l);
397 void buf_save(float buf, string pFilename)
400 fh = fopen(pFilename, FILE_WRITE);
402 error(strcat("Can't write buf to ", pFilename));
403 n = buf_getsize(buf);
404 for(i = 0; i < n; ++i)
405 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
409 string mmsss(float tenths)
413 tenths = floor(tenths + 0.5);
414 minutes = floor(tenths / 600);
415 tenths -= minutes * 600;
416 s = ftos(1000 + tenths);
417 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
420 string mmssss(float hundredths)
424 hundredths = floor(hundredths + 0.5);
425 minutes = floor(hundredths / 6000);
426 hundredths -= minutes * 6000;
427 s = ftos(10000 + hundredths);
428 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
431 string ScoreString(float pFlags, float pValue)
436 pValue = floor(pValue + 0.5); // round
438 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
440 else if(pFlags & SFL_RANK)
442 valstr = ftos(pValue);
444 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
445 valstr = strcat(valstr, "th");
446 else if(substring(valstr, l - 1, 1) == "1")
447 valstr = strcat(valstr, "st");
448 else if(substring(valstr, l - 1, 1) == "2")
449 valstr = strcat(valstr, "nd");
450 else if(substring(valstr, l - 1, 1) == "3")
451 valstr = strcat(valstr, "rd");
453 valstr = strcat(valstr, "th");
455 else if(pFlags & SFL_TIME)
456 valstr = TIME_ENCODED_TOSTRING(pValue);
458 valstr = ftos(pValue);
463 vector cross(vector a, vector b)
466 '1 0 0' * (a_y * b_z - a_z * b_y)
467 + '0 1 0' * (a_z * b_x - a_x * b_z)
468 + '0 0 1' * (a_x * b_y - a_y * b_x);
471 // compressed vector format:
472 // like MD3, just even shorter
473 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
474 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
475 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
476 // length = 2^(length_encoded/8) / 8
477 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
478 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
479 // the special value 0 indicates the zero vector
481 float lengthLogTable[128];
483 float invertLengthLog(float x)
485 float l, r, m, lerr, rerr;
487 if(x >= lengthLogTable[127])
489 if(x <= lengthLogTable[0])
497 m = floor((l + r) / 2);
498 if(lengthLogTable[m] < x)
504 // now: r is >=, l is <
505 lerr = (x - lengthLogTable[l]);
506 rerr = (lengthLogTable[r] - x);
512 vector decompressShortVector(float data)
518 p = (data & 0xF000) / 0x1000;
519 y = (data & 0x0F80) / 0x80;
520 len = (data & 0x007F);
522 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
535 y = .19634954084936207740 * y;
536 p = .19634954084936207740 * p - 1.57079632679489661922;
537 out_x = cos(y) * cos(p);
538 out_y = sin(y) * cos(p);
542 //print("decompressed: ", vtos(out), "\n");
544 return out * lengthLogTable[len];
547 float compressShortVector(vector vec)
553 //print("compress: ", vtos(vec), "\n");
554 ang = vectoangles(vec);
558 if(ang_x < -90 && ang_x > +90)
559 error("BOGUS vectoangles");
560 //print("angles: ", vtos(ang), "\n");
562 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
571 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
572 len = invertLengthLog(vlen(vec));
574 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
576 return (p * 0x1000) + (y * 0x80) + len;
579 void compressShortVector_init()
584 for(i = 0; i < 128; ++i)
586 lengthLogTable[i] = l;
590 if(cvar("developer"))
592 print("Verifying vector compression table...\n");
593 for(i = 0x0F00; i < 0xFFFF; ++i)
594 if(i != compressShortVector(decompressShortVector(i)))
596 print("BROKEN vector compression: ", ftos(i));
597 print(" -> ", vtos(decompressShortVector(i)));
598 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
607 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
609 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
610 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
611 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
612 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
613 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
614 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
615 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
616 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
617 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
618 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
619 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
620 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
625 string fixPriorityList(string order, float from, float to, float subtract, float complete)
630 n = tokenize_console(order);
632 for(i = 0; i < n; ++i)
637 if(w >= from && w <= to)
638 neworder = strcat(neworder, ftos(w), " ");
642 if(w >= from && w <= to)
643 neworder = strcat(neworder, ftos(w), " ");
650 n = tokenize_console(neworder);
651 for(w = to; w >= from; --w)
653 for(i = 0; i < n; ++i)
654 if(stof(argv(i)) == w)
656 if(i == n) // not found
657 neworder = strcat(neworder, ftos(w), " ");
661 return substring(neworder, 0, strlen(neworder) - 1);
664 string mapPriorityList(string order, string(string) mapfunc)
669 n = tokenize_console(order);
671 for(i = 0; i < n; ++i)
672 neworder = strcat(neworder, mapfunc(argv(i)), " ");
674 return substring(neworder, 0, strlen(neworder) - 1);
677 string swapInPriorityList(string order, float i, float j)
682 n = tokenize_console(order);
684 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
687 for(w = 0; w < n; ++w)
690 s = strcat(s, argv(j), " ");
692 s = strcat(s, argv(i), " ");
694 s = strcat(s, argv(w), " ");
696 return substring(s, 0, strlen(s) - 1);
702 float cvar_value_issafe(string s)
704 if(strstrofs(s, "\"", 0) >= 0)
706 if(strstrofs(s, "\\", 0) >= 0)
708 if(strstrofs(s, ";", 0) >= 0)
710 if(strstrofs(s, "$", 0) >= 0)
712 if(strstrofs(s, "\r", 0) >= 0)
714 if(strstrofs(s, "\n", 0) >= 0)
720 void get_mi_min_max(float mode)
725 strunzone(mi_shortname);
726 mi_shortname = mapname;
727 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
728 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
729 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
730 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
731 mi_shortname = strzone(mi_shortname);
743 MapInfo_Get_ByName(mi_shortname, 0, 0);
744 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
746 mi_min = MapInfo_Map_mins;
747 mi_max = MapInfo_Map_maxs;
755 tracebox('1 0 0' * mi_x,
756 '0 1 0' * mi_y + '0 0 1' * mi_z,
757 '0 1 0' * ma_y + '0 0 1' * ma_z,
761 if(!trace_startsolid)
762 mi_min_x = trace_endpos_x;
764 tracebox('0 1 0' * mi_y,
765 '1 0 0' * mi_x + '0 0 1' * mi_z,
766 '1 0 0' * ma_x + '0 0 1' * ma_z,
770 if(!trace_startsolid)
771 mi_min_y = trace_endpos_y;
773 tracebox('0 0 1' * mi_z,
774 '1 0 0' * mi_x + '0 1 0' * mi_y,
775 '1 0 0' * ma_x + '0 1 0' * ma_y,
779 if(!trace_startsolid)
780 mi_min_z = trace_endpos_z;
782 tracebox('1 0 0' * ma_x,
783 '0 1 0' * mi_y + '0 0 1' * mi_z,
784 '0 1 0' * ma_y + '0 0 1' * ma_z,
788 if(!trace_startsolid)
789 mi_max_x = trace_endpos_x;
791 tracebox('0 1 0' * ma_y,
792 '1 0 0' * mi_x + '0 0 1' * mi_z,
793 '1 0 0' * ma_x + '0 0 1' * ma_z,
797 if(!trace_startsolid)
798 mi_max_y = trace_endpos_y;
800 tracebox('0 0 1' * ma_z,
801 '1 0 0' * mi_x + '0 1 0' * mi_y,
802 '1 0 0' * ma_x + '0 1 0' * ma_y,
806 if(!trace_startsolid)
807 mi_max_z = trace_endpos_z;
812 void get_mi_min_max_texcoords(float mode)
816 get_mi_min_max(mode);
821 // extend mi_picmax to get a square aspect ratio
822 // center the map in that area
823 extend = mi_picmax - mi_picmin;
824 if(extend_y > extend_x)
826 mi_picmin_x -= (extend_y - extend_x) * 0.5;
827 mi_picmax_x += (extend_y - extend_x) * 0.5;
831 mi_picmin_y -= (extend_x - extend_y) * 0.5;
832 mi_picmax_y += (extend_x - extend_y) * 0.5;
835 // add another some percent
836 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
840 // calculate the texcoords
841 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
842 // first the two corners of the origin
843 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
844 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
845 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
846 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
847 // then the other corners
848 mi_pictexcoord1_x = mi_pictexcoord0_x;
849 mi_pictexcoord1_y = mi_pictexcoord2_y;
850 mi_pictexcoord3_x = mi_pictexcoord2_x;
851 mi_pictexcoord3_y = mi_pictexcoord0_y;
855 float cvar_settemp(string tmp_cvar, string tmp_value)
857 float created_saved_value;
860 if not(tmp_cvar || tmp_value)
862 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
866 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
867 if(e.netname == tmp_cvar)
868 goto saved; // skip creation
870 // creating a new entity to keep track of this cvar
872 e.classname = "saved_cvar_value";
873 e.netname = strzone(tmp_cvar);
874 e.message = strzone(cvar_string(tmp_cvar));
875 created_saved_value = TRUE;
877 // an entity for this cvar already exists
880 // update the cvar to the value given
881 cvar_set(tmp_cvar, tmp_value);
883 return created_saved_value;
886 float cvar_settemp_restore()
890 while((e = find(world, classname, "saved_cvar_value")))
892 cvar_set(e.netname, e.message);
899 float almost_equals(float a, float b)
902 eps = (max(a, -a) + max(b, -b)) * 0.001;
903 if(a - b < eps && b - a < eps)
908 float almost_in_bounds(float a, float b, float c)
911 eps = (max(a, -a) + max(c, -c)) * 0.001;
912 return b == median(a - eps, b, c + eps);
915 float power2of(float e)
919 float log2of(float x)
921 // NOTE: generated code
994 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1001 return (rgb_y - rgb_z) / (ma - mi);
1003 return (rgb_y - rgb_z) / (ma - mi) + 6;
1005 else if(ma == rgb_y)
1006 return (rgb_z - rgb_x) / (ma - mi) + 2;
1007 else // if(ma == rgb_z)
1008 return (rgb_x - rgb_y) / (ma - mi) + 4;
1011 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1015 hue -= 6 * floor(hue / 6);
1017 //else if(ma == rgb_x)
1018 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1022 rgb_y = hue * (ma - mi) + mi;
1025 //else if(ma == rgb_y)
1026 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1029 rgb_x = (2 - hue) * (ma - mi) + mi;
1037 rgb_z = (hue - 2) * (ma - mi) + mi;
1039 //else // if(ma == rgb_z)
1040 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1044 rgb_y = (4 - hue) * (ma - mi) + mi;
1049 rgb_x = (hue - 4) * (ma - mi) + mi;
1053 //else if(ma == rgb_x)
1054 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1055 else // if(hue <= 6)
1059 rgb_z = (6 - hue) * (ma - mi) + mi;
1065 vector rgb_to_hsv(vector rgb)
1070 mi = min(rgb_x, rgb_y, rgb_z);
1071 ma = max(rgb_x, rgb_y, rgb_z);
1073 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1084 vector hsv_to_rgb(vector hsv)
1086 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1089 vector rgb_to_hsl(vector rgb)
1094 mi = min(rgb_x, rgb_y, rgb_z);
1095 ma = max(rgb_x, rgb_y, rgb_z);
1097 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1099 hsl_z = 0.5 * (mi + ma);
1102 else if(hsl_z <= 0.5)
1103 hsl_y = (ma - mi) / (2*hsl_z);
1104 else // if(hsl_z > 0.5)
1105 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1110 vector hsl_to_rgb(vector hsl)
1112 float mi, ma, maminusmi;
1115 maminusmi = hsl_y * 2 * hsl_z;
1117 maminusmi = hsl_y * (2 - 2 * hsl_z);
1119 // hsl_z = 0.5 * mi + 0.5 * ma
1120 // maminusmi = - mi + ma
1121 mi = hsl_z - 0.5 * maminusmi;
1122 ma = hsl_z + 0.5 * maminusmi;
1124 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1127 string rgb_to_hexcolor(vector rgb)
1132 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1133 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1134 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1138 // requires that m2>m1 in all coordinates, and that m4>m3
1139 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;}
1141 // requires the same, but is a stronger condition
1142 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;}
1147 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1150 // The following function is SLOW.
1151 // For your safety and for the protection of those around you...
1152 // DO NOT CALL THIS AT HOME.
1153 // No really, don't.
1154 if(w(theText, theSize) <= maxWidth)
1155 return strlen(theText); // yeah!
1157 // binary search for right place to cut string
1159 float left, right, middle; // this always works
1161 right = strlen(theText); // this always fails
1164 middle = floor((left + right) / 2);
1165 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1170 while(left < right - 1);
1172 if(w("^7", theSize) == 0) // detect color codes support in the width function
1174 // NOTE: when color codes are involved, this binary search is,
1175 // mathematically, BROKEN. However, it is obviously guaranteed to
1176 // terminate, as the range still halves each time - but nevertheless, it is
1177 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1178 // range, and "right" is outside).
1180 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1181 // and decrease left on the basis of the chars detected of the truncated tag
1182 // Even if the ^xrgb tag is not complete/correct, left is decreased
1183 // (sometimes too much but with a correct result)
1184 // it fixes also ^[0-9]
1185 while(left >= 1 && substring(theText, left-1, 1) == "^")
1188 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1190 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1192 ch = str2chr(theText, left-1);
1193 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1196 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1198 ch = str2chr(theText, left-2);
1199 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1201 ch = str2chr(theText, left-1);
1202 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1211 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1214 // The following function is SLOW.
1215 // For your safety and for the protection of those around you...
1216 // DO NOT CALL THIS AT HOME.
1217 // No really, don't.
1218 if(w(theText) <= maxWidth)
1219 return strlen(theText); // yeah!
1221 // binary search for right place to cut string
1223 float left, right, middle; // this always works
1225 right = strlen(theText); // this always fails
1228 middle = floor((left + right) / 2);
1229 if(w(substring(theText, 0, middle)) <= maxWidth)
1234 while(left < right - 1);
1236 if(w("^7") == 0) // detect color codes support in the width function
1238 // NOTE: when color codes are involved, this binary search is,
1239 // mathematically, BROKEN. However, it is obviously guaranteed to
1240 // terminate, as the range still halves each time - but nevertheless, it is
1241 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1242 // range, and "right" is outside).
1244 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1245 // and decrease left on the basis of the chars detected of the truncated tag
1246 // Even if the ^xrgb tag is not complete/correct, left is decreased
1247 // (sometimes too much but with a correct result)
1248 // it fixes also ^[0-9]
1249 while(left >= 1 && substring(theText, left-1, 1) == "^")
1252 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1254 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1256 ch = str2chr(theText, left-1);
1257 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1260 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1262 ch = str2chr(theText, left-2);
1263 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1265 ch = str2chr(theText, left-1);
1266 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1275 string find_last_color_code(string s)
1277 float start, len, i, carets;
1278 start = strstrofs(s, "^", 0);
1279 if (start == -1) // no caret found
1282 for(i = len; i >= start; --i)
1284 if(substring(s, i, 1) != "^")
1288 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1291 // check if carets aren't all escaped
1292 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1295 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1296 return substring(s, i, 2);
1299 if(substring(s, i+1, 1) == "x")
1300 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1301 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1302 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1303 return substring(s, i, 5);
1305 i -= carets; // this also skips one char before the carets
1311 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1317 s = getWrappedLine_remaining;
1321 getWrappedLine_remaining = string_null;
1322 return s; // the line has no size ANYWAY, nothing would be displayed.
1325 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1326 if(cantake > 0 && cantake < strlen(s))
1329 while(take > 0 && substring(s, take, 1) != " ")
1333 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1334 if(getWrappedLine_remaining == "")
1335 getWrappedLine_remaining = string_null;
1336 else if (tw("^7", theFontSize) == 0)
1337 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1338 return substring(s, 0, cantake);
1342 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1343 if(getWrappedLine_remaining == "")
1344 getWrappedLine_remaining = string_null;
1345 else if (tw("^7", theFontSize) == 0)
1346 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1347 return substring(s, 0, take);
1352 getWrappedLine_remaining = string_null;
1357 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1363 s = getWrappedLine_remaining;
1367 getWrappedLine_remaining = string_null;
1368 return s; // the line has no size ANYWAY, nothing would be displayed.
1371 cantake = textLengthUpToLength(s, w, tw);
1372 if(cantake > 0 && cantake < strlen(s))
1375 while(take > 0 && substring(s, take, 1) != " ")
1379 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1380 if(getWrappedLine_remaining == "")
1381 getWrappedLine_remaining = string_null;
1382 else if (tw("^7") == 0)
1383 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1384 return substring(s, 0, cantake);
1388 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1389 if(getWrappedLine_remaining == "")
1390 getWrappedLine_remaining = string_null;
1391 else if (tw("^7") == 0)
1392 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1393 return substring(s, 0, take);
1398 getWrappedLine_remaining = string_null;
1403 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1405 if(tw(theText, theFontSize) <= maxWidth)
1408 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1411 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1413 if(tw(theText) <= maxWidth)
1416 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1419 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1421 string subpattern, subpattern2, subpattern3, subpattern4;
1422 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1424 subpattern2 = ",teams,";
1426 subpattern2 = ",noteams,";
1428 subpattern3 = ",teamspawns,";
1430 subpattern3 = ",noteamspawns,";
1431 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1432 subpattern4 = ",race,";
1434 subpattern4 = string_null;
1436 if(substring(pattern, 0, 1) == "-")
1438 pattern = substring(pattern, 1, strlen(pattern) - 1);
1439 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1441 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1443 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1445 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1450 if(substring(pattern, 0, 1) == "+")
1451 pattern = substring(pattern, 1, strlen(pattern) - 1);
1452 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1453 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1454 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1455 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1461 void shuffle(float n, swapfunc_t swap, entity pass)
1464 for(i = 1; i < n; ++i)
1466 // swap i-th item at a random position from 0 to i
1467 // proof for even distribution:
1470 // item n+1 gets at any position with chance 1/(n+1)
1471 // all others will get their 1/n chance reduced by factor n/(n+1)
1472 // to be on place n+1, their chance will be 1/(n+1)
1473 // 1/n * n/(n+1) = 1/(n+1)
1475 j = floor(random() * (i + 1));
1481 string substring_range(string s, float b, float e)
1483 return substring(s, b, e - b);
1486 string swapwords(string str, float i, float j)
1489 string s1, s2, s3, s4, s5;
1490 float si, ei, sj, ej, s0, en;
1491 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1492 si = argv_start_index(i);
1493 sj = argv_start_index(j);
1494 ei = argv_end_index(i);
1495 ej = argv_end_index(j);
1496 s0 = argv_start_index(0);
1497 en = argv_end_index(n-1);
1498 s1 = substring_range(str, s0, si);
1499 s2 = substring_range(str, si, ei);
1500 s3 = substring_range(str, ei, sj);
1501 s4 = substring_range(str, sj, ej);
1502 s5 = substring_range(str, ej, en);
1503 return strcat(s1, s4, s3, s2, s5);
1506 string _shufflewords_str;
1507 void _shufflewords_swapfunc(float i, float j, entity pass)
1509 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1511 string shufflewords(string str)
1514 _shufflewords_str = str;
1515 n = tokenizebyseparator(str, " ");
1516 shuffle(n, _shufflewords_swapfunc, world);
1517 str = _shufflewords_str;
1518 _shufflewords_str = string_null;
1522 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1538 // actually, every number solves the equation!
1549 if(a > 0) // put the smaller solution first
1551 v_x = ((-b)-D) / (2*a);
1552 v_y = ((-b)+D) / (2*a);
1556 v_x = (-b+D) / (2*a);
1557 v_y = (-b-D) / (2*a);
1563 // complex solutions!
1576 void check_unacceptable_compiler_bugs()
1578 if(cvar("_allow_unacceptable_compiler_bugs"))
1580 tokenize_console("foo bar");
1581 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1582 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.");
1586 error("The empty string counts as false. We do not want that!");
1589 float compressShotOrigin(vector v)
1593 y = rint(v_y * 4) + 128;
1594 z = rint(v_z * 4) + 128;
1595 if(x > 255 || x < 0)
1597 print("shot origin ", vtos(v), " x out of bounds\n");
1598 x = bound(0, x, 255);
1600 if(y > 255 || y < 0)
1602 print("shot origin ", vtos(v), " y out of bounds\n");
1603 y = bound(0, y, 255);
1605 if(z > 255 || z < 0)
1607 print("shot origin ", vtos(v), " z out of bounds\n");
1608 z = bound(0, z, 255);
1610 return x * 0x10000 + y * 0x100 + z;
1612 vector decompressShotOrigin(float f)
1615 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1616 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1617 v_z = ((f & 0xFF) - 128) / 4;
1621 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1623 float start, end, root, child;
1626 start = floor((n - 2) / 2);
1629 // siftdown(start, count-1);
1631 while(root * 2 + 1 <= n-1)
1633 child = root * 2 + 1;
1635 if(cmp(child, child+1, pass) < 0)
1637 if(cmp(root, child, pass) < 0)
1639 swap(root, child, pass);
1655 // siftdown(0, end);
1657 while(root * 2 + 1 <= end)
1659 child = root * 2 + 1;
1660 if(child < end && cmp(child, child+1, pass) < 0)
1662 if(cmp(root, child, pass) < 0)
1664 swap(root, child, pass);
1674 void RandomSelection_Init()
1676 RandomSelection_totalweight = 0;
1677 RandomSelection_chosen_ent = world;
1678 RandomSelection_chosen_float = 0;
1679 RandomSelection_chosen_string = string_null;
1680 RandomSelection_best_priority = -1;
1682 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1684 if(priority > RandomSelection_best_priority)
1686 RandomSelection_best_priority = priority;
1687 RandomSelection_chosen_ent = e;
1688 RandomSelection_chosen_float = f;
1689 RandomSelection_chosen_string = s;
1690 RandomSelection_totalweight = weight;
1692 else if(priority == RandomSelection_best_priority)
1694 RandomSelection_totalweight += weight;
1695 if(random() * RandomSelection_totalweight <= weight)
1697 RandomSelection_chosen_ent = e;
1698 RandomSelection_chosen_float = f;
1699 RandomSelection_chosen_string = s;
1704 vector healtharmor_maxdamage(float h, float a, float armorblock)
1706 // NOTE: we'll always choose the SMALLER value...
1707 float healthdamage, armordamage, armorideal;
1709 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1710 armordamage = a + (h - 1); // damage we can take if we could use more armor
1711 armorideal = healthdamage * armorblock;
1713 if(armordamage < healthdamage)
1726 vector healtharmor_applydamage(float a, float armorblock, float damage)
1729 v_y = bound(0, damage * armorblock, a); // save
1730 v_x = bound(0, damage - v_y, damage); // take
1735 string getcurrentmod()
1739 m = cvar_string("fs_gamedir");
1740 n = tokenize_console(m);
1752 v = ReadShort() * 256; // note: this is signed
1753 v += ReadByte(); // note: this is unsigned
1757 void WriteInt24_t(float dst, float val)
1760 WriteShort(dst, (v = floor(val / 256)));
1761 WriteByte(dst, val - v * 256); // 0..255
1766 float float2range11(float f)
1768 // continuous function mapping all reals into -1..1
1769 return f / (fabs(f) + 1);
1772 float float2range01(float f)
1774 // continuous function mapping all reals into 0..1
1775 return 0.5 + 0.5 * float2range11(f);
1778 // from the GNU Scientific Library
1779 float gsl_ran_gaussian_lastvalue;
1780 float gsl_ran_gaussian_lastvalue_set;
1781 float gsl_ran_gaussian(float sigma)
1784 if(gsl_ran_gaussian_lastvalue_set)
1786 gsl_ran_gaussian_lastvalue_set = 0;
1787 return sigma * gsl_ran_gaussian_lastvalue;
1791 a = random() * 2 * M_PI;
1792 b = sqrt(-2 * log(random()));
1793 gsl_ran_gaussian_lastvalue = cos(a) * b;
1794 gsl_ran_gaussian_lastvalue_set = 1;
1795 return sigma * sin(a) * b;
1799 string car(string s)
1802 o = strstrofs(s, " ", 0);
1805 return substring(s, 0, o);
1807 string cdr(string s)
1810 o = strstrofs(s, " ", 0);
1813 return substring(s, o + 1, strlen(s) - (o + 1));
1815 float matchacl(string acl, string str)
1822 t = car(acl); acl = cdr(acl);
1824 if(substring(t, 0, 1) == "-")
1827 t = substring(t, 1, strlen(t) - 1);
1829 else if(substring(t, 0, 1) == "+")
1830 t = substring(t, 1, strlen(t) - 1);
1831 if(substring(t, -1, 1) == "*")
1833 t = substring(t, 0, strlen(t) - 1);
1834 s = substring(s, 0, strlen(t));
1846 float startsWith(string haystack, string needle)
1848 return substring(haystack, 0, strlen(needle)) == needle;
1850 float startsWithNocase(string haystack, string needle)
1852 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1855 string get_model_datafilename(string m, float sk, string fil)
1860 m = "models/player/*_";
1862 m = strcat(m, ftos(sk));
1865 return strcat(m, ".", fil);
1868 float get_model_parameters(string m, float sk)
1873 get_model_parameters_modelname = string_null;
1874 get_model_parameters_modelskin = -1;
1875 get_model_parameters_name = string_null;
1876 get_model_parameters_species = -1;
1877 get_model_parameters_sex = string_null;
1878 get_model_parameters_weight = -1;
1879 get_model_parameters_age = -1;
1880 get_model_parameters_desc = string_null;
1886 if(substring(m, -4, -1) != ".txt")
1888 if(substring(m, -6, 1) != "_")
1890 sk = stof(substring(m, -5, 1));
1891 m = substring(m, 0, -7);
1894 fn = get_model_datafilename(m, sk, "txt");
1895 fh = fopen(fn, FILE_READ);
1899 fn = get_model_datafilename(m, sk, "txt");
1900 fh = fopen(fn, FILE_READ);
1905 get_model_parameters_modelname = m;
1906 get_model_parameters_modelskin = sk;
1907 while((s = fgets(fh)))
1910 break; // next lines will be description
1914 get_model_parameters_name = s;
1918 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1919 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1920 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1921 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1922 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1923 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1924 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1927 get_model_parameters_sex = s;
1929 get_model_parameters_weight = stof(s);
1931 get_model_parameters_age = stof(s);
1934 while((s = fgets(fh)))
1936 if(get_model_parameters_desc)
1937 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1939 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1947 vector vec2(vector v)
1954 vector NearestPointOnBox(entity box, vector org)
1956 vector m1, m2, nearest;
1958 m1 = box.mins + box.origin;
1959 m2 = box.maxs + box.origin;
1961 nearest_x = bound(m1_x, org_x, m2_x);
1962 nearest_y = bound(m1_y, org_y, m2_y);
1963 nearest_z = bound(m1_z, org_z, m2_z);
1969 float vercmp_recursive(string v1, string v2)
1975 dot1 = strstrofs(v1, ".", 0);
1976 dot2 = strstrofs(v2, ".", 0);
1980 s1 = substring(v1, 0, dot1);
1984 s2 = substring(v2, 0, dot2);
1986 r = stof(s1) - stof(s2);
1990 r = strcasecmp(s1, s2);
2003 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2006 float vercmp(string v1, string v2)
2008 if(strcasecmp(v1, v2) == 0) // early out check
2017 return vercmp_recursive(v1, v2);
2020 float u8_strsize(string s)
2040 // translation helpers
2041 string language_filename(string s)
2046 if(fn == "" || fn == "dump")
2048 fn = strcat(s, ".", fn);
2049 if((fh = fopen(fn, FILE_READ)) >= 0)
2056 string CTX(string s)
2058 float p = strstrofs(s, "^", 0);
2061 return substring(s, p+1, -1);
2064 // x-encoding (encoding as zero length invisible string)
2065 const string XENCODE_2 = "xX";
2066 const string XENCODE_22 = "0123456789abcdefABCDEF";
2067 string xencode(float f)
2070 d = mod(f, 22); f = floor(f / 22);
2071 c = mod(f, 22); f = floor(f / 22);
2072 b = mod(f, 22); f = floor(f / 22);
2073 a = mod(f, 2); // f = floor(f / 2);
2076 substring(XENCODE_2, a, 1),
2077 substring(XENCODE_22, b, 1),
2078 substring(XENCODE_22, c, 1),
2079 substring(XENCODE_22, d, 1)
2082 float xdecode(string s)
2085 if(substring(s, 0, 1) != "^")
2089 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2090 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2091 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2092 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2093 if(a < 0 || b < 0 || c < 0 || d < 0)
2095 return ((a * 22 + b) * 22 + c) * 22 + d;
2098 float lowestbit(float f)
2109 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2111 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2114 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2117 // escape the string to make it safe for consoles
2118 string MakeConsoleSafe(string input)
2120 input = strreplace("\n", "", input);
2121 input = strreplace("\\", "\\\\", input);
2122 input = strreplace("$", "$$", input);
2123 input = strreplace("\"", "\\\"", input);
2128 // get true/false value of a string with multiple different inputs
2129 float InterpretBoolean(string input)
2131 switch(strtolower(input))
2143 default: return stof(input);
2149 entity ReadCSQCEntity()
2155 return findfloat(world, entnum, f);
2159 float shutdown_running;
2164 void CSQC_Shutdown()
2170 if(shutdown_running)
2172 print("Recursive shutdown detected! Only restoring cvars...\n");
2176 shutdown_running = 1;
2179 cvar_settemp_restore(); // this must be done LAST, but in any case
2182 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2183 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2184 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2185 // this will use the value:
2187 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2188 // accuracy at x is 1/derivative, i.e.
2189 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2191 void WriteApproxPastTime(float dst, float t)
2193 float dt = time - t;
2195 // warning: this is approximate; do not resend when you don't have to!
2196 // be careful with sendflags here!
2197 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2200 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2203 dt = rint(bound(0, dt, 255));
2209 float ReadApproxPastTime()
2211 float dt = ReadByte();
2213 // map from range...PPROXPASTTIME_MAX / 256
2214 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2216 return servertime - dt;
2221 .float skeleton_bones_index;
2222 void Skeleton_SetBones(entity e)
2224 // set skeleton_bones to the total number of bones on the model
2225 if(e.skeleton_bones_index == e.modelindex)
2226 return; // same model, nothing to update
2229 skelindex = skel_create(e.modelindex);
2230 e.skeleton_bones = skel_get_numbones(skelindex);
2231 skel_delete(skelindex);
2232 e.skeleton_bones_index = e.modelindex;
2236 string to_execute_next_frame;
2237 void execute_next_frame()
2239 if(to_execute_next_frame)
2241 localcmd("\n", to_execute_next_frame, "\n");
2242 strunzone(to_execute_next_frame);
2243 to_execute_next_frame = string_null;
2246 void queue_to_execute_next_frame(string s)
2248 if(to_execute_next_frame)
2250 s = strcat(s, "\n", to_execute_next_frame);
2251 strunzone(to_execute_next_frame);
2253 to_execute_next_frame = strzone(s);