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"));
42 string unescape(string in)
47 // but it doesn't seem to be necessary in my tests at least
52 for(i = 0; i < len; ++i)
54 s = substring(in, i, 1);
57 s = substring(in, i+1, 1);
59 str = strcat(str, "\n");
61 str = strcat(str, "\\");
63 str = strcat(str, substring(in, i, 2));
73 void wordwrap_cb(string s, float l, void(string) callback)
76 float lleft, i, j, wlen;
80 for (i = 0;i < strlen(s);++i)
82 if (substring(s, i, 2) == "\\n")
88 else if (substring(s, i, 1) == "\n")
93 else if (substring(s, i, 1) == " ")
103 for (j = i+1;j < strlen(s);++j)
104 // ^^ this skips over the first character of a word, which
105 // is ALWAYS part of the word
106 // this is safe since if i+1 == strlen(s), i will become
107 // strlen(s)-1 at the end of this block and the function
108 // will terminate. A space can't be the first character we
109 // read here, and neither can a \n be the start, since these
110 // two cases have been handled above.
112 c = substring(s, j, 1);
119 // we need to keep this tempstring alive even if substring is
120 // called repeatedly, so call strcat even though we're not
130 callback(substring(s, i, wlen));
131 lleft = lleft - wlen;
138 float dist_point_line(vector p, vector l0, vector ldir)
140 ldir = normalize(ldir);
142 // remove the component in line direction
143 p = p - (p * ldir) * ldir;
145 // vlen of the remaining vector
149 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
178 float median(float a, float b, float c)
181 return bound(a, b, c);
182 return bound(c, b, a);
185 // converts a number to a string with the indicated number of decimals
186 // works for up to 10 decimals!
187 string ftos_decimals(float number, float decimals)
189 // we have sprintf...
190 return sprintf("%.*f", decimals, number);
194 vector colormapPaletteColor(float c, float isPants)
198 case 0: return '0.800000 0.800000 0.800000';
199 case 1: return '0.600000 0.400000 0.000000';
200 case 2: return '0.000000 1.000000 0.501961';
201 case 3: return '0.000000 1.000000 0.000000';
202 case 4: return '1.000000 0.000000 0.000000';
203 case 5: return '0.000000 0.658824 1.000000';
204 case 6: return '0.000000 1.000000 1.000000';
205 case 7: return '0.501961 1.000000 0.000000';
206 case 8: return '0.501961 0.000000 1.000000';
207 case 9: return '1.000000 0.000000 1.000000';
208 case 10: return '1.000000 0.000000 0.501961';
209 case 11: return '0.600000 0.600000 0.600000';
210 case 12: return '1.000000 1.000000 0.000000';
211 case 13: return '0.000000 0.313725 1.000000';
212 case 14: return '1.000000 0.501961 0.000000';
216 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
217 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
218 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
221 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
222 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
223 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
224 default: return '0.000 0.000 0.000';
228 // unzone the string, and return it as tempstring. Safe to be called on string_null
229 string fstrunzone(string s)
239 float fexists(string f)
242 fh = fopen(f, FILE_READ);
249 // Databases (hash tables)
250 #define DB_BUCKETS 8192
251 void db_save(float db, string pFilename)
254 fh = fopen(pFilename, FILE_WRITE);
257 print(strcat("^1Can't write DB to ", pFilename));
261 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
262 for(i = 0; i < n; ++i)
263 fputs(fh, strcat(bufstr_get(db, i), "\n"));
272 float db_load(string pFilename)
274 float db, fh, i, j, n;
279 fh = fopen(pFilename, FILE_READ);
283 if(stof(l) == DB_BUCKETS)
286 while((l = fgets(fh)))
289 bufstr_set(db, i, l);
295 // different count of buckets, or a dump?
296 // need to reorganize the database then (SLOW)
298 // note: we also parse the first line (l) in case the DB file is
299 // missing the bucket count
302 n = tokenizebyseparator(l, "\\");
303 for(j = 2; j < n; j += 2)
304 db_put(db, argv(j-1), uri_unescape(argv(j)));
306 while((l = fgets(fh)));
312 void db_dump(float db, string pFilename)
314 float fh, i, j, n, m;
315 fh = fopen(pFilename, FILE_WRITE);
317 error(strcat("Can't dump DB to ", pFilename));
320 for(i = 0; i < n; ++i)
322 m = tokenizebyseparator(bufstr_get(db, i), "\\");
323 for(j = 2; j < m; j += 2)
324 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
329 void db_close(float db)
334 string db_get(float db, string pKey)
337 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
338 return uri_unescape(infoget(bufstr_get(db, h), pKey));
341 void db_put(float db, string pKey, string pValue)
344 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
345 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
352 db = db_load("foo.db");
353 print("LOADED. FILL...\n");
354 for(i = 0; i < DB_BUCKETS; ++i)
355 db_put(db, ftos(random()), "X");
356 print("FILLED. SAVE...\n");
357 db_save(db, "foo.db");
358 print("SAVED. CLOSE...\n");
363 // Multiline text file buffers
364 float buf_load(string pFilename)
371 fh = fopen(pFilename, FILE_READ);
378 while((l = fgets(fh)))
380 bufstr_set(buf, i, l);
387 void buf_save(float buf, string pFilename)
390 fh = fopen(pFilename, FILE_WRITE);
392 error(strcat("Can't write buf to ", pFilename));
393 n = buf_getsize(buf);
394 for(i = 0; i < n; ++i)
395 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
399 string GametypeNameFromType(float g)
401 if (g == GAME_DEATHMATCH) return "dm";
402 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
403 else if (g == GAME_DOMINATION) return "dom";
404 else if (g == GAME_CTF) return "ctf";
405 else if (g == GAME_RUNEMATCH) return "rune";
406 else if (g == GAME_LMS) return "lms";
407 else if (g == GAME_ARENA) return "arena";
408 else if (g == GAME_CA) return "ca";
409 else if (g == GAME_KEYHUNT) return "kh";
410 else if (g == GAME_ONSLAUGHT) return "ons";
411 else if (g == GAME_ASSAULT) return "as";
412 else if (g == GAME_RACE) return "rc";
413 else if (g == GAME_NEXBALL) return "nexball";
414 else if (g == GAME_CTS) return "cts";
415 else if (g == GAME_FREEZETAG) return "freezetag";
416 else if (g == GAME_KEEPAWAY) return "ka";
420 string mmsss(float tenths)
424 tenths = floor(tenths + 0.5);
425 minutes = floor(tenths / 600);
426 tenths -= minutes * 600;
427 s = ftos(1000 + tenths);
428 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
431 string mmssss(float hundredths)
435 hundredths = floor(hundredths + 0.5);
436 minutes = floor(hundredths / 6000);
437 hundredths -= minutes * 6000;
438 s = ftos(10000 + hundredths);
439 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
442 string ScoreString(float pFlags, float pValue)
447 pValue = floor(pValue + 0.5); // round
449 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
451 else if(pFlags & SFL_RANK)
453 valstr = ftos(pValue);
455 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
456 valstr = strcat(valstr, "th");
457 else if(substring(valstr, l - 1, 1) == "1")
458 valstr = strcat(valstr, "st");
459 else if(substring(valstr, l - 1, 1) == "2")
460 valstr = strcat(valstr, "nd");
461 else if(substring(valstr, l - 1, 1) == "3")
462 valstr = strcat(valstr, "rd");
464 valstr = strcat(valstr, "th");
466 else if(pFlags & SFL_TIME)
467 valstr = TIME_ENCODED_TOSTRING(pValue);
469 valstr = ftos(pValue);
474 vector cross(vector a, vector b)
477 '1 0 0' * (a_y * b_z - a_z * b_y)
478 + '0 1 0' * (a_z * b_x - a_x * b_z)
479 + '0 0 1' * (a_x * b_y - a_y * b_x);
482 // compressed vector format:
483 // like MD3, just even shorter
484 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
485 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
486 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
487 // length = 2^(length_encoded/8) / 8
488 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
489 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
490 // the special value 0 indicates the zero vector
492 float lengthLogTable[128];
494 float invertLengthLog(float x)
496 float l, r, m, lerr, rerr;
498 if(x >= lengthLogTable[127])
500 if(x <= lengthLogTable[0])
508 m = floor((l + r) / 2);
509 if(lengthLogTable[m] < x)
515 // now: r is >=, l is <
516 lerr = (x - lengthLogTable[l]);
517 rerr = (lengthLogTable[r] - x);
523 vector decompressShortVector(float data)
529 p = (data & 0xF000) / 0x1000;
530 y = (data & 0x0F80) / 0x80;
531 len = (data & 0x007F);
533 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
546 y = .19634954084936207740 * y;
547 p = .19634954084936207740 * p - 1.57079632679489661922;
548 out_x = cos(y) * cos(p);
549 out_y = sin(y) * cos(p);
553 //print("decompressed: ", vtos(out), "\n");
555 return out * lengthLogTable[len];
558 float compressShortVector(vector vec)
564 //print("compress: ", vtos(vec), "\n");
565 ang = vectoangles(vec);
569 if(ang_x < -90 && ang_x > +90)
570 error("BOGUS vectoangles");
571 //print("angles: ", vtos(ang), "\n");
573 p = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
582 y = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
583 len = invertLengthLog(vlen(vec));
585 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
587 return (p * 0x1000) + (y * 0x80) + len;
590 void compressShortVector_init()
595 for(i = 0; i < 128; ++i)
597 lengthLogTable[i] = l;
601 if(cvar("developer"))
603 print("Verifying vector compression table...\n");
604 for(i = 0x0F00; i < 0xFFFF; ++i)
605 if(i != compressShortVector(decompressShortVector(i)))
607 print("BROKEN vector compression: ", ftos(i));
608 print(" -> ", vtos(decompressShortVector(i)));
609 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
618 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
620 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
621 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
628 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
629 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
630 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
631 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
636 string fixPriorityList(string order, float from, float to, float subtract, float complete)
641 n = tokenize_console(order);
643 for(i = 0; i < n; ++i)
648 if(w >= from && w <= to)
649 neworder = strcat(neworder, ftos(w), " ");
653 if(w >= from && w <= to)
654 neworder = strcat(neworder, ftos(w), " ");
661 n = tokenize_console(neworder);
662 for(w = to; w >= from; --w)
664 for(i = 0; i < n; ++i)
665 if(stof(argv(i)) == w)
667 if(i == n) // not found
668 neworder = strcat(neworder, ftos(w), " ");
672 return substring(neworder, 0, strlen(neworder) - 1);
675 string mapPriorityList(string order, string(string) mapfunc)
680 n = tokenize_console(order);
682 for(i = 0; i < n; ++i)
683 neworder = strcat(neworder, mapfunc(argv(i)), " ");
685 return substring(neworder, 0, strlen(neworder) - 1);
688 string swapInPriorityList(string order, float i, float j)
693 n = tokenize_console(order);
695 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
698 for(w = 0; w < n; ++w)
701 s = strcat(s, argv(j), " ");
703 s = strcat(s, argv(i), " ");
705 s = strcat(s, argv(w), " ");
707 return substring(s, 0, strlen(s) - 1);
713 float cvar_value_issafe(string s)
715 if(strstrofs(s, "\"", 0) >= 0)
717 if(strstrofs(s, "\\", 0) >= 0)
719 if(strstrofs(s, ";", 0) >= 0)
721 if(strstrofs(s, "$", 0) >= 0)
723 if(strstrofs(s, "\r", 0) >= 0)
725 if(strstrofs(s, "\n", 0) >= 0)
731 void get_mi_min_max(float mode)
736 strunzone(mi_shortname);
737 mi_shortname = mapname;
738 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
739 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
740 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
741 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
742 mi_shortname = strzone(mi_shortname);
754 MapInfo_Get_ByName(mi_shortname, 0, 0);
755 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
757 mi_min = MapInfo_Map_mins;
758 mi_max = MapInfo_Map_maxs;
766 tracebox('1 0 0' * mi_x,
767 '0 1 0' * mi_y + '0 0 1' * mi_z,
768 '0 1 0' * ma_y + '0 0 1' * ma_z,
772 if(!trace_startsolid)
773 mi_min_x = trace_endpos_x;
775 tracebox('0 1 0' * mi_y,
776 '1 0 0' * mi_x + '0 0 1' * mi_z,
777 '1 0 0' * ma_x + '0 0 1' * ma_z,
781 if(!trace_startsolid)
782 mi_min_y = trace_endpos_y;
784 tracebox('0 0 1' * mi_z,
785 '1 0 0' * mi_x + '0 1 0' * mi_y,
786 '1 0 0' * ma_x + '0 1 0' * ma_y,
790 if(!trace_startsolid)
791 mi_min_z = trace_endpos_z;
793 tracebox('1 0 0' * ma_x,
794 '0 1 0' * mi_y + '0 0 1' * mi_z,
795 '0 1 0' * ma_y + '0 0 1' * ma_z,
799 if(!trace_startsolid)
800 mi_max_x = trace_endpos_x;
802 tracebox('0 1 0' * ma_y,
803 '1 0 0' * mi_x + '0 0 1' * mi_z,
804 '1 0 0' * ma_x + '0 0 1' * ma_z,
808 if(!trace_startsolid)
809 mi_max_y = trace_endpos_y;
811 tracebox('0 0 1' * ma_z,
812 '1 0 0' * mi_x + '0 1 0' * mi_y,
813 '1 0 0' * ma_x + '0 1 0' * ma_y,
817 if(!trace_startsolid)
818 mi_max_z = trace_endpos_z;
823 void get_mi_min_max_texcoords(float mode)
827 get_mi_min_max(mode);
832 // extend mi_picmax to get a square aspect ratio
833 // center the map in that area
834 extend = mi_picmax - mi_picmin;
835 if(extend_y > extend_x)
837 mi_picmin_x -= (extend_y - extend_x) * 0.5;
838 mi_picmax_x += (extend_y - extend_x) * 0.5;
842 mi_picmin_y -= (extend_x - extend_y) * 0.5;
843 mi_picmax_y += (extend_x - extend_y) * 0.5;
846 // add another some percent
847 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
851 // calculate the texcoords
852 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
853 // first the two corners of the origin
854 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
855 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
856 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
857 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
858 // then the other corners
859 mi_pictexcoord1_x = mi_pictexcoord0_x;
860 mi_pictexcoord1_y = mi_pictexcoord2_y;
861 mi_pictexcoord3_x = mi_pictexcoord2_x;
862 mi_pictexcoord3_y = mi_pictexcoord0_y;
866 void cvar_settemp(string cv, string val)
869 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
873 e.classname = "saved_cvar_value";
874 e.netname = strzone(cv);
875 e.message = strzone(cvar_string(cv));
880 void cvar_settemp_restore()
883 while((e = find(world, classname, "saved_cvar_value")))
885 cvar_set(e.netname, e.message);
890 float almost_equals(float a, float b)
893 eps = (max(a, -a) + max(b, -b)) * 0.001;
894 if(a - b < eps && b - a < eps)
899 float almost_in_bounds(float a, float b, float c)
902 eps = (max(a, -a) + max(c, -c)) * 0.001;
903 return b == median(a - eps, b, c + eps);
906 float power2of(float e)
910 float log2of(float x)
912 // NOTE: generated code
985 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
992 return (rgb_y - rgb_z) / (ma - mi);
994 return (rgb_y - rgb_z) / (ma - mi) + 6;
997 return (rgb_z - rgb_x) / (ma - mi) + 2;
998 else // if(ma == rgb_z)
999 return (rgb_x - rgb_y) / (ma - mi) + 4;
1002 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1006 hue -= 6 * floor(hue / 6);
1008 //else if(ma == rgb_x)
1009 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1013 rgb_y = hue * (ma - mi) + mi;
1016 //else if(ma == rgb_y)
1017 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1020 rgb_x = (2 - hue) * (ma - mi) + mi;
1028 rgb_z = (hue - 2) * (ma - mi) + mi;
1030 //else // if(ma == rgb_z)
1031 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1035 rgb_y = (4 - hue) * (ma - mi) + mi;
1040 rgb_x = (hue - 4) * (ma - mi) + mi;
1044 //else if(ma == rgb_x)
1045 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1046 else // if(hue <= 6)
1050 rgb_z = (6 - hue) * (ma - mi) + mi;
1056 vector rgb_to_hsv(vector rgb)
1061 mi = min(rgb_x, rgb_y, rgb_z);
1062 ma = max(rgb_x, rgb_y, rgb_z);
1064 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1075 vector hsv_to_rgb(vector hsv)
1077 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1080 vector rgb_to_hsl(vector rgb)
1085 mi = min(rgb_x, rgb_y, rgb_z);
1086 ma = max(rgb_x, rgb_y, rgb_z);
1088 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1090 hsl_z = 0.5 * (mi + ma);
1093 else if(hsl_z <= 0.5)
1094 hsl_y = (ma - mi) / (2*hsl_z);
1095 else // if(hsl_z > 0.5)
1096 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1101 vector hsl_to_rgb(vector hsl)
1103 float mi, ma, maminusmi;
1106 maminusmi = hsl_y * 2 * hsl_z;
1108 maminusmi = hsl_y * (2 - 2 * hsl_z);
1110 // hsl_z = 0.5 * mi + 0.5 * ma
1111 // maminusmi = - mi + ma
1112 mi = hsl_z - 0.5 * maminusmi;
1113 ma = hsl_z + 0.5 * maminusmi;
1115 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1118 string rgb_to_hexcolor(vector rgb)
1123 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1124 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1125 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1129 // requires that m2>m1 in all coordinates, and that m4>m3
1130 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;}
1132 // requires the same, but is a stronger condition
1133 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;}
1138 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1141 // The following function is SLOW.
1142 // For your safety and for the protection of those around you...
1143 // DO NOT CALL THIS AT HOME.
1144 // No really, don't.
1145 if(w(theText, theSize) <= maxWidth)
1146 return strlen(theText); // yeah!
1148 // binary search for right place to cut string
1150 float left, right, middle; // this always works
1152 right = strlen(theText); // this always fails
1155 middle = floor((left + right) / 2);
1156 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1161 while(left < right - 1);
1163 if(w("^7", theSize) == 0) // detect color codes support in the width function
1165 // NOTE: when color codes are involved, this binary search is,
1166 // mathematically, BROKEN. However, it is obviously guaranteed to
1167 // terminate, as the range still halves each time - but nevertheless, it is
1168 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1169 // range, and "right" is outside).
1171 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1172 // and decrease left on the basis of the chars detected of the truncated tag
1173 // Even if the ^xrgb tag is not complete/correct, left is decreased
1174 // (sometimes too much but with a correct result)
1175 // it fixes also ^[0-9]
1176 while(left >= 1 && substring(theText, left-1, 1) == "^")
1179 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1181 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1183 ch = str2chr(theText, left-1);
1184 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1187 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1189 ch = str2chr(theText, left-2);
1190 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1192 ch = str2chr(theText, left-1);
1193 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1202 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1205 // The following function is SLOW.
1206 // For your safety and for the protection of those around you...
1207 // DO NOT CALL THIS AT HOME.
1208 // No really, don't.
1209 if(w(theText) <= maxWidth)
1210 return strlen(theText); // yeah!
1212 // binary search for right place to cut string
1214 float left, right, middle; // this always works
1216 right = strlen(theText); // this always fails
1219 middle = floor((left + right) / 2);
1220 if(w(substring(theText, 0, middle)) <= maxWidth)
1225 while(left < right - 1);
1227 if(w("^7") == 0) // detect color codes support in the width function
1229 // NOTE: when color codes are involved, this binary search is,
1230 // mathematically, BROKEN. However, it is obviously guaranteed to
1231 // terminate, as the range still halves each time - but nevertheless, it is
1232 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1233 // range, and "right" is outside).
1235 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1236 // and decrease left on the basis of the chars detected of the truncated tag
1237 // Even if the ^xrgb tag is not complete/correct, left is decreased
1238 // (sometimes too much but with a correct result)
1239 // it fixes also ^[0-9]
1240 while(left >= 1 && substring(theText, left-1, 1) == "^")
1243 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1245 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1247 ch = str2chr(theText, left-1);
1248 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1251 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1253 ch = str2chr(theText, left-2);
1254 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1256 ch = str2chr(theText, left-1);
1257 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1266 string find_last_color_code(string s)
1268 float start, len, i, carets;
1269 start = strstrofs(s, "^", 0);
1270 if (start == -1) // no caret found
1273 for(i = len; i >= start; --i)
1275 if(substring(s, i, 1) != "^")
1279 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1282 // check if carets aren't all escaped
1283 if (carets == 1 || mod(carets, 2) == 1) // first check is just an optimization
1286 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1287 return substring(s, i, 2);
1290 if(substring(s, i+1, 1) == "x")
1291 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1292 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1293 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1294 return substring(s, i, 5);
1296 i -= carets; // this also skips one char before the carets
1302 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1308 s = getWrappedLine_remaining;
1312 getWrappedLine_remaining = string_null;
1313 return s; // the line has no size ANYWAY, nothing would be displayed.
1316 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1317 if(cantake > 0 && cantake < strlen(s))
1320 while(take > 0 && substring(s, take, 1) != " ")
1324 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1325 if(getWrappedLine_remaining == "")
1326 getWrappedLine_remaining = string_null;
1327 else if (tw("^7", theFontSize) == 0)
1328 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1329 return substring(s, 0, cantake);
1333 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
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, take)), getWrappedLine_remaining);
1338 return substring(s, 0, take);
1343 getWrappedLine_remaining = string_null;
1348 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1354 s = getWrappedLine_remaining;
1358 getWrappedLine_remaining = string_null;
1359 return s; // the line has no size ANYWAY, nothing would be displayed.
1362 cantake = textLengthUpToLength(s, w, tw);
1363 if(cantake > 0 && cantake < strlen(s))
1366 while(take > 0 && substring(s, take, 1) != " ")
1370 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1371 if(getWrappedLine_remaining == "")
1372 getWrappedLine_remaining = string_null;
1373 else if (tw("^7") == 0)
1374 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1375 return substring(s, 0, cantake);
1379 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
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, take)), getWrappedLine_remaining);
1384 return substring(s, 0, take);
1389 getWrappedLine_remaining = string_null;
1394 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1396 if(tw(theText, theFontSize) <= maxWidth)
1399 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1402 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1404 if(tw(theText) <= maxWidth)
1407 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1410 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1412 string subpattern, subpattern2, subpattern3, subpattern4;
1413 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1415 subpattern2 = ",teams,";
1417 subpattern2 = ",noteams,";
1419 subpattern3 = ",teamspawns,";
1421 subpattern3 = ",noteamspawns,";
1422 if(gt == GAME_RACE || gt == GAME_CTS)
1423 subpattern4 = ",race,";
1425 subpattern4 = string_null;
1427 if(substring(pattern, 0, 1) == "-")
1429 pattern = substring(pattern, 1, strlen(pattern) - 1);
1430 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1432 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1434 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1436 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1441 if(substring(pattern, 0, 1) == "+")
1442 pattern = substring(pattern, 1, strlen(pattern) - 1);
1443 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1444 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1445 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1446 if((!subpattern4) || strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1452 void shuffle(float n, swapfunc_t swap, entity pass)
1455 for(i = 1; i < n; ++i)
1457 // swap i-th item at a random position from 0 to i
1458 // proof for even distribution:
1461 // item n+1 gets at any position with chance 1/(n+1)
1462 // all others will get their 1/n chance reduced by factor n/(n+1)
1463 // to be on place n+1, their chance will be 1/(n+1)
1464 // 1/n * n/(n+1) = 1/(n+1)
1466 j = floor(random() * (i + 1));
1472 string substring_range(string s, float b, float e)
1474 return substring(s, b, e - b);
1477 string swapwords(string str, float i, float j)
1480 string s1, s2, s3, s4, s5;
1481 float si, ei, sj, ej, s0, en;
1482 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1483 si = argv_start_index(i);
1484 sj = argv_start_index(j);
1485 ei = argv_end_index(i);
1486 ej = argv_end_index(j);
1487 s0 = argv_start_index(0);
1488 en = argv_end_index(n-1);
1489 s1 = substring_range(str, s0, si);
1490 s2 = substring_range(str, si, ei);
1491 s3 = substring_range(str, ei, sj);
1492 s4 = substring_range(str, sj, ej);
1493 s5 = substring_range(str, ej, en);
1494 return strcat(s1, s4, s3, s2, s5);
1497 string _shufflewords_str;
1498 void _shufflewords_swapfunc(float i, float j, entity pass)
1500 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1502 string shufflewords(string str)
1505 _shufflewords_str = str;
1506 n = tokenizebyseparator(str, " ");
1507 shuffle(n, _shufflewords_swapfunc, world);
1508 str = _shufflewords_str;
1509 _shufflewords_str = string_null;
1513 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1529 // actually, every number solves the equation!
1540 if(a > 0) // put the smaller solution first
1542 v_x = ((-b)-D) / (2*a);
1543 v_y = ((-b)+D) / (2*a);
1547 v_x = (-b+D) / (2*a);
1548 v_y = (-b-D) / (2*a);
1554 // complex solutions!
1567 void check_unacceptable_compiler_bugs()
1569 if(cvar("_allow_unacceptable_compiler_bugs"))
1571 tokenize_console("foo bar");
1572 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1573 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.");
1576 float compressShotOrigin(vector v)
1580 y = rint(v_y * 4) + 128;
1581 z = rint(v_z * 4) + 128;
1582 if(x > 255 || x < 0)
1584 print("shot origin ", vtos(v), " x out of bounds\n");
1585 x = bound(0, x, 255);
1587 if(y > 255 || y < 0)
1589 print("shot origin ", vtos(v), " y out of bounds\n");
1590 y = bound(0, y, 255);
1592 if(z > 255 || z < 0)
1594 print("shot origin ", vtos(v), " z out of bounds\n");
1595 z = bound(0, z, 255);
1597 return x * 0x10000 + y * 0x100 + z;
1599 vector decompressShotOrigin(float f)
1602 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1603 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1604 v_z = ((f & 0xFF) - 128) / 4;
1608 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1610 float start, end, root, child;
1613 start = floor((n - 2) / 2);
1616 // siftdown(start, count-1);
1618 while(root * 2 + 1 <= n-1)
1620 child = root * 2 + 1;
1622 if(cmp(child, child+1, pass) < 0)
1624 if(cmp(root, child, pass) < 0)
1626 swap(root, child, pass);
1642 // siftdown(0, end);
1644 while(root * 2 + 1 <= end)
1646 child = root * 2 + 1;
1647 if(child < end && cmp(child, child+1, pass) < 0)
1649 if(cmp(root, child, pass) < 0)
1651 swap(root, child, pass);
1661 void RandomSelection_Init()
1663 RandomSelection_totalweight = 0;
1664 RandomSelection_chosen_ent = world;
1665 RandomSelection_chosen_float = 0;
1666 RandomSelection_chosen_string = string_null;
1667 RandomSelection_best_priority = -1;
1669 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1671 if(priority > RandomSelection_best_priority)
1673 RandomSelection_best_priority = priority;
1674 RandomSelection_chosen_ent = e;
1675 RandomSelection_chosen_float = f;
1676 RandomSelection_chosen_string = s;
1677 RandomSelection_totalweight = weight;
1679 else if(priority == RandomSelection_best_priority)
1681 RandomSelection_totalweight += weight;
1682 if(random() * RandomSelection_totalweight <= weight)
1684 RandomSelection_chosen_ent = e;
1685 RandomSelection_chosen_float = f;
1686 RandomSelection_chosen_string = s;
1691 vector healtharmor_maxdamage(float h, float a, float armorblock)
1693 // NOTE: we'll always choose the SMALLER value...
1694 float healthdamage, armordamage, armorideal;
1696 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1697 armordamage = a + (h - 1); // damage we can take if we could use more armor
1698 armorideal = healthdamage * armorblock;
1700 if(armordamage < healthdamage)
1713 vector healtharmor_applydamage(float a, float armorblock, float damage)
1716 v_y = bound(0, damage * armorblock, a); // save
1717 v_x = bound(0, damage - v_y, damage); // take
1722 string getcurrentmod()
1726 m = cvar_string("fs_gamedir");
1727 n = tokenize_console(m);
1739 v = ReadShort() * 256; // note: this is signed
1740 v += ReadByte(); // note: this is unsigned
1744 void WriteInt24_t(float dst, float val)
1747 WriteShort(dst, (v = floor(val / 256)));
1748 WriteByte(dst, val - v * 256); // 0..255
1753 float float2range11(float f)
1755 // continuous function mapping all reals into -1..1
1756 return f / (fabs(f) + 1);
1759 float float2range01(float f)
1761 // continuous function mapping all reals into 0..1
1762 return 0.5 + 0.5 * float2range11(f);
1765 // from the GNU Scientific Library
1766 float gsl_ran_gaussian_lastvalue;
1767 float gsl_ran_gaussian_lastvalue_set;
1768 float gsl_ran_gaussian(float sigma)
1771 if(gsl_ran_gaussian_lastvalue_set)
1773 gsl_ran_gaussian_lastvalue_set = 0;
1774 return sigma * gsl_ran_gaussian_lastvalue;
1778 a = random() * 2 * M_PI;
1779 b = sqrt(-2 * log(random()));
1780 gsl_ran_gaussian_lastvalue = cos(a) * b;
1781 gsl_ran_gaussian_lastvalue_set = 1;
1782 return sigma * sin(a) * b;
1786 string car(string s)
1789 o = strstrofs(s, " ", 0);
1792 return substring(s, 0, o);
1794 string cdr(string s)
1797 o = strstrofs(s, " ", 0);
1800 return substring(s, o + 1, strlen(s) - (o + 1));
1802 float matchacl(string acl, string str)
1809 t = car(acl); acl = cdr(acl);
1811 if(substring(t, 0, 1) == "-")
1814 t = substring(t, 1, strlen(t) - 1);
1816 else if(substring(t, 0, 1) == "+")
1817 t = substring(t, 1, strlen(t) - 1);
1818 if(substring(t, -1, 1) == "*")
1820 t = substring(t, 0, strlen(t) - 1);
1821 s = substring(s, 0, strlen(t));
1833 float startsWith(string haystack, string needle)
1835 return substring(haystack, 0, strlen(needle)) == needle;
1837 float startsWithNocase(string haystack, string needle)
1839 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1842 string get_model_datafilename(string m, float sk, string fil)
1847 m = "models/player/*_";
1849 m = strcat(m, ftos(sk));
1852 return strcat(m, ".", fil);
1855 float get_model_parameters(string m, float sk)
1860 get_model_parameters_modelname = string_null;
1861 get_model_parameters_modelskin = -1;
1862 get_model_parameters_name = string_null;
1863 get_model_parameters_species = -1;
1864 get_model_parameters_sex = string_null;
1865 get_model_parameters_weight = -1;
1866 get_model_parameters_age = -1;
1867 get_model_parameters_desc = string_null;
1873 if(substring(m, -4, -1) != ".txt")
1875 if(substring(m, -6, 1) != "_")
1877 sk = stof(substring(m, -5, 1));
1878 m = substring(m, 0, -7);
1881 fn = get_model_datafilename(m, sk, "txt");
1882 fh = fopen(fn, FILE_READ);
1886 fn = get_model_datafilename(m, sk, "txt");
1887 fh = fopen(fn, FILE_READ);
1892 get_model_parameters_modelname = m;
1893 get_model_parameters_modelskin = sk;
1894 while((s = fgets(fh)))
1897 break; // next lines will be description
1901 get_model_parameters_name = s;
1905 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1906 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1907 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1908 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1909 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1910 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1911 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1914 get_model_parameters_sex = s;
1916 get_model_parameters_weight = stof(s);
1918 get_model_parameters_age = stof(s);
1921 while((s = fgets(fh)))
1923 if(get_model_parameters_desc)
1924 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1926 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1934 vector vec2(vector v)
1941 vector NearestPointOnBox(entity box, vector org)
1943 vector m1, m2, nearest;
1945 m1 = box.mins + box.origin;
1946 m2 = box.maxs + box.origin;
1948 nearest_x = bound(m1_x, org_x, m2_x);
1949 nearest_y = bound(m1_y, org_y, m2_y);
1950 nearest_z = bound(m1_z, org_z, m2_z);
1956 float vercmp_recursive(string v1, string v2)
1962 dot1 = strstrofs(v1, ".", 0);
1963 dot2 = strstrofs(v2, ".", 0);
1967 s1 = substring(v1, 0, dot1);
1971 s2 = substring(v2, 0, dot2);
1973 r = stof(s1) - stof(s2);
1977 r = strcasecmp(s1, s2);
1990 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
1993 float vercmp(string v1, string v2)
1995 if(strcasecmp(v1, v2) == 0) // early out check
2004 return vercmp_recursive(v1, v2);
2007 float u8_strsize(string s)
2027 // translation helpers
2028 string language_filename(string s)
2033 if(fn == "" || fn == "dump")
2035 fn = strcat(s, ".", fn);
2036 if((fh = fopen(fn, FILE_READ)) >= 0)
2043 string CTX(string s)
2045 float p = strstrofs(s, "^", 0);
2048 return substring(s, p+1, -1);
2051 // x-encoding (encoding as zero length invisible string)
2052 const string XENCODE_2 = "xX";
2053 const string XENCODE_22 = "0123456789abcdefABCDEF";
2054 string xencode(float f)
2057 d = mod(f, 22); f = floor(f / 22);
2058 c = mod(f, 22); f = floor(f / 22);
2059 b = mod(f, 22); f = floor(f / 22);
2060 a = mod(f, 2); // f = floor(f / 2);
2063 substring(XENCODE_2, a, 1),
2064 substring(XENCODE_22, b, 1),
2065 substring(XENCODE_22, c, 1),
2066 substring(XENCODE_22, d, 1)
2069 float xdecode(string s)
2072 if(substring(s, 0, 1) != "^")
2076 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2077 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2078 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2079 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2080 if(a < 0 || b < 0 || c < 0 || d < 0)
2082 return ((a * 22 + b) * 22 + c) * 22 + d;
2085 float lowestbit(float f)
2096 entity ReadCSQCEntity()
2102 return findfloat(world, entnum, f);
2106 float shutdown_running;
2111 void CSQC_Shutdown()
2117 if(shutdown_running)
2119 print("Recursive shutdown detected! Only restoring cvars...\n");
2123 shutdown_running = 1;
2126 cvar_settemp_restore(); // this must be done LAST, but in any case