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 local 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)
193 // if negative, cut off the sign first
195 return strcat("-", ftos_decimals(-number, decimals));
196 // it now is always positive!
199 number = floor(number * pow(10, decimals) + 0.5);
202 result = ftos(number);
203 len = strlen(result);
204 // does it have a decimal point (should not happen)? If there is one, it is always at len-7)
205 // if ftos had messed it up, which should never happen: "34278.000000"
207 if(substring(result, len - 7, 1) == ".")
209 dprint("ftos(integer) has comma? Can't be. Affected result: ", result, "\n");
210 result = substring(result, 0, len - 7);
215 return result; // don't insert a point for zero decimals
216 // is it too short? If yes, insert leading zeroes
219 result = strcat(substring("0000000000", 0, decimals - len + 1), result);
222 // and now... INSERT THE POINT!
223 tmp = substring(result, len - decimals, decimals);
224 result = strcat(substring(result, 0, len - decimals), ".", tmp);
229 vector colormapPaletteColor(float c, float isPants)
233 case 0: return '0.800000 0.800000 0.800000';
234 case 1: return '0.600000 0.400000 0.000000';
235 case 2: return '0.000000 1.000000 0.501961';
236 case 3: return '0.000000 1.000000 0.000000';
237 case 4: return '1.000000 0.000000 0.000000';
238 case 5: return '0.000000 0.501961 1.000000';
239 case 6: return '0.000000 1.000000 1.000000';
240 case 7: return '0.501961 1.000000 0.000000';
241 case 8: return '0.501961 0.000000 1.000000';
242 case 9: return '1.000000 0.000000 1.000000';
243 case 10: return '1.000000 0.000000 0.501961';
244 case 11: return '0.600000 0.600000 0.600000';
245 case 12: return '1.000000 1.000000 0.000000';
246 case 13: return '0.000000 0.000000 1.000000';
247 case 14: return '1.000000 0.501961 0.000000';
251 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
252 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
253 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
256 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
257 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
258 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
259 default: return '0.000 0.000 0.000';
263 // unzone the string, and return it as tempstring. Safe to be called on string_null
264 string fstrunzone(string s)
274 // Databases (hash tables)
275 #define DB_BUCKETS 8192
276 void db_save(float db, string pFilename)
279 fh = fopen(pFilename, FILE_WRITE);
282 print(strcat("^1Can't write DB to ", pFilename));
286 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
287 for(i = 0; i < n; ++i)
288 fputs(fh, strcat(bufstr_get(db, i), "\n"));
297 float db_load(string pFilename)
299 float db, fh, i, j, n;
304 fh = fopen(pFilename, FILE_READ);
307 if(stof(fgets(fh)) == DB_BUCKETS)
310 while((l = fgets(fh)))
313 bufstr_set(db, i, l);
319 // different count of buckets?
320 // need to reorganize the database then (SLOW)
321 while((l = fgets(fh)))
323 n = tokenizebyseparator(l, "\\");
324 for(j = 2; j < n; j += 2)
325 db_put(db, argv(j-1), uri_unescape(argv(j)));
332 void db_dump(float db, string pFilename)
334 float fh, i, j, n, m;
335 fh = fopen(pFilename, FILE_WRITE);
337 error(strcat("Can't dump DB to ", pFilename));
340 for(i = 0; i < n; ++i)
342 m = tokenizebyseparator(bufstr_get(db, i), "\\");
343 for(j = 2; j < m; j += 2)
344 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
349 void db_close(float db)
354 string db_get(float db, string pKey)
357 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
358 return uri_unescape(infoget(bufstr_get(db, h), pKey));
361 void db_put(float db, string pKey, string pValue)
364 h = mod(crc16(FALSE, pKey), DB_BUCKETS);
365 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
372 db = db_load("foo.db");
373 print("LOADED. FILL...\n");
374 for(i = 0; i < DB_BUCKETS; ++i)
375 db_put(db, ftos(random()), "X");
376 print("FILLED. SAVE...\n");
377 db_save(db, "foo.db");
378 print("SAVED. CLOSE...\n");
383 // Multiline text file buffers
384 float buf_load(string pFilename)
391 fh = fopen(pFilename, FILE_READ);
395 while((l = fgets(fh)))
397 bufstr_set(buf, i, l);
404 void buf_save(float buf, string pFilename)
407 fh = fopen(pFilename, FILE_WRITE);
409 error(strcat("Can't write buf to ", pFilename));
410 n = buf_getsize(buf);
411 for(i = 0; i < n; ++i)
412 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
416 string GametypeNameFromType(float g)
418 if (g == GAME_DEATHMATCH) return "dm";
419 else if (g == GAME_TEAM_DEATHMATCH) return "tdm";
420 else if (g == GAME_DOMINATION) return "dom";
421 else if (g == GAME_CTF) return "ctf";
422 else if (g == GAME_RUNEMATCH) return "rune";
423 else if (g == GAME_LMS) return "lms";
424 else if (g == GAME_ARENA) return "arena";
425 else if (g == GAME_KEYHUNT) return "kh";
426 else if (g == GAME_ONSLAUGHT) return "ons";
427 else if (g == GAME_ASSAULT) return "as";
428 else if (g == GAME_RACE) return "race";
432 string mmsss(float tenths)
436 tenths = floor(tenths + 0.5);
437 minutes = floor(tenths / 600);
438 tenths -= minutes * 600;
439 s = ftos(1000 + tenths);
440 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
443 string ScoreString(float pFlags, float pValue)
448 pValue = floor(pValue + 0.5); // round
450 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
452 else if(pFlags & SFL_RANK)
454 valstr = ftos(pValue);
456 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
457 valstr = strcat(valstr, "th");
458 else if(substring(valstr, l - 1, 1) == "1")
459 valstr = strcat(valstr, "st");
460 else if(substring(valstr, l - 1, 1) == "2")
461 valstr = strcat(valstr, "nd");
462 else if(substring(valstr, l - 1, 1) == "3")
463 valstr = strcat(valstr, "rd");
465 valstr = strcat(valstr, "th");
467 else if(pFlags & SFL_TIME)
468 valstr = mmsss(pValue);
470 valstr = ftos(pValue);
475 vector cross(vector a, vector b)
478 '1 0 0' * (a_y * b_z - a_z * b_y)
479 + '0 1 0' * (a_z * b_x - a_x * b_z)
480 + '0 0 1' * (a_x * b_y - a_y * b_x);
483 // compressed vector format:
484 // like MD3, just even shorter
485 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
486 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
487 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
488 // length = 2^(length_encoded/8) / 8
489 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
490 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
491 // the special value 0 indicates the zero vector
493 float lengthLogTable[128];
495 float invertLengthLog(float x)
497 float l, r, m, lerr, rerr;
499 if(x >= lengthLogTable[127])
501 if(x <= lengthLogTable[0])
509 m = floor((l + r) / 2);
510 if(lengthLogTable[m] < x)
516 // now: r is >=, l is <
517 lerr = (x - lengthLogTable[l]);
518 rerr = (lengthLogTable[r] - x);
524 vector decompressShortVector(float data)
527 float pitch, yaw, len;
530 pitch = (data & 0xF000) / 0x1000;
531 yaw = (data & 0x0F80) / 0x80;
532 len = (data & 0x007F);
534 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
547 yaw = .19634954084936207740 * yaw;
548 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
549 out_x = cos(yaw) * cos(pitch);
550 out_y = sin(yaw) * cos(pitch);
554 //print("decompressed: ", vtos(out), "\n");
556 return out * lengthLogTable[len];
559 float compressShortVector(vector vec)
562 float pitch, yaw, len;
565 //print("compress: ", vtos(vec), "\n");
566 ang = vectoangles(vec);
570 if(ang_x < -90 && ang_x > +90)
571 error("BOGUS vectoangles");
572 //print("angles: ", vtos(ang), "\n");
574 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
583 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
584 len = invertLengthLog(vlen(vec));
586 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
588 return (pitch * 0x1000) + (yaw * 0x80) + len;
591 void compressShortVector_init()
596 for(i = 0; i < 128; ++i)
598 lengthLogTable[i] = l;
602 if(cvar("developer"))
604 print("Verifying vector compression table...\n");
605 for(i = 0x0F00; i < 0xFFFF; ++i)
606 if(i != compressShortVector(decompressShortVector(i)))
608 print("BROKEN vector compression: ", ftos(i));
609 print(" -> ", vtos(decompressShortVector(i)));
610 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
619 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
621 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
622 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
623 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
628 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
629 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
630 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
631 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
632 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
636 void fixedmakevectors(vector a)
638 // a makevectors that actually inverts vectoangles
644 string fixPriorityList(string order, float from, float to, float subtract, float complete)
649 n = tokenize_sane(order);
650 for(i = 0; i < n; ++i)
655 if(w >= from && w <= to)
656 neworder = strcat(neworder, ftos(w), " ");
660 if(w >= from && w <= to)
661 neworder = strcat(neworder, ftos(w), " ");
668 n = tokenize_sane(neworder);
669 for(w = to; w >= from; --w)
671 for(i = 0; i < n; ++i)
672 if(stof(argv(i)) == w)
674 if(i == n) // not found
675 neworder = strcat(neworder, ftos(w), " ");
679 return substring(neworder, 0, strlen(neworder) - 1);
682 string swapInPriorityList(string order, float i, float j)
687 n = tokenize_sane(order);
689 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
692 for(w = 0; w < n; ++w)
695 s = strcat(s, argv(j), " ");
697 s = strcat(s, argv(i), " ");
699 s = strcat(s, argv(w), " ");
701 return substring(s, 0, strlen(s) - 1);
707 float cvar_value_issafe(string s)
709 if(strstrofs(s, "\"", 0) >= 0)
711 if(strstrofs(s, "\\", 0) >= 0)
713 if(strstrofs(s, ";", 0) >= 0)
715 if(strstrofs(s, "$", 0) >= 0)
717 if(strstrofs(s, "\r", 0) >= 0)
719 if(strstrofs(s, "\n", 0) >= 0)
725 void get_mi_min_max(float mode)
730 strunzone(mi_shortname);
731 mi_shortname = mapname;
732 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
733 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
734 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
735 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
736 mi_shortname = strzone(mi_shortname);
748 MapInfo_Get_ByName(mi_shortname, 0, 0);
749 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
751 mi_min = MapInfo_Map_mins;
752 mi_max = MapInfo_Map_maxs;
760 tracebox('1 0 0' * mi_x,
761 '0 1 0' * mi_y + '0 0 1' * mi_z,
762 '0 1 0' * ma_y + '0 0 1' * ma_z,
766 if(!trace_startsolid)
767 mi_min_x = trace_endpos_x;
769 tracebox('0 1 0' * mi_y,
770 '1 0 0' * mi_x + '0 0 1' * mi_z,
771 '1 0 0' * ma_x + '0 0 1' * ma_z,
775 if(!trace_startsolid)
776 mi_min_y = trace_endpos_y;
778 tracebox('0 0 1' * mi_z,
779 '1 0 0' * mi_x + '0 1 0' * mi_y,
780 '1 0 0' * ma_x + '0 1 0' * ma_y,
784 if(!trace_startsolid)
785 mi_min_z = trace_endpos_z;
787 tracebox('1 0 0' * ma_x,
788 '0 1 0' * mi_y + '0 0 1' * mi_z,
789 '0 1 0' * ma_y + '0 0 1' * ma_z,
793 if(!trace_startsolid)
794 mi_max_x = trace_endpos_x;
796 tracebox('0 1 0' * ma_y,
797 '1 0 0' * mi_x + '0 0 1' * mi_z,
798 '1 0 0' * ma_x + '0 0 1' * ma_z,
802 if(!trace_startsolid)
803 mi_max_y = trace_endpos_y;
805 tracebox('0 0 1' * ma_z,
806 '1 0 0' * mi_x + '0 1 0' * mi_y,
807 '1 0 0' * ma_x + '0 1 0' * ma_y,
811 if(!trace_startsolid)
812 mi_max_z = trace_endpos_z;
817 void get_mi_min_max_texcoords(float mode)
821 get_mi_min_max(mode);
826 // extend mi_picmax to get a square aspect ratio
827 // center the map in that area
828 extend = mi_picmax - mi_picmin;
829 if(extend_y > extend_x)
831 mi_picmin_x -= (extend_y - extend_x) * 0.5;
832 mi_picmax_x += (extend_y - extend_x) * 0.5;
836 mi_picmin_y -= (extend_x - extend_y) * 0.5;
837 mi_picmax_y += (extend_x - extend_y) * 0.5;
840 // add another some percent
841 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
845 // calculate the texcoords
846 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
847 // first the two corners of the origin
848 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
849 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
850 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
851 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
852 // then the other corners
853 mi_pictexcoord1_x = mi_pictexcoord0_x;
854 mi_pictexcoord1_y = mi_pictexcoord2_y;
855 mi_pictexcoord3_x = mi_pictexcoord2_x;
856 mi_pictexcoord3_y = mi_pictexcoord0_y;
861 void cvar_settemp(string pKey, string pValue)
863 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
865 void cvar_settemp_restore()
867 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
870 void cvar_settemp(string pKey, string pValue)
872 cvar_set("settemp_list", strcat("1 ", pKey, " ", cvar_string("settemp_var"), " ", cvar_string("settemp_list")));
874 registercvar(cvar_string("settemp_var"), "", 0);
876 registercvar(cvar_string("settemp_var"), "");
878 cvar_set(cvar_string("settemp_var"), cvar_string(pKey));
879 cvar_set("settemp_var", strcat(cvar_string("settemp_var"), "x"));
880 cvar_set(pKey, pValue);
883 void cvar_settemp_restore()
885 // undo what cvar_settemp did
888 n = tokenize_sane(cvar_string("settemp_list"));
889 for(i = 0; i < n - 3; i += 3)
893 cvar_set(s1, s2); // fteqcc sucks
895 cvar_set("settemp_list", "0");
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);
918 float (string s) _tokenize_builtin = #58;
919 string (float argnum) _argv_builtin = #59;
920 float (string s, string sep) _tokenizebyseparator_builtin = #479;
922 float (string s) _tokenize_builtin = #441;
923 string (float argnum) _argv_builtin = #442;
924 float (string s, string sep) _tokenizebyseparator_builtin = #479;
926 float(string s) _tokenize_console = #514;
927 float(float i) _argv_start_index_builtin = #515;
928 float(float i) _argv_end_index_builtin = #516;
930 float MAX_TOKENS = 256;
931 string _argv_sane_buffer[MAX_TOKENS];
932 float _argv_sane_startpos[MAX_TOKENS];
933 float _argv_sane_endpos[MAX_TOKENS];
936 string _argv_sane(float i)
938 // Perl-ish -1 for the last argument
941 return strcat("", _argv_sane_buffer[i]); // force tempstring
944 float _argv_start_index_sane(float i)
946 // Perl-ish -1 for the last argument
949 return _argv_sane_startpos[i];
952 float _argv_end_index_sane(float i)
954 // Perl-ish -1 for the last argument
957 return _argv_sane_endpos[i];
960 //string TOKENIZE_SANE_WHITESPACE_CHARS = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
961 string TOKENIZE_SANE_WHITESPACE_CHARS = "\x20\x0d\x0a\x09";
962 string TOKENIZE_SANE_COMMENT_BREAKERS = "\x0d\x0a";
963 // change this if DP changes its type to "char"!
965 float _tokenize_sane(string s)
967 // This MUST match COM_ParseToken_Console!
968 string com_token, tmp;
973 for(i = 0; i < _argc_sane; ++i)
975 if(_argv_sane_buffer[i])
976 strunzone(_argv_sane_buffer[i]);
977 _argv_sane_buffer[i] = string_null;
978 _argv_sane_startpos[i] = 0;
988 for(; data < end && strstrofs(TOKENIZE_SANE_WHITESPACE_CHARS, substring(s, data, 1), 0) >= 0; ++data)
993 if(substring(s, data, 2) == "//")
997 // Any call to the tokenizer ALREADY assumes it's a single line, so we can safely abort if we see a comment.
1000 while(data < end && strstrofs(TOKENIZE_SANE_COMMENT_BREAKERS, substring(s, data, 1), 0) >= 0)
1002 continue; // go to skipwhite again
1005 // I'd like to simply put a "break" here, but then fteqcc says this function has unreachable code
1008 else if(substring(s, data, 1) == "\"")
1012 _argv_sane_startpos[_argc_sane] = data;
1013 for(++data; data < end && substring(s, data, 1) != "\""; ++data)
1015 // allow escaped " and \ case
1016 tmp = substring(s, data, 2);
1017 if(tmp == "\\\"" || tmp == "\\\\")
1019 com_token = strcat(com_token, substring(s, data, 1));
1021 if(substring(s, data, 1) == "\"")
1023 _argv_sane_endpos[_argc_sane] = data;
1024 _argv_sane_buffer[_argc_sane] = strzone(com_token);
1031 _argv_sane_startpos[_argc_sane] = data;
1032 for(; data < end && strstrofs(TOKENIZE_SANE_WHITESPACE_CHARS, substring(s, data, 1), 0) < 0; ++data)
1033 com_token = strcat(com_token, substring(s, data, 1));
1034 _argv_sane_endpos[_argc_sane] = data;
1035 _argv_sane_buffer[_argc_sane] = strzone(com_token);
1046 // matching the console 1:1
1048 float tokenize_sane_force_native(string s)
1050 argv = _argv_builtin;
1051 argv_start_index = _argv_start_index_builtin;
1052 argv_end_index = _argv_end_index_builtin;
1053 return _tokenize_console(s);
1056 float tokenize_sane_force_emulation(string s)
1059 argv_start_index = _argv_start_index_sane;
1060 argv_end_index = _argv_end_index_sane;
1061 return _tokenize_sane(s);
1064 float tokenize_sane(string s)
1066 if(checkextension("DP_QC_TOKENIZE_CONSOLE"))
1067 return tokenize_sane_force_native(s);
1068 return tokenize_sane_force_emulation(s);
1071 float tokenize_insane(string s)
1073 argv = _argv_builtin;
1074 argv_start_index = _argv_start_index_builtin;
1075 argv_end_index = _argv_end_index_builtin;
1076 return _tokenize_builtin(s);
1079 float tokenizebyseparator(string s, string sep)
1081 argv = _argv_builtin;
1082 argv_start_index = _argv_start_index_builtin;
1083 argv_end_index = _argv_end_index_builtin;
1084 return _tokenizebyseparator_builtin(s, sep);
1087 float power2of(float e)
1091 float log2of(float x)
1093 // NOTE: generated code
1166 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1170 else if(ma == rgb_x)
1173 return (rgb_y - rgb_z) / (ma - mi);
1175 return (rgb_y - rgb_z) / (ma - mi) + 6;
1177 else if(ma == rgb_y)
1178 return (rgb_z - rgb_x) / (ma - mi) + 2;
1179 else // if(ma == rgb_z)
1180 return (rgb_x - rgb_y) / (ma - mi) + 4;
1183 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1187 hue -= 6 * floor(hue / 6);
1189 //else if(ma == rgb_x)
1190 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1194 rgb_y = hue * (ma - mi) + mi;
1197 //else if(ma == rgb_y)
1198 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1201 rgb_x = (2 - hue) * (ma - mi) + mi;
1209 rgb_z = (hue - 2) * (ma - mi) + mi;
1211 //else // if(ma == rgb_z)
1212 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1216 rgb_y = (4 - hue) * (ma - mi) + mi;
1221 rgb_x = (hue - 4) * (ma - mi) + mi;
1225 //else if(ma == rgb_x)
1226 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1227 else // if(hue <= 6)
1231 rgb_z = (6 - hue) * (ma - mi) + mi;
1237 vector rgb_to_hsv(vector rgb)
1242 mi = min3(rgb_x, rgb_y, rgb_z);
1243 ma = max3(rgb_x, rgb_y, rgb_z);
1245 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1256 vector hsv_to_rgb(vector hsv)
1258 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1261 vector rgb_to_hsl(vector rgb)
1266 mi = min3(rgb_x, rgb_y, rgb_z);
1267 ma = max3(rgb_x, rgb_y, rgb_z);
1269 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1271 hsl_z = 0.5 * (mi + ma);
1274 else if(hsl_z <= 0.5)
1275 hsl_y = (ma - mi) / (2*hsl_z);
1276 else // if(hsl_z > 0.5)
1277 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1282 vector hsl_to_rgb(vector hsl)
1284 float mi, ma, maminusmi;
1287 maminusmi = hsl_y * 2 * hsl_z;
1289 maminusmi = hsl_y * (2 - 2 * hsl_z);
1291 // hsl_z = 0.5 * mi + 0.5 * ma
1292 // maminusmi = - mi + ma
1293 mi = hsl_z - 0.5 * maminusmi;
1294 ma = hsl_z + 0.5 * maminusmi;
1296 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1299 string rgb_to_hexcolor(vector rgb)
1304 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1305 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1306 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1310 // requires that m2>m1 in all coordinates, and that m4>m3
1311 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;};
1313 // requires the same, but is a stronger condition
1314 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;};
1317 // angles transforms
1318 // angles in fixedmakevectors/fixedvectoangles space
1319 vector AnglesTransform_Apply(vector transform, vector v)
1321 fixedmakevectors(transform);
1322 return v_forward * v_x
1327 vector AnglesTransform_Multiply(vector t1, vector t2)
1329 vector m_forward, m_up;
1330 fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
1331 m_forward = AnglesTransform_Apply(t1, m_forward); m_up = AnglesTransform_Apply(t1, m_up);
1332 return fixedvectoangles2(m_forward, m_up);
1335 vector AnglesTransform_Invert(vector transform)
1337 vector i_forward, i_up;
1338 fixedmakevectors(transform);
1339 // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
1340 // but these are orthogonal unit vectors!
1341 // so to invert, we can simply fixedvectoangles the TRANSPOSED matrix
1342 // TODO is this always -transform?
1343 i_forward_x = v_forward_x;
1344 i_forward_y = -v_right_x;
1345 i_forward_z = v_up_x;
1346 i_up_x = v_forward_z;
1347 i_up_y = -v_right_z;
1349 return fixedvectoangles2(i_forward, i_up);
1352 vector AnglesTransform_TurnDirection(vector transform)
1354 // turn 180 degrees around v_up
1355 // changes in-direction to out-direction
1356 fixedmakevectors(transform);
1357 return fixedvectoangles2(-1 * v_forward, 1 * v_up);
1360 vector AnglesTransform_Divide(vector to_transform, vector from_transform)
1362 return AnglesTransform_Multiply(to_transform, AnglesTransform_Invert(from_transform));
1366 float textLengthUpToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t w)
1368 float ICanHasKallerz;
1370 // detect color codes support in the width function
1371 ICanHasKallerz = (w("^7") == 0);
1374 // The following function is SLOW.
1375 // For your safety and for the protection of those around you...
1376 // DO NOT CALL THIS AT HOME.
1377 // No really, don't.
1378 if(w(theText) <= maxWidth)
1379 return strlen(theText); // yeah!
1381 // binary search for right place to cut string
1383 float left, right, middle; // this always works
1385 right = strlen(theText); // this always fails
1388 middle = floor((left + right) / 2);
1389 if(w(substring(theText, 0, middle)) <= maxWidth)
1394 while(left < right - 1);
1398 // NOTE: when color codes are involved, this binary search is,
1399 // mathematically, BROKEN. However, it is obviously guaranteed to
1400 // terminate, as the range still halves each time - but nevertheless, it is
1401 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1402 // range, and "right" is outside).
1404 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1405 // and decrease left on the basis of the chars detected of the truncated tag
1406 // Even if the ^xrgb tag is not complete/correct, left is decreased
1407 // (sometimes too much but with a correct result)
1408 // it fixes also ^[0-9]
1409 while(left >= 1 && substring(theText, left-1, 1) == "^")
1412 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1414 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1416 ch = str2chr(theText, left-1);
1417 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1420 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1422 ch = str2chr(theText, left-2);
1423 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1425 ch = str2chr(theText, left-1);
1426 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1435 string getWrappedLine(float w, textLengthUpToWidth_widthFunction_t tw)
1441 s = getWrappedLine_remaining;
1443 cantake = textLengthUpToWidth(s, w, tw);
1444 if(cantake > 0 && cantake < strlen(s))
1447 while(take > 0 && substring(s, take, 1) != " ")
1451 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1452 if(getWrappedLine_remaining == "")
1453 getWrappedLine_remaining = string_null;
1454 return substring(s, 0, cantake);
1458 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1459 if(getWrappedLine_remaining == "")
1460 getWrappedLine_remaining = string_null;
1461 return substring(s, 0, take);
1466 getWrappedLine_remaining = string_null;
1471 string textShortenToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t tw)
1473 if(tw(theText) <= maxWidth)
1476 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("..."), tw)), "...");
1479 float isGametypeInFilter(float gt, float tp, string pattern)
1481 string subpattern, subpattern2;
1482 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1484 subpattern2 = ",teams,";
1486 subpattern2 = ",noteams,";
1488 if(substring(pattern, 0, 1) == "-")
1490 pattern = substring(pattern, 1, strlen(pattern) - 1);
1491 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1493 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1498 if(substring(pattern, 0, 1) == "+")
1499 pattern = substring(pattern, 1, strlen(pattern) - 1);
1500 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1501 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1507 void shuffle(float n, shuffle_swapfunc_t swap)
1510 for(i = 1; i < n; ++i)
1512 // swap i-th item at a random position from 0 to i
1513 // proof for even distribution:
1516 // item n+1 gets at any position with chance 1/(n+1)
1517 // all others will get their 1/n chance reduced by factor n/(n+1)
1518 // to be on place n+1, their chance will be 1/(n+1)
1519 // 1/n * n/(n+1) = 1/(n+1)
1521 j = floor(random() * (i + 1));
1527 string substring_range(string s, float b, float e)
1529 return substring(s, b, e - b);
1532 string swapwords(string str, float i, float j)
1535 string s1, s2, s3, s4, s5;
1536 float si, ei, sj, ej, s0, en;
1537 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1538 si = argv_start_index(i);
1539 sj = argv_start_index(j);
1540 ei = argv_end_index(i);
1541 ej = argv_end_index(j);
1542 s0 = argv_start_index(0);
1543 en = argv_end_index(n-1);
1544 s1 = substring_range(str, s0, si);
1545 s2 = substring_range(str, si, ei);
1546 s3 = substring_range(str, ei, sj);
1547 s4 = substring_range(str, sj, ej);
1548 s5 = substring_range(str, ej, en);
1549 return strcat(s1, s4, s3, s2, s5);
1552 string _shufflewords_str;
1553 void _shufflewords_swapfunc(float i, float j)
1555 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1557 string shufflewords(string str)
1560 _shufflewords_str = str;
1561 n = tokenizebyseparator(str, " ");
1562 shuffle(n, _shufflewords_swapfunc);
1563 str = _shufflewords_str;
1564 _shufflewords_str = string_null;
1568 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1584 // actually, every number solves the equation!
1595 if(a > 0) // put the smaller solution first
1597 v_x = ((-b)-D) / (2*a);
1598 v_y = ((-b)+D) / (2*a);
1602 v_x = (-b+D) / (2*a);
1603 v_y = (-b-D) / (2*a);
1609 // complex solutions!
1623 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1624 float _unacceptable_compiler_bug_1_b() { return 1; }
1625 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1626 float _unacceptable_compiler_bug_1_d() { return 1; }
1628 void check_unacceptable_compiler_bugs()
1630 if(cvar("_allow_unacceptable_compiler_bugs"))
1632 tokenize_sane("foo bar");
1633 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1634 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.");