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 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1587 // make origin and speed relative
1592 // now solve for ret, ret normalized:
1593 // eorg + t * evel == t * ret * spd
1594 // or, rather, solve for t:
1595 // |eorg + t * evel| == t * spd
1596 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1597 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1598 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1599 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1600 // q = (eorg * eorg) / (evel * evel - spd * spd)
1601 if(!solution_z) // no real solution
1604 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1605 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1606 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1607 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1608 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1609 // spd < |evel| * sin angle(evel, eorg)
1612 else if(solution_x > 0)
1614 // both solutions > 0: take the smaller one
1615 // happens if p < 0 and q > 0
1616 ret = normalize(eorg + solution_x * evel);
1618 else if(solution_y > 0)
1620 // one solution > 0: take the larger one
1621 // happens if q < 0 or q == 0 and p < 0
1622 ret = normalize(eorg + solution_y * evel);
1626 // no solution > 0: reject
1627 // happens if p > 0 and q >= 0
1628 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1629 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1634 // "Enemy is moving away from me at more than spd"
1638 // NOTE: we always got a solution if spd > |evel|
1640 if(newton_style == 2)
1641 ret = normalize(ret * spd + myvel);
1646 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1651 if(newton_style == 2)
1653 // true Newtonian projectiles with automatic aim adjustment
1655 // solve: |outspeed * mydir - myvel| = spd
1656 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1657 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1661 // myvel^2 - (mydir * myvel)^2 > spd^2
1662 // velocity without mydir component > spd
1663 // fire at smallest possible spd that works?
1664 // |(mydir * myvel) * myvel - myvel| = spd
1666 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1670 outspeed = solution_y; // the larger one
1673 //outspeed = 0; // slowest possible shot
1674 outspeed = solution_x; // the real part (that is, the average!)
1675 //dprint("impossible shot, adjusting\n");
1678 outspeed = bound(spd * mi, outspeed, spd * ma);
1679 return mydir * outspeed;
1683 return myvel + spd * mydir;
1686 void check_unacceptable_compiler_bugs()
1688 if(cvar("_allow_unacceptable_compiler_bugs"))
1690 tokenize_console("foo bar");
1691 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1692 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.");
1696 error("The empty string counts as false. We do not want that!");
1699 float compressShotOrigin(vector v)
1703 y = rint(v_y * 4) + 128;
1704 z = rint(v_z * 4) + 128;
1705 if(x > 255 || x < 0)
1707 print("shot origin ", vtos(v), " x out of bounds\n");
1708 x = bound(0, x, 255);
1710 if(y > 255 || y < 0)
1712 print("shot origin ", vtos(v), " y out of bounds\n");
1713 y = bound(0, y, 255);
1715 if(z > 255 || z < 0)
1717 print("shot origin ", vtos(v), " z out of bounds\n");
1718 z = bound(0, z, 255);
1720 return x * 0x10000 + y * 0x100 + z;
1722 vector decompressShotOrigin(float f)
1725 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1726 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1727 v_z = ((f & 0xFF) - 128) / 4;
1731 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1733 float start, end, root, child;
1736 start = floor((n - 2) / 2);
1739 // siftdown(start, count-1);
1741 while(root * 2 + 1 <= n-1)
1743 child = root * 2 + 1;
1745 if(cmp(child, child+1, pass) < 0)
1747 if(cmp(root, child, pass) < 0)
1749 swap(root, child, pass);
1765 // siftdown(0, end);
1767 while(root * 2 + 1 <= end)
1769 child = root * 2 + 1;
1770 if(child < end && cmp(child, child+1, pass) < 0)
1772 if(cmp(root, child, pass) < 0)
1774 swap(root, child, pass);
1784 void RandomSelection_Init()
1786 RandomSelection_totalweight = 0;
1787 RandomSelection_chosen_ent = world;
1788 RandomSelection_chosen_float = 0;
1789 RandomSelection_chosen_string = string_null;
1790 RandomSelection_best_priority = -1;
1792 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1794 if(priority > RandomSelection_best_priority)
1796 RandomSelection_best_priority = priority;
1797 RandomSelection_chosen_ent = e;
1798 RandomSelection_chosen_float = f;
1799 RandomSelection_chosen_string = s;
1800 RandomSelection_totalweight = weight;
1802 else if(priority == RandomSelection_best_priority)
1804 RandomSelection_totalweight += weight;
1805 if(random() * RandomSelection_totalweight <= weight)
1807 RandomSelection_chosen_ent = e;
1808 RandomSelection_chosen_float = f;
1809 RandomSelection_chosen_string = s;
1814 vector healtharmor_maxdamage(float h, float a, float armorblock)
1816 // NOTE: we'll always choose the SMALLER value...
1817 float healthdamage, armordamage, armorideal;
1819 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1820 armordamage = a + (h - 1); // damage we can take if we could use more armor
1821 armorideal = healthdamage * armorblock;
1823 if(armordamage < healthdamage)
1836 vector healtharmor_applydamage(float a, float armorblock, float damage)
1839 v_y = bound(0, damage * armorblock, a); // save
1840 v_x = bound(0, damage - v_y, damage); // take
1845 string getcurrentmod()
1849 m = cvar_string("fs_gamedir");
1850 n = tokenize_console(m);
1862 v = ReadShort() * 256; // note: this is signed
1863 v += ReadByte(); // note: this is unsigned
1867 void WriteInt24_t(float dst, float val)
1870 WriteShort(dst, (v = floor(val / 256)));
1871 WriteByte(dst, val - v * 256); // 0..255
1876 float float2range11(float f)
1878 // continuous function mapping all reals into -1..1
1879 return f / (fabs(f) + 1);
1882 float float2range01(float f)
1884 // continuous function mapping all reals into 0..1
1885 return 0.5 + 0.5 * float2range11(f);
1888 // from the GNU Scientific Library
1889 float gsl_ran_gaussian_lastvalue;
1890 float gsl_ran_gaussian_lastvalue_set;
1891 float gsl_ran_gaussian(float sigma)
1894 if(gsl_ran_gaussian_lastvalue_set)
1896 gsl_ran_gaussian_lastvalue_set = 0;
1897 return sigma * gsl_ran_gaussian_lastvalue;
1901 a = random() * 2 * M_PI;
1902 b = sqrt(-2 * log(random()));
1903 gsl_ran_gaussian_lastvalue = cos(a) * b;
1904 gsl_ran_gaussian_lastvalue_set = 1;
1905 return sigma * sin(a) * b;
1909 string car(string s)
1912 o = strstrofs(s, " ", 0);
1915 return substring(s, 0, o);
1917 string cdr(string s)
1920 o = strstrofs(s, " ", 0);
1923 return substring(s, o + 1, strlen(s) - (o + 1));
1925 float matchacl(string acl, string str)
1932 t = car(acl); acl = cdr(acl);
1935 if(substring(t, 0, 1) == "-")
1938 t = substring(t, 1, strlen(t) - 1);
1940 else if(substring(t, 0, 1) == "+")
1941 t = substring(t, 1, strlen(t) - 1);
1943 if(substring(t, -1, 1) == "*")
1945 t = substring(t, 0, strlen(t) - 1);
1946 s = substring(str, 0, strlen(t));
1958 float startsWith(string haystack, string needle)
1960 return substring(haystack, 0, strlen(needle)) == needle;
1962 float startsWithNocase(string haystack, string needle)
1964 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1967 string get_model_datafilename(string m, float sk, string fil)
1972 m = "models/player/*_";
1974 m = strcat(m, ftos(sk));
1977 return strcat(m, ".", fil);
1980 float get_model_parameters(string m, float sk)
1985 get_model_parameters_modelname = string_null;
1986 get_model_parameters_modelskin = -1;
1987 get_model_parameters_name = string_null;
1988 get_model_parameters_species = -1;
1989 get_model_parameters_sex = string_null;
1990 get_model_parameters_weight = -1;
1991 get_model_parameters_age = -1;
1992 get_model_parameters_desc = string_null;
1998 if(substring(m, -4, -1) != ".txt")
2000 if(substring(m, -6, 1) != "_")
2002 sk = stof(substring(m, -5, 1));
2003 m = substring(m, 0, -7);
2006 fn = get_model_datafilename(m, sk, "txt");
2007 fh = fopen(fn, FILE_READ);
2011 fn = get_model_datafilename(m, sk, "txt");
2012 fh = fopen(fn, FILE_READ);
2017 get_model_parameters_modelname = m;
2018 get_model_parameters_modelskin = sk;
2019 while((s = fgets(fh)))
2022 break; // next lines will be description
2026 get_model_parameters_name = s;
2030 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2031 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2032 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2033 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2034 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2035 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2036 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2039 get_model_parameters_sex = s;
2041 get_model_parameters_weight = stof(s);
2043 get_model_parameters_age = stof(s);
2046 while((s = fgets(fh)))
2048 if(get_model_parameters_desc)
2049 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2051 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2059 vector vec2(vector v)
2066 vector NearestPointOnBox(entity box, vector org)
2068 vector m1, m2, nearest;
2070 m1 = box.mins + box.origin;
2071 m2 = box.maxs + box.origin;
2073 nearest_x = bound(m1_x, org_x, m2_x);
2074 nearest_y = bound(m1_y, org_y, m2_y);
2075 nearest_z = bound(m1_z, org_z, m2_z);
2081 float vercmp_recursive(string v1, string v2)
2087 dot1 = strstrofs(v1, ".", 0);
2088 dot2 = strstrofs(v2, ".", 0);
2092 s1 = substring(v1, 0, dot1);
2096 s2 = substring(v2, 0, dot2);
2098 r = stof(s1) - stof(s2);
2102 r = strcasecmp(s1, s2);
2115 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2118 float vercmp(string v1, string v2)
2120 if(strcasecmp(v1, v2) == 0) // early out check
2129 return vercmp_recursive(v1, v2);
2132 float u8_strsize(string s)
2152 // translation helpers
2153 string language_filename(string s)
2158 if(fn == "" || fn == "dump")
2160 fn = strcat(s, ".", fn);
2161 if((fh = fopen(fn, FILE_READ)) >= 0)
2168 string CTX(string s)
2170 float p = strstrofs(s, "^", 0);
2173 return substring(s, p+1, -1);
2176 // x-encoding (encoding as zero length invisible string)
2177 const string XENCODE_2 = "xX";
2178 const string XENCODE_22 = "0123456789abcdefABCDEF";
2179 string xencode(float f)
2182 d = mod(f, 22); f = floor(f / 22);
2183 c = mod(f, 22); f = floor(f / 22);
2184 b = mod(f, 22); f = floor(f / 22);
2185 a = mod(f, 2); // f = floor(f / 2);
2188 substring(XENCODE_2, a, 1),
2189 substring(XENCODE_22, b, 1),
2190 substring(XENCODE_22, c, 1),
2191 substring(XENCODE_22, d, 1)
2194 float xdecode(string s)
2197 if(substring(s, 0, 1) != "^")
2201 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2202 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2203 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2204 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2205 if(a < 0 || b < 0 || c < 0 || d < 0)
2207 return ((a * 22 + b) * 22 + c) * 22 + d;
2210 float lowestbit(float f)
2221 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2223 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2226 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2229 // escape the string to make it safe for consoles
2230 string MakeConsoleSafe(string input)
2232 input = strreplace("\n", "", input);
2233 input = strreplace("\\", "\\\\", input);
2234 input = strreplace("$", "$$", input);
2235 input = strreplace("\"", "\\\"", input);
2240 // get true/false value of a string with multiple different inputs
2241 float InterpretBoolean(string input)
2243 switch(strtolower(input))
2255 default: return stof(input);
2261 entity ReadCSQCEntity()
2267 return findfloat(world, entnum, f);
2271 float shutdown_running;
2276 void CSQC_Shutdown()
2282 if(shutdown_running)
2284 print("Recursive shutdown detected! Only restoring cvars...\n");
2288 shutdown_running = 1;
2291 cvar_settemp_restore(); // this must be done LAST, but in any case
2294 #define APPROXPASTTIME_ACCURACY_REQUIREMENT 0.05
2295 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2296 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2297 // this will use the value:
2299 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2300 // accuracy at x is 1/derivative, i.e.
2301 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2303 void WriteApproxPastTime(float dst, float t)
2305 float dt = time - t;
2307 // warning: this is approximate; do not resend when you don't have to!
2308 // be careful with sendflags here!
2309 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2312 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2315 dt = rint(bound(0, dt, 255));
2321 float ReadApproxPastTime()
2323 float dt = ReadByte();
2325 // map from range...PPROXPASTTIME_MAX / 256
2326 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2328 return servertime - dt;
2333 .float skeleton_bones_index;
2334 void Skeleton_SetBones(entity e)
2336 // set skeleton_bones to the total number of bones on the model
2337 if(e.skeleton_bones_index == e.modelindex)
2338 return; // same model, nothing to update
2341 skelindex = skel_create(e.modelindex);
2342 e.skeleton_bones = skel_get_numbones(skelindex);
2343 skel_delete(skelindex);
2344 e.skeleton_bones_index = e.modelindex;
2348 string to_execute_next_frame;
2349 void execute_next_frame()
2351 if(to_execute_next_frame)
2353 localcmd("\n", to_execute_next_frame, "\n");
2354 strunzone(to_execute_next_frame);
2355 to_execute_next_frame = string_null;
2358 void queue_to_execute_next_frame(string s)
2360 if(to_execute_next_frame)
2362 s = strcat(s, "\n", to_execute_next_frame);
2363 strunzone(to_execute_next_frame);
2365 to_execute_next_frame = strzone(s);
2368 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2371 ((( startspeedfactor + endspeedfactor - 2
2372 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2373 ) * x + startspeedfactor
2377 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2379 if(startspeedfactor < 0 || endspeedfactor < 0)
2383 // if this is the case, the possible zeros of the first derivative are outside
2385 We can calculate this condition as condition
2390 // better, see below:
2391 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2394 // if this is the case, the first derivative has no zeros at all
2395 float se = startspeedfactor + endspeedfactor;
2396 float s_e = startspeedfactor - endspeedfactor;
2397 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2400 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2401 // we also get s_e <= 6 - se
2402 // 3 * (se - 4)^2 + (6 - se)^2
2403 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2404 // Therefore, above "better" check works!
2408 // known good cases:
2416 // (3.5, [0.2..2.3])
2420 .float FindConnectedComponent_processing;
2421 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2423 entity queue_start, queue_end;
2425 // we build a queue of to-be-processed entities.
2426 // queue_start is the next entity to be checked for neighbors
2427 // queue_end is the last entity added
2429 if(e.FindConnectedComponent_processing)
2430 error("recursion or broken cleanup");
2432 // start with a 1-element queue
2433 queue_start = queue_end = e;
2434 queue_end.fld = world;
2435 queue_end.FindConnectedComponent_processing = 1;
2437 // for each queued item:
2438 for(; queue_start; queue_start = queue_start.fld)
2440 // find all neighbors of queue_start
2442 for(t = world; (t = nxt(t, queue_start, pass)); )
2444 if(t.FindConnectedComponent_processing)
2446 if(iscon(t, queue_start, pass))
2448 // it is connected? ADD IT. It will look for neighbors soon too.
2451 queue_end.fld = world;
2452 queue_end.FindConnectedComponent_processing = 1;
2458 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2459 queue_start.FindConnectedComponent_processing = 0;