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.658824 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.313725 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 "rc";
429 else if (g == GAME_NEXBALL) return "nexball";
430 else if (g == GAME_CTS) return "cts";
434 string mmsss(float tenths)
438 tenths = floor(tenths + 0.5);
439 minutes = floor(tenths / 600);
440 tenths -= minutes * 600;
441 s = ftos(1000 + tenths);
442 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
445 string ScoreString(float pFlags, float pValue)
450 pValue = floor(pValue + 0.5); // round
452 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
454 else if(pFlags & SFL_RANK)
456 valstr = ftos(pValue);
458 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
459 valstr = strcat(valstr, "th");
460 else if(substring(valstr, l - 1, 1) == "1")
461 valstr = strcat(valstr, "st");
462 else if(substring(valstr, l - 1, 1) == "2")
463 valstr = strcat(valstr, "nd");
464 else if(substring(valstr, l - 1, 1) == "3")
465 valstr = strcat(valstr, "rd");
467 valstr = strcat(valstr, "th");
469 else if(pFlags & SFL_TIME)
470 valstr = mmsss(pValue);
472 valstr = ftos(pValue);
477 vector cross(vector a, vector b)
480 '1 0 0' * (a_y * b_z - a_z * b_y)
481 + '0 1 0' * (a_z * b_x - a_x * b_z)
482 + '0 0 1' * (a_x * b_y - a_y * b_x);
485 // compressed vector format:
486 // like MD3, just even shorter
487 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
488 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
489 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
490 // length = 2^(length_encoded/8) / 8
491 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
492 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
493 // the special value 0 indicates the zero vector
495 float lengthLogTable[128];
497 float invertLengthLog(float x)
499 float l, r, m, lerr, rerr;
501 if(x >= lengthLogTable[127])
503 if(x <= lengthLogTable[0])
511 m = floor((l + r) / 2);
512 if(lengthLogTable[m] < x)
518 // now: r is >=, l is <
519 lerr = (x - lengthLogTable[l]);
520 rerr = (lengthLogTable[r] - x);
526 vector decompressShortVector(float data)
529 float pitch, yaw, len;
532 pitch = (data & 0xF000) / 0x1000;
533 yaw = (data & 0x0F80) / 0x80;
534 len = (data & 0x007F);
536 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
549 yaw = .19634954084936207740 * yaw;
550 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
551 out_x = cos(yaw) * cos(pitch);
552 out_y = sin(yaw) * cos(pitch);
556 //print("decompressed: ", vtos(out), "\n");
558 return out * lengthLogTable[len];
561 float compressShortVector(vector vec)
564 float pitch, yaw, len;
567 //print("compress: ", vtos(vec), "\n");
568 ang = vectoangles(vec);
572 if(ang_x < -90 && ang_x > +90)
573 error("BOGUS vectoangles");
574 //print("angles: ", vtos(ang), "\n");
576 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
585 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
586 len = invertLengthLog(vlen(vec));
588 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
590 return (pitch * 0x1000) + (yaw * 0x80) + len;
593 void compressShortVector_init()
598 for(i = 0; i < 128; ++i)
600 lengthLogTable[i] = l;
604 if(cvar("developer"))
606 print("Verifying vector compression table...\n");
607 for(i = 0x0F00; i < 0xFFFF; ++i)
608 if(i != compressShortVector(decompressShortVector(i)))
610 print("BROKEN vector compression: ", ftos(i));
611 print(" -> ", vtos(decompressShortVector(i)));
612 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
621 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
623 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
624 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
625 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
626 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
627 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
628 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
629 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
630 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
631 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
632 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
633 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
634 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
638 void fixedmakevectors(vector a)
640 // a makevectors that actually inverts vectoangles
646 string fixPriorityList(string order, float from, float to, float subtract, float complete)
651 n = tokenize_console(order);
652 for(i = 0; i < n; ++i)
657 if(w >= from && w <= to)
658 neworder = strcat(neworder, ftos(w), " ");
662 if(w >= from && w <= to)
663 neworder = strcat(neworder, ftos(w), " ");
670 n = tokenize_console(neworder);
671 for(w = to; w >= from; --w)
673 for(i = 0; i < n; ++i)
674 if(stof(argv(i)) == w)
676 if(i == n) // not found
677 neworder = strcat(neworder, ftos(w), " ");
681 return substring(neworder, 0, strlen(neworder) - 1);
684 string swapInPriorityList(string order, float i, float j)
689 n = tokenize_console(order);
691 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
694 for(w = 0; w < n; ++w)
697 s = strcat(s, argv(j), " ");
699 s = strcat(s, argv(i), " ");
701 s = strcat(s, argv(w), " ");
703 return substring(s, 0, strlen(s) - 1);
709 float cvar_value_issafe(string s)
711 if(strstrofs(s, "\"", 0) >= 0)
713 if(strstrofs(s, "\\", 0) >= 0)
715 if(strstrofs(s, ";", 0) >= 0)
717 if(strstrofs(s, "$", 0) >= 0)
719 if(strstrofs(s, "\r", 0) >= 0)
721 if(strstrofs(s, "\n", 0) >= 0)
727 void get_mi_min_max(float mode)
732 strunzone(mi_shortname);
733 mi_shortname = mapname;
734 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
735 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
736 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
737 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
738 mi_shortname = strzone(mi_shortname);
750 MapInfo_Get_ByName(mi_shortname, 0, 0);
751 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
753 mi_min = MapInfo_Map_mins;
754 mi_max = MapInfo_Map_maxs;
762 tracebox('1 0 0' * mi_x,
763 '0 1 0' * mi_y + '0 0 1' * mi_z,
764 '0 1 0' * ma_y + '0 0 1' * ma_z,
768 if(!trace_startsolid)
769 mi_min_x = trace_endpos_x;
771 tracebox('0 1 0' * mi_y,
772 '1 0 0' * mi_x + '0 0 1' * mi_z,
773 '1 0 0' * ma_x + '0 0 1' * ma_z,
777 if(!trace_startsolid)
778 mi_min_y = trace_endpos_y;
780 tracebox('0 0 1' * mi_z,
781 '1 0 0' * mi_x + '0 1 0' * mi_y,
782 '1 0 0' * ma_x + '0 1 0' * ma_y,
786 if(!trace_startsolid)
787 mi_min_z = trace_endpos_z;
789 tracebox('1 0 0' * ma_x,
790 '0 1 0' * mi_y + '0 0 1' * mi_z,
791 '0 1 0' * ma_y + '0 0 1' * ma_z,
795 if(!trace_startsolid)
796 mi_max_x = trace_endpos_x;
798 tracebox('0 1 0' * ma_y,
799 '1 0 0' * mi_x + '0 0 1' * mi_z,
800 '1 0 0' * ma_x + '0 0 1' * ma_z,
804 if(!trace_startsolid)
805 mi_max_y = trace_endpos_y;
807 tracebox('0 0 1' * ma_z,
808 '1 0 0' * mi_x + '0 1 0' * mi_y,
809 '1 0 0' * ma_x + '0 1 0' * ma_y,
813 if(!trace_startsolid)
814 mi_max_z = trace_endpos_z;
819 void get_mi_min_max_texcoords(float mode)
823 get_mi_min_max(mode);
828 // extend mi_picmax to get a square aspect ratio
829 // center the map in that area
830 extend = mi_picmax - mi_picmin;
831 if(extend_y > extend_x)
833 mi_picmin_x -= (extend_y - extend_x) * 0.5;
834 mi_picmax_x += (extend_y - extend_x) * 0.5;
838 mi_picmin_y -= (extend_x - extend_y) * 0.5;
839 mi_picmax_y += (extend_x - extend_y) * 0.5;
842 // add another some percent
843 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
847 // calculate the texcoords
848 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
849 // first the two corners of the origin
850 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
851 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
852 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
853 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
854 // then the other corners
855 mi_pictexcoord1_x = mi_pictexcoord0_x;
856 mi_pictexcoord1_y = mi_pictexcoord2_y;
857 mi_pictexcoord3_x = mi_pictexcoord2_x;
858 mi_pictexcoord3_y = mi_pictexcoord0_y;
863 void cvar_settemp(string pKey, string pValue)
865 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
867 void cvar_settemp_restore()
869 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
872 void cvar_settemp(string pKey, string pValue)
874 if(cvar_string(pKey) == pValue)
876 cvar_set("settemp_list", strcat("1 ", pKey, " ", cvar_string("settemp_var"), " ", cvar_string("settemp_list")));
878 registercvar(cvar_string("settemp_var"), "", 0);
880 registercvar(cvar_string("settemp_var"), "");
882 cvar_set(cvar_string("settemp_var"), cvar_string(pKey));
883 cvar_set("settemp_var", strcat(cvar_string("settemp_var"), "x"));
884 cvar_set(pKey, pValue);
887 void cvar_settemp_restore()
889 // undo what cvar_settemp did
891 n = tokenize_console(cvar_string("settemp_list"));
892 for(i = 0; i < n - 3; i += 3)
893 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
894 cvar_set("settemp_list", "0");
898 float almost_equals(float a, float b)
901 eps = (max(a, -a) + max(b, -b)) * 0.001;
902 if(a - b < eps && b - a < eps)
907 float almost_in_bounds(float a, float b, float c)
910 eps = (max(a, -a) + max(c, -c)) * 0.001;
911 return b == median(a - eps, b, c + eps);
914 float power2of(float e)
918 float log2of(float x)
920 // NOTE: generated code
993 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1000 return (rgb_y - rgb_z) / (ma - mi);
1002 return (rgb_y - rgb_z) / (ma - mi) + 6;
1004 else if(ma == rgb_y)
1005 return (rgb_z - rgb_x) / (ma - mi) + 2;
1006 else // if(ma == rgb_z)
1007 return (rgb_x - rgb_y) / (ma - mi) + 4;
1010 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1014 hue -= 6 * floor(hue / 6);
1016 //else if(ma == rgb_x)
1017 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1021 rgb_y = hue * (ma - mi) + mi;
1024 //else if(ma == rgb_y)
1025 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1028 rgb_x = (2 - hue) * (ma - mi) + mi;
1036 rgb_z = (hue - 2) * (ma - mi) + mi;
1038 //else // if(ma == rgb_z)
1039 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1043 rgb_y = (4 - hue) * (ma - mi) + mi;
1048 rgb_x = (hue - 4) * (ma - mi) + mi;
1052 //else if(ma == rgb_x)
1053 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1054 else // if(hue <= 6)
1058 rgb_z = (6 - hue) * (ma - mi) + mi;
1064 vector rgb_to_hsv(vector rgb)
1069 mi = min3(rgb_x, rgb_y, rgb_z);
1070 ma = max3(rgb_x, rgb_y, rgb_z);
1072 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1083 vector hsv_to_rgb(vector hsv)
1085 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1088 vector rgb_to_hsl(vector rgb)
1093 mi = min3(rgb_x, rgb_y, rgb_z);
1094 ma = max3(rgb_x, rgb_y, rgb_z);
1096 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1098 hsl_z = 0.5 * (mi + ma);
1101 else if(hsl_z <= 0.5)
1102 hsl_y = (ma - mi) / (2*hsl_z);
1103 else // if(hsl_z > 0.5)
1104 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1109 vector hsl_to_rgb(vector hsl)
1111 float mi, ma, maminusmi;
1114 maminusmi = hsl_y * 2 * hsl_z;
1116 maminusmi = hsl_y * (2 - 2 * hsl_z);
1118 // hsl_z = 0.5 * mi + 0.5 * ma
1119 // maminusmi = - mi + ma
1120 mi = hsl_z - 0.5 * maminusmi;
1121 ma = hsl_z + 0.5 * maminusmi;
1123 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1126 string rgb_to_hexcolor(vector rgb)
1131 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1132 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1133 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1137 // requires that m2>m1 in all coordinates, and that m4>m3
1138 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;};
1140 // requires the same, but is a stronger condition
1141 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;};
1144 // angles transforms
1145 // angles in fixedmakevectors/fixedvectoangles space
1146 vector AnglesTransform_Apply(vector transform, vector v)
1148 fixedmakevectors(transform);
1149 return v_forward * v_x
1154 vector AnglesTransform_Multiply(vector t1, vector t2)
1156 vector m_forward, m_up;
1157 fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
1158 m_forward = AnglesTransform_Apply(t1, m_forward); m_up = AnglesTransform_Apply(t1, m_up);
1159 return fixedvectoangles2(m_forward, m_up);
1162 vector AnglesTransform_Invert(vector transform)
1164 vector i_forward, i_up;
1165 fixedmakevectors(transform);
1166 // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
1167 // but these are orthogonal unit vectors!
1168 // so to invert, we can simply fixedvectoangles the TRANSPOSED matrix
1169 // TODO is this always -transform?
1170 i_forward_x = v_forward_x;
1171 i_forward_y = -v_right_x;
1172 i_forward_z = v_up_x;
1173 i_up_x = v_forward_z;
1174 i_up_y = -v_right_z;
1176 return fixedvectoangles2(i_forward, i_up);
1179 vector AnglesTransform_TurnDirection(vector transform)
1181 // turn 180 degrees around v_up
1182 // changes in-direction to out-direction
1183 fixedmakevectors(transform);
1184 return fixedvectoangles2(-1 * v_forward, 1 * v_up);
1187 vector AnglesTransform_Divide(vector to_transform, vector from_transform)
1189 return AnglesTransform_Multiply(to_transform, AnglesTransform_Invert(from_transform));
1193 float textLengthUpToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t w)
1195 float ICanHasKallerz;
1197 // detect color codes support in the width function
1198 ICanHasKallerz = (w("^7") == 0);
1201 // The following function is SLOW.
1202 // For your safety and for the protection of those around you...
1203 // DO NOT CALL THIS AT HOME.
1204 // No really, don't.
1205 if(w(theText) <= maxWidth)
1206 return strlen(theText); // yeah!
1208 // binary search for right place to cut string
1210 float left, right, middle; // this always works
1212 right = strlen(theText); // this always fails
1215 middle = floor((left + right) / 2);
1216 if(w(substring(theText, 0, middle)) <= maxWidth)
1221 while(left < right - 1);
1225 // NOTE: when color codes are involved, this binary search is,
1226 // mathematically, BROKEN. However, it is obviously guaranteed to
1227 // terminate, as the range still halves each time - but nevertheless, it is
1228 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1229 // range, and "right" is outside).
1231 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1232 // and decrease left on the basis of the chars detected of the truncated tag
1233 // Even if the ^xrgb tag is not complete/correct, left is decreased
1234 // (sometimes too much but with a correct result)
1235 // it fixes also ^[0-9]
1236 while(left >= 1 && substring(theText, left-1, 1) == "^")
1239 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1241 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1243 ch = str2chr(theText, left-1);
1244 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1247 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1249 ch = str2chr(theText, left-2);
1250 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1252 ch = str2chr(theText, left-1);
1253 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1262 string getWrappedLine(float w, textLengthUpToWidth_widthFunction_t tw)
1268 s = getWrappedLine_remaining;
1270 cantake = textLengthUpToWidth(s, w, tw);
1271 if(cantake > 0 && cantake < strlen(s))
1274 while(take > 0 && substring(s, take, 1) != " ")
1278 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1279 if(getWrappedLine_remaining == "")
1280 getWrappedLine_remaining = string_null;
1281 return substring(s, 0, cantake);
1285 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1286 if(getWrappedLine_remaining == "")
1287 getWrappedLine_remaining = string_null;
1288 return substring(s, 0, take);
1293 getWrappedLine_remaining = string_null;
1298 string textShortenToWidth(string theText, float maxWidth, textLengthUpToWidth_widthFunction_t tw)
1300 if(tw(theText) <= maxWidth)
1303 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("..."), tw)), "...");
1306 float isGametypeInFilter(float gt, float tp, string pattern)
1308 string subpattern, subpattern2, subpattern3;
1309 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1311 subpattern2 = ",teams,";
1313 subpattern2 = ",noteams,";
1314 if(gt == GAME_RACE || gt == GAME_CTS)
1315 subpattern3 = ",race,";
1317 subpattern3 = string_null;
1319 if(substring(pattern, 0, 1) == "-")
1321 pattern = substring(pattern, 1, strlen(pattern) - 1);
1322 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1324 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1326 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1331 if(substring(pattern, 0, 1) == "+")
1332 pattern = substring(pattern, 1, strlen(pattern) - 1);
1333 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1334 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1335 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1341 void shuffle(float n, swapfunc_t swap, entity pass)
1344 for(i = 1; i < n; ++i)
1346 // swap i-th item at a random position from 0 to i
1347 // proof for even distribution:
1350 // item n+1 gets at any position with chance 1/(n+1)
1351 // all others will get their 1/n chance reduced by factor n/(n+1)
1352 // to be on place n+1, their chance will be 1/(n+1)
1353 // 1/n * n/(n+1) = 1/(n+1)
1355 j = floor(random() * (i + 1));
1361 string substring_range(string s, float b, float e)
1363 return substring(s, b, e - b);
1366 string swapwords(string str, float i, float j)
1369 string s1, s2, s3, s4, s5;
1370 float si, ei, sj, ej, s0, en;
1371 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1372 si = argv_start_index(i);
1373 sj = argv_start_index(j);
1374 ei = argv_end_index(i);
1375 ej = argv_end_index(j);
1376 s0 = argv_start_index(0);
1377 en = argv_end_index(n-1);
1378 s1 = substring_range(str, s0, si);
1379 s2 = substring_range(str, si, ei);
1380 s3 = substring_range(str, ei, sj);
1381 s4 = substring_range(str, sj, ej);
1382 s5 = substring_range(str, ej, en);
1383 return strcat(s1, s4, s3, s2, s5);
1386 string _shufflewords_str;
1387 void _shufflewords_swapfunc(float i, float j, entity pass)
1389 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1391 string shufflewords(string str)
1394 _shufflewords_str = str;
1395 n = tokenizebyseparator(str, " ");
1396 shuffle(n, _shufflewords_swapfunc, world);
1397 str = _shufflewords_str;
1398 _shufflewords_str = string_null;
1402 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1418 // actually, every number solves the equation!
1429 if(a > 0) // put the smaller solution first
1431 v_x = ((-b)-D) / (2*a);
1432 v_y = ((-b)+D) / (2*a);
1436 v_x = (-b+D) / (2*a);
1437 v_y = (-b-D) / (2*a);
1443 // complex solutions!
1457 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1458 float _unacceptable_compiler_bug_1_b() { return 1; }
1459 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1460 float _unacceptable_compiler_bug_1_d() { return 1; }
1462 void check_unacceptable_compiler_bugs()
1464 if(cvar("_allow_unacceptable_compiler_bugs"))
1466 tokenize_console("foo bar");
1467 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1468 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.");
1471 float compressShotOrigin(vector v)
1475 y = rint(v_y * 4) + 128;
1476 z = rint(v_z * 4) + 128;
1477 if(x > 255 || x < 0)
1478 error("shot origin x out of bounds");
1479 if(y > 255 || y < 0)
1480 error("shot origin y out of bounds");
1481 if(z > 255 || z < 0)
1482 error("shot origin z out of bounds");
1483 return x * 0x10000 + y * 0x100 + z;
1485 vector decompressShotOrigin(float f)
1488 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1489 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1490 v_z = ((f & 0xFF) - 128) / 4;
1494 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1496 float start, end, root, child;
1499 start = floor((n - 2) / 2);
1502 // siftdown(start, count-1);
1504 while(root * 2 + 1 <= n-1)
1506 child = root * 2 + 1;
1508 if(cmp(child, child+1, pass) < 0)
1510 if(cmp(root, child, pass) < 0)
1512 swap(root, child, pass);
1528 // siftdown(0, end);
1530 while(root * 2 + 1 <= end)
1532 child = root * 2 + 1;
1533 if(child < end && cmp(child, child+1, pass) < 0)
1535 if(cmp(root, child, pass) < 0)
1537 swap(root, child, pass);
1547 void RandomSelection_Init()
1549 RandomSelection_totalweight = 0;
1550 RandomSelection_chosen_ent = world;
1551 RandomSelection_chosen_float = 0;
1552 RandomSelection_chosen_string = string_null;
1553 RandomSelection_best_priority = -1;
1555 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1557 if(priority > RandomSelection_best_priority)
1559 RandomSelection_best_priority = priority;
1560 RandomSelection_chosen_ent = e;
1561 RandomSelection_chosen_float = f;
1562 RandomSelection_chosen_string = s;
1563 RandomSelection_totalweight = weight;
1565 else if(priority == RandomSelection_best_priority)
1567 RandomSelection_totalweight += weight;
1568 if(random() * RandomSelection_totalweight <= weight)
1570 RandomSelection_chosen_ent = e;
1571 RandomSelection_chosen_float = f;
1572 RandomSelection_chosen_string = s;
1577 vector healtharmor_maxdamage(float h, float a, float armorblock)
1579 // NOTE: we'll always choose the SMALLER value...
1580 float healthdamage, armordamage, armorideal;
1582 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1583 armordamage = a + (h - 1); // damage we can take if we could use more armor
1584 armorideal = healthdamage * armorblock;
1586 if(armordamage < healthdamage)
1599 vector healtharmor_applydamage(float a, float armorblock, float damage)
1602 v_y = bound(0, damage * armorblock, a); // save
1603 v_x = bound(0, damage - v_y, damage); // take
1608 string getcurrentmod()
1612 m = cvar_string("fs_gamedir");
1613 n = tokenize_console(m);