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 mmssss(float hundredths)
449 hundredths = floor(hundredths + 0.5);
450 minutes = floor(hundredths / 6000);
451 hundredths -= minutes * 6000;
452 s = ftos(10000 + hundredths);
453 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
456 string ScoreString(float pFlags, float pValue)
461 pValue = floor(pValue + 0.5); // round
463 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
465 else if(pFlags & SFL_RANK)
467 valstr = ftos(pValue);
469 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
470 valstr = strcat(valstr, "th");
471 else if(substring(valstr, l - 1, 1) == "1")
472 valstr = strcat(valstr, "st");
473 else if(substring(valstr, l - 1, 1) == "2")
474 valstr = strcat(valstr, "nd");
475 else if(substring(valstr, l - 1, 1) == "3")
476 valstr = strcat(valstr, "rd");
478 valstr = strcat(valstr, "th");
480 else if(pFlags & SFL_TIME)
481 valstr = TIME_ENCODED_TOSTRING(pValue);
483 valstr = ftos(pValue);
488 vector cross(vector a, vector b)
491 '1 0 0' * (a_y * b_z - a_z * b_y)
492 + '0 1 0' * (a_z * b_x - a_x * b_z)
493 + '0 0 1' * (a_x * b_y - a_y * b_x);
496 // compressed vector format:
497 // like MD3, just even shorter
498 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
499 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
500 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
501 // length = 2^(length_encoded/8) / 8
502 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
503 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
504 // the special value 0 indicates the zero vector
506 float lengthLogTable[128];
508 float invertLengthLog(float x)
510 float l, r, m, lerr, rerr;
512 if(x >= lengthLogTable[127])
514 if(x <= lengthLogTable[0])
522 m = floor((l + r) / 2);
523 if(lengthLogTable[m] < x)
529 // now: r is >=, l is <
530 lerr = (x - lengthLogTable[l]);
531 rerr = (lengthLogTable[r] - x);
537 vector decompressShortVector(float data)
540 float pitch, yaw, len;
543 pitch = (data & 0xF000) / 0x1000;
544 yaw = (data & 0x0F80) / 0x80;
545 len = (data & 0x007F);
547 //print("\ndecompress: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
560 yaw = .19634954084936207740 * yaw;
561 pitch = .19634954084936207740 * pitch - 1.57079632679489661922;
562 out_x = cos(yaw) * cos(pitch);
563 out_y = sin(yaw) * cos(pitch);
567 //print("decompressed: ", vtos(out), "\n");
569 return out * lengthLogTable[len];
572 float compressShortVector(vector vec)
575 float pitch, yaw, len;
578 //print("compress: ", vtos(vec), "\n");
579 ang = vectoangles(vec);
583 if(ang_x < -90 && ang_x > +90)
584 error("BOGUS vectoangles");
585 //print("angles: ", vtos(ang), "\n");
587 pitch = floor(0.5 + (ang_x + 90) * 16 / 180) & 15; // -90..90 to 0..14
596 yaw = floor(0.5 + ang_y * 32 / 360) & 31; // 0..360 to 0..32
597 len = invertLengthLog(vlen(vec));
599 //print("compressed: pitch ", ftos(pitch)); print("yaw ", ftos(yaw)); print("len ", ftos(len), "\n");
601 return (pitch * 0x1000) + (yaw * 0x80) + len;
604 void compressShortVector_init()
609 for(i = 0; i < 128; ++i)
611 lengthLogTable[i] = l;
615 if(cvar("developer"))
617 print("Verifying vector compression table...\n");
618 for(i = 0x0F00; i < 0xFFFF; ++i)
619 if(i != compressShortVector(decompressShortVector(i)))
621 print("BROKEN vector compression: ", ftos(i));
622 print(" -> ", vtos(decompressShortVector(i)));
623 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
632 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
634 traceline(v0, v0 + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
635 traceline(v0, v0 + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
636 traceline(v0, v0 + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
637 traceline(v0 + dvx, v0 + dvx + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
638 traceline(v0 + dvx, v0 + dvx + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
639 traceline(v0 + dvy, v0 + dvy + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
640 traceline(v0 + dvy, v0 + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
641 traceline(v0 + dvz, v0 + dvz + dvx, TRUE, forent); if(trace_fraction < 1) return 0;
642 traceline(v0 + dvz, v0 + dvz + dvy, TRUE, forent); if(trace_fraction < 1) return 0;
643 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
644 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
645 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, TRUE, forent); if(trace_fraction < 1) return 0;
649 void fixedmakevectors(vector a)
651 // a makevectors that actually inverts vectoangles
657 string fixPriorityList(string order, float from, float to, float subtract, float complete)
662 n = tokenize_console(order);
663 for(i = 0; i < n; ++i)
668 if(w >= from && w <= to)
669 neworder = strcat(neworder, ftos(w), " ");
673 if(w >= from && w <= to)
674 neworder = strcat(neworder, ftos(w), " ");
681 n = tokenize_console(neworder);
682 for(w = to; w >= from; --w)
684 for(i = 0; i < n; ++i)
685 if(stof(argv(i)) == w)
687 if(i == n) // not found
688 neworder = strcat(neworder, ftos(w), " ");
692 return substring(neworder, 0, strlen(neworder) - 1);
695 string swapInPriorityList(string order, float i, float j)
700 n = tokenize_console(order);
702 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
705 for(w = 0; w < n; ++w)
708 s = strcat(s, argv(j), " ");
710 s = strcat(s, argv(i), " ");
712 s = strcat(s, argv(w), " ");
714 return substring(s, 0, strlen(s) - 1);
720 float cvar_value_issafe(string s)
722 if(strstrofs(s, "\"", 0) >= 0)
724 if(strstrofs(s, "\\", 0) >= 0)
726 if(strstrofs(s, ";", 0) >= 0)
728 if(strstrofs(s, "$", 0) >= 0)
730 if(strstrofs(s, "\r", 0) >= 0)
732 if(strstrofs(s, "\n", 0) >= 0)
738 void get_mi_min_max(float mode)
743 strunzone(mi_shortname);
744 mi_shortname = mapname;
745 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
746 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
747 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
748 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
749 mi_shortname = strzone(mi_shortname);
761 MapInfo_Get_ByName(mi_shortname, 0, 0);
762 if(MapInfo_Map_mins_x < MapInfo_Map_maxs_x)
764 mi_min = MapInfo_Map_mins;
765 mi_max = MapInfo_Map_maxs;
773 tracebox('1 0 0' * mi_x,
774 '0 1 0' * mi_y + '0 0 1' * mi_z,
775 '0 1 0' * ma_y + '0 0 1' * ma_z,
779 if(!trace_startsolid)
780 mi_min_x = trace_endpos_x;
782 tracebox('0 1 0' * mi_y,
783 '1 0 0' * mi_x + '0 0 1' * mi_z,
784 '1 0 0' * ma_x + '0 0 1' * ma_z,
788 if(!trace_startsolid)
789 mi_min_y = trace_endpos_y;
791 tracebox('0 0 1' * mi_z,
792 '1 0 0' * mi_x + '0 1 0' * mi_y,
793 '1 0 0' * ma_x + '0 1 0' * ma_y,
797 if(!trace_startsolid)
798 mi_min_z = trace_endpos_z;
800 tracebox('1 0 0' * ma_x,
801 '0 1 0' * mi_y + '0 0 1' * mi_z,
802 '0 1 0' * ma_y + '0 0 1' * ma_z,
806 if(!trace_startsolid)
807 mi_max_x = trace_endpos_x;
809 tracebox('0 1 0' * ma_y,
810 '1 0 0' * mi_x + '0 0 1' * mi_z,
811 '1 0 0' * ma_x + '0 0 1' * ma_z,
815 if(!trace_startsolid)
816 mi_max_y = trace_endpos_y;
818 tracebox('0 0 1' * ma_z,
819 '1 0 0' * mi_x + '0 1 0' * mi_y,
820 '1 0 0' * ma_x + '0 1 0' * ma_y,
824 if(!trace_startsolid)
825 mi_max_z = trace_endpos_z;
830 void get_mi_min_max_texcoords(float mode)
834 get_mi_min_max(mode);
839 // extend mi_picmax to get a square aspect ratio
840 // center the map in that area
841 extend = mi_picmax - mi_picmin;
842 if(extend_y > extend_x)
844 mi_picmin_x -= (extend_y - extend_x) * 0.5;
845 mi_picmax_x += (extend_y - extend_x) * 0.5;
849 mi_picmin_y -= (extend_x - extend_y) * 0.5;
850 mi_picmax_y += (extend_x - extend_y) * 0.5;
853 // add another some percent
854 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
858 // calculate the texcoords
859 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
860 // first the two corners of the origin
861 mi_pictexcoord0_x = (mi_min_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
862 mi_pictexcoord0_y = (mi_min_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
863 mi_pictexcoord2_x = (mi_max_x - mi_picmin_x) / (mi_picmax_x - mi_picmin_x);
864 mi_pictexcoord2_y = (mi_max_y - mi_picmin_y) / (mi_picmax_y - mi_picmin_y);
865 // then the other corners
866 mi_pictexcoord1_x = mi_pictexcoord0_x;
867 mi_pictexcoord1_y = mi_pictexcoord2_y;
868 mi_pictexcoord3_x = mi_pictexcoord2_x;
869 mi_pictexcoord3_y = mi_pictexcoord0_y;
874 void cvar_settemp(string pKey, string pValue)
876 error("cvar_settemp called from CSQC - use cvar_clientsettemp instead!");
878 void cvar_settemp_restore()
880 error("cvar_settemp_restore called from CSQC - use cvar_clientsettemp instead!");
883 void cvar_settemp(string pKey, string pValue)
885 if(cvar_string(pKey) == pValue)
887 cvar_set("settemp_list", strcat("1 ", pKey, " ", cvar_string("settemp_var"), " ", cvar_string("settemp_list")));
889 registercvar(cvar_string("settemp_var"), "", 0);
891 registercvar(cvar_string("settemp_var"), "");
893 cvar_set(cvar_string("settemp_var"), cvar_string(pKey));
894 cvar_set("settemp_var", strcat(cvar_string("settemp_var"), "x"));
895 cvar_set(pKey, pValue);
898 void cvar_settemp_restore()
900 // undo what cvar_settemp did
902 n = tokenize_console(cvar_string("settemp_list"));
903 for(i = 0; i < n - 3; i += 3)
904 cvar_set(argv(i + 1), cvar_string(argv(i + 2)));
905 cvar_set("settemp_list", "0");
909 float almost_equals(float a, float b)
912 eps = (max(a, -a) + max(b, -b)) * 0.001;
913 if(a - b < eps && b - a < eps)
918 float almost_in_bounds(float a, float b, float c)
921 eps = (max(a, -a) + max(c, -c)) * 0.001;
922 return b == median(a - eps, b, c + eps);
925 float power2of(float e)
929 float log2of(float x)
931 // NOTE: generated code
1004 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1008 else if(ma == rgb_x)
1011 return (rgb_y - rgb_z) / (ma - mi);
1013 return (rgb_y - rgb_z) / (ma - mi) + 6;
1015 else if(ma == rgb_y)
1016 return (rgb_z - rgb_x) / (ma - mi) + 2;
1017 else // if(ma == rgb_z)
1018 return (rgb_x - rgb_y) / (ma - mi) + 4;
1021 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1025 hue -= 6 * floor(hue / 6);
1027 //else if(ma == rgb_x)
1028 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1032 rgb_y = hue * (ma - mi) + mi;
1035 //else if(ma == rgb_y)
1036 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1039 rgb_x = (2 - hue) * (ma - mi) + mi;
1047 rgb_z = (hue - 2) * (ma - mi) + mi;
1049 //else // if(ma == rgb_z)
1050 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1054 rgb_y = (4 - hue) * (ma - mi) + mi;
1059 rgb_x = (hue - 4) * (ma - mi) + mi;
1063 //else if(ma == rgb_x)
1064 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1065 else // if(hue <= 6)
1069 rgb_z = (6 - hue) * (ma - mi) + mi;
1075 vector rgb_to_hsv(vector rgb)
1080 mi = min3(rgb_x, rgb_y, rgb_z);
1081 ma = max3(rgb_x, rgb_y, rgb_z);
1083 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1094 vector hsv_to_rgb(vector hsv)
1096 return hue_mi_ma_to_rgb(hsv_x, hsv_z * (1 - hsv_y), hsv_z);
1099 vector rgb_to_hsl(vector rgb)
1104 mi = min3(rgb_x, rgb_y, rgb_z);
1105 ma = max3(rgb_x, rgb_y, rgb_z);
1107 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1109 hsl_z = 0.5 * (mi + ma);
1112 else if(hsl_z <= 0.5)
1113 hsl_y = (ma - mi) / (2*hsl_z);
1114 else // if(hsl_z > 0.5)
1115 hsl_y = (ma - mi) / (2 - 2*hsl_z);
1120 vector hsl_to_rgb(vector hsl)
1122 float mi, ma, maminusmi;
1125 maminusmi = hsl_y * 2 * hsl_z;
1127 maminusmi = hsl_y * (2 - 2 * hsl_z);
1129 // hsl_z = 0.5 * mi + 0.5 * ma
1130 // maminusmi = - mi + ma
1131 mi = hsl_z - 0.5 * maminusmi;
1132 ma = hsl_z + 0.5 * maminusmi;
1134 return hue_mi_ma_to_rgb(hsl_x, mi, ma);
1137 string rgb_to_hexcolor(vector rgb)
1142 DEC_TO_HEXDIGIT(floor(rgb_x * 15 + 0.5)),
1143 DEC_TO_HEXDIGIT(floor(rgb_y * 15 + 0.5)),
1144 DEC_TO_HEXDIGIT(floor(rgb_z * 15 + 0.5))
1148 // requires that m2>m1 in all coordinates, and that m4>m3
1149 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;};
1151 // requires the same, but is a stronger condition
1152 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;};
1155 // angles transforms
1156 // angles in fixedmakevectors/fixedvectoangles space
1157 vector AnglesTransform_Apply(vector transform, vector v)
1159 fixedmakevectors(transform);
1160 return v_forward * v_x
1165 vector AnglesTransform_Multiply(vector t1, vector t2)
1167 vector m_forward, m_up;
1168 fixedmakevectors(t2); m_forward = v_forward; m_up = v_up;
1169 m_forward = AnglesTransform_Apply(t1, m_forward); m_up = AnglesTransform_Apply(t1, m_up);
1170 return fixedvectoangles2(m_forward, m_up);
1173 vector AnglesTransform_Invert(vector transform)
1175 vector i_forward, i_up;
1176 fixedmakevectors(transform);
1177 // we want angles that turn v_forward into '1 0 0', v_right into '0 1 0' and v_up into '0 0 1'
1178 // but these are orthogonal unit vectors!
1179 // so to invert, we can simply fixedvectoangles the TRANSPOSED matrix
1180 // TODO is this always -transform?
1181 i_forward_x = v_forward_x;
1182 i_forward_y = -v_right_x;
1183 i_forward_z = v_up_x;
1184 i_up_x = v_forward_z;
1185 i_up_y = -v_right_z;
1187 return fixedvectoangles2(i_forward, i_up);
1190 vector AnglesTransform_TurnDirection(vector transform)
1192 // turn 180 degrees around v_up
1193 // changes in-direction to out-direction
1194 fixedmakevectors(transform);
1195 return fixedvectoangles2(-1 * v_forward, 1 * v_up);
1198 vector AnglesTransform_Divide(vector to_transform, vector from_transform)
1200 return AnglesTransform_Multiply(to_transform, AnglesTransform_Invert(from_transform));
1204 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1206 float ICanHasKallerz;
1208 // detect color codes support in the width function
1209 ICanHasKallerz = (w("^7", theSize) == 0);
1212 // The following function is SLOW.
1213 // For your safety and for the protection of those around you...
1214 // DO NOT CALL THIS AT HOME.
1215 // No really, don't.
1216 if(w(theText, theSize) <= maxWidth)
1217 return strlen(theText); // yeah!
1219 // binary search for right place to cut string
1221 float left, right, middle; // this always works
1223 right = strlen(theText); // this always fails
1226 middle = floor((left + right) / 2);
1227 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1232 while(left < right - 1);
1236 // NOTE: when color codes are involved, this binary search is,
1237 // mathematically, BROKEN. However, it is obviously guaranteed to
1238 // terminate, as the range still halves each time - but nevertheless, it is
1239 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1240 // range, and "right" is outside).
1242 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1243 // and decrease left on the basis of the chars detected of the truncated tag
1244 // Even if the ^xrgb tag is not complete/correct, left is decreased
1245 // (sometimes too much but with a correct result)
1246 // it fixes also ^[0-9]
1247 while(left >= 1 && substring(theText, left-1, 1) == "^")
1250 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1252 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1254 ch = str2chr(theText, left-1);
1255 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1258 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1260 ch = str2chr(theText, left-2);
1261 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1263 ch = str2chr(theText, left-1);
1264 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1273 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1275 float ICanHasKallerz;
1277 // detect color codes support in the width function
1278 ICanHasKallerz = (w("^7") == 0);
1281 // The following function is SLOW.
1282 // For your safety and for the protection of those around you...
1283 // DO NOT CALL THIS AT HOME.
1284 // No really, don't.
1285 if(w(theText) <= maxWidth)
1286 return strlen(theText); // yeah!
1288 // binary search for right place to cut string
1290 float left, right, middle; // this always works
1292 right = strlen(theText); // this always fails
1295 middle = floor((left + right) / 2);
1296 if(w(substring(theText, 0, middle)) <= maxWidth)
1301 while(left < right - 1);
1305 // NOTE: when color codes are involved, this binary search is,
1306 // mathematically, BROKEN. However, it is obviously guaranteed to
1307 // terminate, as the range still halves each time - but nevertheless, it is
1308 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1309 // range, and "right" is outside).
1311 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1312 // and decrease left on the basis of the chars detected of the truncated tag
1313 // Even if the ^xrgb tag is not complete/correct, left is decreased
1314 // (sometimes too much but with a correct result)
1315 // it fixes also ^[0-9]
1316 while(left >= 1 && substring(theText, left-1, 1) == "^")
1319 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1321 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1323 ch = str2chr(theText, left-1);
1324 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1327 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1329 ch = str2chr(theText, left-2);
1330 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1332 ch = str2chr(theText, left-1);
1333 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1342 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1348 s = getWrappedLine_remaining;
1350 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1351 if(cantake > 0 && cantake < strlen(s))
1354 while(take > 0 && substring(s, take, 1) != " ")
1358 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1359 if(getWrappedLine_remaining == "")
1360 getWrappedLine_remaining = string_null;
1361 return substring(s, 0, cantake);
1365 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1366 if(getWrappedLine_remaining == "")
1367 getWrappedLine_remaining = string_null;
1368 return substring(s, 0, take);
1373 getWrappedLine_remaining = string_null;
1378 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1384 s = getWrappedLine_remaining;
1386 cantake = textLengthUpToLength(s, w, tw);
1387 if(cantake > 0 && cantake < strlen(s))
1390 while(take > 0 && substring(s, take, 1) != " ")
1394 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1395 if(getWrappedLine_remaining == "")
1396 getWrappedLine_remaining = string_null;
1397 return substring(s, 0, cantake);
1401 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1402 if(getWrappedLine_remaining == "")
1403 getWrappedLine_remaining = string_null;
1404 return substring(s, 0, take);
1409 getWrappedLine_remaining = string_null;
1414 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1416 if(tw(theText, theFontSize) <= maxWidth)
1419 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1422 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1424 if(tw(theText) <= maxWidth)
1427 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1430 float isGametypeInFilter(float gt, float tp, string pattern)
1432 string subpattern, subpattern2, subpattern3;
1433 subpattern = strcat(",", GametypeNameFromType(gt), ",");
1435 subpattern2 = ",teams,";
1437 subpattern2 = ",noteams,";
1438 if(gt == GAME_RACE || gt == GAME_CTS)
1439 subpattern3 = ",race,";
1441 subpattern3 = string_null;
1443 if(substring(pattern, 0, 1) == "-")
1445 pattern = substring(pattern, 1, strlen(pattern) - 1);
1446 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1448 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1450 if(subpattern3 && strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1455 if(substring(pattern, 0, 1) == "+")
1456 pattern = substring(pattern, 1, strlen(pattern) - 1);
1457 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1458 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1459 if((!subpattern3) || strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1465 void shuffle(float n, swapfunc_t swap, entity pass)
1468 for(i = 1; i < n; ++i)
1470 // swap i-th item at a random position from 0 to i
1471 // proof for even distribution:
1474 // item n+1 gets at any position with chance 1/(n+1)
1475 // all others will get their 1/n chance reduced by factor n/(n+1)
1476 // to be on place n+1, their chance will be 1/(n+1)
1477 // 1/n * n/(n+1) = 1/(n+1)
1479 j = floor(random() * (i + 1));
1485 string substring_range(string s, float b, float e)
1487 return substring(s, b, e - b);
1490 string swapwords(string str, float i, float j)
1493 string s1, s2, s3, s4, s5;
1494 float si, ei, sj, ej, s0, en;
1495 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1496 si = argv_start_index(i);
1497 sj = argv_start_index(j);
1498 ei = argv_end_index(i);
1499 ej = argv_end_index(j);
1500 s0 = argv_start_index(0);
1501 en = argv_end_index(n-1);
1502 s1 = substring_range(str, s0, si);
1503 s2 = substring_range(str, si, ei);
1504 s3 = substring_range(str, ei, sj);
1505 s4 = substring_range(str, sj, ej);
1506 s5 = substring_range(str, ej, en);
1507 return strcat(s1, s4, s3, s2, s5);
1510 string _shufflewords_str;
1511 void _shufflewords_swapfunc(float i, float j, entity pass)
1513 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1515 string shufflewords(string str)
1518 _shufflewords_str = str;
1519 n = tokenizebyseparator(str, " ");
1520 shuffle(n, _shufflewords_swapfunc, world);
1521 str = _shufflewords_str;
1522 _shufflewords_str = string_null;
1526 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1542 // actually, every number solves the equation!
1553 if(a > 0) // put the smaller solution first
1555 v_x = ((-b)-D) / (2*a);
1556 v_y = ((-b)+D) / (2*a);
1560 v_x = (-b+D) / (2*a);
1561 v_y = (-b-D) / (2*a);
1567 // complex solutions!
1581 float _unacceptable_compiler_bug_1_a(float b, float c) { return b == c; }
1582 float _unacceptable_compiler_bug_1_b() { return 1; }
1583 float _unacceptable_compiler_bug_1_c(float d) { return 2 * d; }
1584 float _unacceptable_compiler_bug_1_d() { return 1; }
1586 void check_unacceptable_compiler_bugs()
1588 if(cvar("_allow_unacceptable_compiler_bugs"))
1590 tokenize_console("foo bar");
1591 if(strcat(argv(0), substring("foo bar", 4, 7 - argv_start_index(1))) == "barbar")
1592 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.");
1595 float compressShotOrigin(vector v)
1599 y = rint(v_y * 4) + 128;
1600 z = rint(v_z * 4) + 128;
1601 if(x > 255 || x < 0)
1602 error("shot origin x out of bounds");
1603 if(y > 255 || y < 0)
1604 error("shot origin y out of bounds");
1605 if(z > 255 || z < 0)
1606 error("shot origin z out of bounds");
1607 return x * 0x10000 + y * 0x100 + z;
1609 vector decompressShotOrigin(float f)
1612 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1613 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1614 v_z = ((f & 0xFF) - 128) / 4;
1618 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1620 float start, end, root, child;
1623 start = floor((n - 2) / 2);
1626 // siftdown(start, count-1);
1628 while(root * 2 + 1 <= n-1)
1630 child = root * 2 + 1;
1632 if(cmp(child, child+1, pass) < 0)
1634 if(cmp(root, child, pass) < 0)
1636 swap(root, child, pass);
1652 // siftdown(0, end);
1654 while(root * 2 + 1 <= end)
1656 child = root * 2 + 1;
1657 if(child < end && cmp(child, child+1, pass) < 0)
1659 if(cmp(root, child, pass) < 0)
1661 swap(root, child, pass);
1671 void RandomSelection_Init()
1673 RandomSelection_totalweight = 0;
1674 RandomSelection_chosen_ent = world;
1675 RandomSelection_chosen_float = 0;
1676 RandomSelection_chosen_string = string_null;
1677 RandomSelection_best_priority = -1;
1679 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1681 if(priority > RandomSelection_best_priority)
1683 RandomSelection_best_priority = priority;
1684 RandomSelection_chosen_ent = e;
1685 RandomSelection_chosen_float = f;
1686 RandomSelection_chosen_string = s;
1687 RandomSelection_totalweight = weight;
1689 else if(priority == RandomSelection_best_priority)
1691 RandomSelection_totalweight += weight;
1692 if(random() * RandomSelection_totalweight <= weight)
1694 RandomSelection_chosen_ent = e;
1695 RandomSelection_chosen_float = f;
1696 RandomSelection_chosen_string = s;
1701 vector healtharmor_maxdamage(float h, float a, float armorblock)
1703 // NOTE: we'll always choose the SMALLER value...
1704 float healthdamage, armordamage, armorideal;
1706 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1707 armordamage = a + (h - 1); // damage we can take if we could use more armor
1708 armorideal = healthdamage * armorblock;
1710 if(armordamage < healthdamage)
1723 vector healtharmor_applydamage(float a, float armorblock, float damage)
1726 v_y = bound(0, damage * armorblock, a); // save
1727 v_x = bound(0, damage - v_y, damage); // take
1732 string getcurrentmod()
1736 m = cvar_string("fs_gamedir");
1737 n = tokenize_console(m);