4 #include "../dpdefs/csprogsdefs.qh"
5 #include "../client/defs.qh"
6 #include "constants.qh"
7 #include "../warpzonelib/mathlib.qh"
9 #include "notifications.qh"
10 #include "deathtypes.qh"
13 #include "../dpdefs/progsdefs.qh"
14 #include "../dpdefs/dpextensions.qh"
15 #include "../warpzonelib/mathlib.qh"
16 #include "constants.qh"
17 #include "../server/autocvars.qh"
18 #include "../server/defs.qh"
19 #include "notifications.qh"
20 #include "deathtypes.qh"
24 string wordwrap_buffer;
26 void wordwrap_buffer_put(string s)
28 wordwrap_buffer = strcat(wordwrap_buffer, s);
31 string wordwrap(string s, float l)
35 wordwrap_cb(s, l, wordwrap_buffer_put);
43 void wordwrap_buffer_sprint(string s)
45 wordwrap_buffer = strcat(wordwrap_buffer, s);
48 sprint(self, wordwrap_buffer);
53 void wordwrap_sprint(string s, float l)
56 wordwrap_cb(s, l, wordwrap_buffer_sprint);
57 if(wordwrap_buffer != "")
58 sprint(self, strcat(wordwrap_buffer, "\n"));
66 string draw_UseSkinFor(string pic)
68 if(substring(pic, 0, 1) == "/")
69 return substring(pic, 1, strlen(pic)-1);
71 return strcat(draw_currentSkin, "/", pic);
75 string unescape(string in)
80 // but it doesn't seem to be necessary in my tests at least
85 for(i = 0; i < len; ++i)
87 s = substring(in, i, 1);
90 s = substring(in, i+1, 1);
92 str = strcat(str, "\n");
94 str = strcat(str, "\\");
96 str = strcat(str, substring(in, i, 2));
106 void wordwrap_cb(string s, float l, void(string) callback)
109 float lleft, i, j, wlen;
113 for (i = 0;i < strlen(s);++i)
115 if (substring(s, i, 2) == "\\n")
121 else if (substring(s, i, 1) == "\n")
126 else if (substring(s, i, 1) == " ")
136 for (j = i+1;j < strlen(s);++j)
137 // ^^ this skips over the first character of a word, which
138 // is ALWAYS part of the word
139 // this is safe since if i+1 == strlen(s), i will become
140 // strlen(s)-1 at the end of this block and the function
141 // will terminate. A space can't be the first character we
142 // read here, and neither can a \n be the start, since these
143 // two cases have been handled above.
145 c = substring(s, j, 1);
152 // we need to keep this tempstring alive even if substring is
153 // called repeatedly, so call strcat even though we're not
163 callback(substring(s, i, wlen));
164 lleft = lleft - wlen;
171 float dist_point_line(vector p, vector l0, vector ldir)
173 ldir = normalize(ldir);
175 // remove the component in line direction
176 p = p - (p * ldir) * ldir;
178 // vlen of the remaining vector
182 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
211 float median(float a, float b, float c)
214 return bound(a, b, c);
215 return bound(c, b, a);
218 // converts a number to a string with the indicated number of decimals
219 // works for up to 10 decimals!
220 string ftos_decimals(float number, float decimals)
222 // inhibit stupid negative zero
225 // we have sprintf...
226 return sprintf("%.*f", decimals, number);
229 vector colormapPaletteColor(float c, float isPants)
233 case 0: return '1.000000 1.000000 1.000000';
234 case 1: return '1.000000 0.333333 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.666667 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.000000 0.000000 1.000000';
245 case 12: return '1.000000 1.000000 0.000000';
246 case 13: return '0.000000 0.333333 1.000000';
247 case 14: return '1.000000 0.666667 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 bool fexists(string f)
276 int fh = fopen(f, FILE_READ);
283 // Databases (hash tables)
284 const float DB_BUCKETS = 8192;
285 void db_save(float db, string pFilename)
288 fh = fopen(pFilename, FILE_WRITE);
291 print(strcat("^1Can't write DB to ", pFilename));
295 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
296 for(i = 0; i < n; ++i)
297 fputs(fh, strcat(bufstr_get(db, i), "\n"));
306 int db_load(string pFilename)
308 float db, fh, i, j, n;
313 fh = fopen(pFilename, FILE_READ);
317 if(stof(l) == DB_BUCKETS)
320 while((l = fgets(fh)))
323 bufstr_set(db, i, l);
329 // different count of buckets, or a dump?
330 // need to reorganize the database then (SLOW)
332 // note: we also parse the first line (l) in case the DB file is
333 // missing the bucket count
336 n = tokenizebyseparator(l, "\\");
337 for(j = 2; j < n; j += 2)
338 db_put(db, argv(j-1), uri_unescape(argv(j)));
340 while((l = fgets(fh)));
346 void db_dump(float db, string pFilename)
348 float fh, i, j, n, m;
349 fh = fopen(pFilename, FILE_WRITE);
351 error(strcat("Can't dump DB to ", pFilename));
354 for(i = 0; i < n; ++i)
356 m = tokenizebyseparator(bufstr_get(db, i), "\\");
357 for(j = 2; j < m; j += 2)
358 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
363 void db_close(float db)
368 string db_get(float db, string pKey)
371 h = crc16(false, pKey) % DB_BUCKETS;
372 return uri_unescape(infoget(bufstr_get(db, h), pKey));
375 void db_put(float db, string pKey, string pValue)
378 h = crc16(false, pKey) % DB_BUCKETS;
379 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
386 db = db_load("foo.db");
387 print("LOADED. FILL...\n");
388 for(i = 0; i < DB_BUCKETS; ++i)
389 db_put(db, ftos(random()), "X");
390 print("FILLED. SAVE...\n");
391 db_save(db, "foo.db");
392 print("SAVED. CLOSE...\n");
397 // Multiline text file buffers
398 int buf_load(string pFilename)
405 fh = fopen(pFilename, FILE_READ);
412 while((l = fgets(fh)))
414 bufstr_set(buf, i, l);
421 void buf_save(float buf, string pFilename)
424 fh = fopen(pFilename, FILE_WRITE);
426 error(strcat("Can't write buf to ", pFilename));
427 n = buf_getsize(buf);
428 for(i = 0; i < n; ++i)
429 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
433 string format_time(float seconds)
435 float days, hours, minutes;
436 seconds = floor(seconds + 0.5);
437 days = floor(seconds / 864000);
438 seconds -= days * 864000;
439 hours = floor(seconds / 36000);
440 seconds -= hours * 36000;
441 minutes = floor(seconds / 600);
442 seconds -= minutes * 600;
444 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
446 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
449 string mmsss(float tenths)
453 tenths = floor(tenths + 0.5);
454 minutes = floor(tenths / 600);
455 tenths -= minutes * 600;
456 s = ftos(1000 + tenths);
457 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
460 string mmssss(float hundredths)
464 hundredths = floor(hundredths + 0.5);
465 minutes = floor(hundredths / 6000);
466 hundredths -= minutes * 6000;
467 s = ftos(10000 + hundredths);
468 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
471 string ScoreString(int pFlags, float pValue)
476 pValue = floor(pValue + 0.5); // round
478 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
480 else if(pFlags & SFL_RANK)
482 valstr = ftos(pValue);
484 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
485 valstr = strcat(valstr, "th");
486 else if(substring(valstr, l - 1, 1) == "1")
487 valstr = strcat(valstr, "st");
488 else if(substring(valstr, l - 1, 1) == "2")
489 valstr = strcat(valstr, "nd");
490 else if(substring(valstr, l - 1, 1) == "3")
491 valstr = strcat(valstr, "rd");
493 valstr = strcat(valstr, "th");
495 else if(pFlags & SFL_TIME)
496 valstr = TIME_ENCODED_TOSTRING(pValue);
498 valstr = ftos(pValue);
503 // compressed vector format:
504 // like MD3, just even shorter
505 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
506 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
507 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
508 // length = 2^(length_encoded/8) / 8
509 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
510 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
511 // the special value 0 indicates the zero vector
513 float lengthLogTable[128];
515 float invertLengthLog(float x)
519 if(x >= lengthLogTable[127])
521 if(x <= lengthLogTable[0])
529 m = floor((l + r) / 2);
530 if(lengthLogTable[m] < x)
536 // now: r is >=, l is <
537 float lerr = (x - lengthLogTable[l]);
538 float rerr = (lengthLogTable[r] - x);
544 vector decompressShortVector(int data)
549 float p = (data & 0xF000) / 0x1000;
550 float y = (data & 0x0F80) / 0x80;
551 int len = (data & 0x007F);
553 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
566 y = .19634954084936207740 * y;
567 p = .19634954084936207740 * p - 1.57079632679489661922;
568 out.x = cos(y) * cos(p);
569 out.y = sin(y) * cos(p);
573 //print("decompressed: ", vtos(out), "\n");
575 return out * lengthLogTable[len];
578 float compressShortVector(vector vec)
584 //print("compress: ", vtos(vec), "\n");
585 ang = vectoangles(vec);
589 if(ang.x < -90 && ang.x > +90)
590 error("BOGUS vectoangles");
591 //print("angles: ", vtos(ang), "\n");
593 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
602 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
603 len = invertLengthLog(vlen(vec));
605 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
607 return (p * 0x1000) + (y * 0x80) + len;
610 void compressShortVector_init()
613 float f = pow(2, 1/8);
615 for(i = 0; i < 128; ++i)
617 lengthLogTable[i] = l;
621 if(cvar("developer"))
623 print("Verifying vector compression table...\n");
624 for(i = 0x0F00; i < 0xFFFF; ++i)
625 if(i != compressShortVector(decompressShortVector(i)))
627 print("BROKEN vector compression: ", ftos(i));
628 print(" -> ", vtos(decompressShortVector(i)));
629 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
638 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
640 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
641 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
642 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
643 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
644 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
645 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
646 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
647 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
648 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
649 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
650 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
651 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
656 string fixPriorityList(string order, float from, float to, float subtract, float complete)
661 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 mapPriorityList(string order, string(string) mapfunc)
700 n = tokenize_console(order);
702 for(i = 0; i < n; ++i)
703 neworder = strcat(neworder, mapfunc(argv(i)), " ");
705 return substring(neworder, 0, strlen(neworder) - 1);
708 string swapInPriorityList(string order, float i, float j)
713 n = tokenize_console(order);
715 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
718 for(w = 0; w < n; ++w)
721 s = strcat(s, argv(j), " ");
723 s = strcat(s, argv(i), " ");
725 s = strcat(s, argv(w), " ");
727 return substring(s, 0, strlen(s) - 1);
733 float cvar_value_issafe(string s)
735 if(strstrofs(s, "\"", 0) >= 0)
737 if(strstrofs(s, "\\", 0) >= 0)
739 if(strstrofs(s, ";", 0) >= 0)
741 if(strstrofs(s, "$", 0) >= 0)
743 if(strstrofs(s, "\r", 0) >= 0)
745 if(strstrofs(s, "\n", 0) >= 0)
751 void get_mi_min_max(float mode)
756 strunzone(mi_shortname);
757 mi_shortname = mapname;
758 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
759 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
760 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
761 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
762 mi_shortname = strzone(mi_shortname);
774 MapInfo_Get_ByName(mi_shortname, 0, 0);
775 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
777 mi_min = MapInfo_Map_mins;
778 mi_max = MapInfo_Map_maxs;
786 tracebox('1 0 0' * mi.x,
787 '0 1 0' * mi.y + '0 0 1' * mi.z,
788 '0 1 0' * ma.y + '0 0 1' * ma.z,
792 if(!trace_startsolid)
793 mi_min.x = trace_endpos.x;
795 tracebox('0 1 0' * mi.y,
796 '1 0 0' * mi.x + '0 0 1' * mi.z,
797 '1 0 0' * ma.x + '0 0 1' * ma.z,
801 if(!trace_startsolid)
802 mi_min.y = trace_endpos.y;
804 tracebox('0 0 1' * mi.z,
805 '1 0 0' * mi.x + '0 1 0' * mi.y,
806 '1 0 0' * ma.x + '0 1 0' * ma.y,
810 if(!trace_startsolid)
811 mi_min.z = trace_endpos.z;
813 tracebox('1 0 0' * ma.x,
814 '0 1 0' * mi.y + '0 0 1' * mi.z,
815 '0 1 0' * ma.y + '0 0 1' * ma.z,
819 if(!trace_startsolid)
820 mi_max.x = trace_endpos.x;
822 tracebox('0 1 0' * ma.y,
823 '1 0 0' * mi.x + '0 0 1' * mi.z,
824 '1 0 0' * ma.x + '0 0 1' * ma.z,
828 if(!trace_startsolid)
829 mi_max.y = trace_endpos.y;
831 tracebox('0 0 1' * ma.z,
832 '1 0 0' * mi.x + '0 1 0' * mi.y,
833 '1 0 0' * ma.x + '0 1 0' * ma.y,
837 if(!trace_startsolid)
838 mi_max.z = trace_endpos.z;
843 void get_mi_min_max_texcoords(float mode)
847 get_mi_min_max(mode);
852 // extend mi_picmax to get a square aspect ratio
853 // center the map in that area
854 extend = mi_picmax - mi_picmin;
855 if(extend.y > extend.x)
857 mi_picmin.x -= (extend.y - extend.x) * 0.5;
858 mi_picmax.x += (extend.y - extend.x) * 0.5;
862 mi_picmin.y -= (extend.x - extend.y) * 0.5;
863 mi_picmax.y += (extend.x - extend.y) * 0.5;
866 // add another some percent
867 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
871 // calculate the texcoords
872 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
873 // first the two corners of the origin
874 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
875 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
876 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
877 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
878 // then the other corners
879 mi_pictexcoord1_x = mi_pictexcoord0_x;
880 mi_pictexcoord1_y = mi_pictexcoord2_y;
881 mi_pictexcoord3_x = mi_pictexcoord2_x;
882 mi_pictexcoord3_y = mi_pictexcoord0_y;
886 float cvar_settemp(string tmp_cvar, string tmp_value)
888 float created_saved_value;
891 created_saved_value = 0;
893 if (!(tmp_cvar || tmp_value))
895 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
899 if(!cvar_type(tmp_cvar))
901 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
905 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
906 if(e.netname == tmp_cvar)
907 created_saved_value = -1; // skip creation
909 if(created_saved_value != -1)
911 // creating a new entity to keep track of this cvar
913 e.classname = "saved_cvar_value";
914 e.netname = strzone(tmp_cvar);
915 e.message = strzone(cvar_string(tmp_cvar));
916 created_saved_value = 1;
919 // update the cvar to the value given
920 cvar_set(tmp_cvar, tmp_value);
922 return created_saved_value;
925 float cvar_settemp_restore()
929 while((e = find(e, classname, "saved_cvar_value")))
931 if(cvar_type(e.netname))
933 cvar_set(e.netname, e.message);
938 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
944 float almost_equals(float a, float b)
947 eps = (max(a, -a) + max(b, -b)) * 0.001;
948 if(a - b < eps && b - a < eps)
953 float almost_in_bounds(float a, float b, float c)
956 eps = (max(a, -a) + max(c, -c)) * 0.001;
959 return b == median(a - eps, b, c + eps);
962 float power2of(float e)
966 float log2of(float x)
968 // NOTE: generated code
1041 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1045 else if(ma == rgb.x)
1048 return (rgb.y - rgb.z) / (ma - mi);
1050 return (rgb.y - rgb.z) / (ma - mi) + 6;
1052 else if(ma == rgb.y)
1053 return (rgb.z - rgb.x) / (ma - mi) + 2;
1054 else // if(ma == rgb_z)
1055 return (rgb.x - rgb.y) / (ma - mi) + 4;
1058 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1062 hue -= 6 * floor(hue / 6);
1064 //else if(ma == rgb_x)
1065 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1069 rgb.y = hue * (ma - mi) + mi;
1072 //else if(ma == rgb_y)
1073 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1076 rgb.x = (2 - hue) * (ma - mi) + mi;
1084 rgb.z = (hue - 2) * (ma - mi) + mi;
1086 //else // if(ma == rgb_z)
1087 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1091 rgb.y = (4 - hue) * (ma - mi) + mi;
1096 rgb.x = (hue - 4) * (ma - mi) + mi;
1100 //else if(ma == rgb_x)
1101 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1102 else // if(hue <= 6)
1106 rgb.z = (6 - hue) * (ma - mi) + mi;
1112 vector rgb_to_hsv(vector rgb)
1117 mi = min(rgb.x, rgb.y, rgb.z);
1118 ma = max(rgb.x, rgb.y, rgb.z);
1120 hsv.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1131 vector hsv_to_rgb(vector hsv)
1133 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1136 vector rgb_to_hsl(vector rgb)
1141 mi = min(rgb.x, rgb.y, rgb.z);
1142 ma = max(rgb.x, rgb.y, rgb.z);
1144 hsl.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1146 hsl.z = 0.5 * (mi + ma);
1149 else if(hsl.z <= 0.5)
1150 hsl.y = (ma - mi) / (2*hsl.z);
1151 else // if(hsl_z > 0.5)
1152 hsl.y = (ma - mi) / (2 - 2*hsl.z);
1157 vector hsl_to_rgb(vector hsl)
1159 float mi, ma, maminusmi;
1162 maminusmi = hsl.y * 2 * hsl.z;
1164 maminusmi = hsl.y * (2 - 2 * hsl.z);
1166 // hsl_z = 0.5 * mi + 0.5 * ma
1167 // maminusmi = - mi + ma
1168 mi = hsl.z - 0.5 * maminusmi;
1169 ma = hsl.z + 0.5 * maminusmi;
1171 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1174 string rgb_to_hexcolor(vector rgb)
1179 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1180 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1181 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1185 // requires that m2>m1 in all coordinates, and that m4>m3
1186 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;}
1188 // requires the same, but is a stronger condition
1189 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;}
1194 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1197 // The following function is SLOW.
1198 // For your safety and for the protection of those around you...
1199 // DO NOT CALL THIS AT HOME.
1200 // No really, don't.
1201 if(w(theText, theSize) <= maxWidth)
1202 return strlen(theText); // yeah!
1204 // binary search for right place to cut string
1206 float left, right, middle; // this always works
1208 right = strlen(theText); // this always fails
1211 middle = floor((left + right) / 2);
1212 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1217 while(left < right - 1);
1219 if(w("^7", theSize) == 0) // detect color codes support in the width function
1221 // NOTE: when color codes are involved, this binary search is,
1222 // mathematically, BROKEN. However, it is obviously guaranteed to
1223 // terminate, as the range still halves each time - but nevertheless, it is
1224 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1225 // range, and "right" is outside).
1227 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1228 // and decrease left on the basis of the chars detected of the truncated tag
1229 // Even if the ^xrgb tag is not complete/correct, left is decreased
1230 // (sometimes too much but with a correct result)
1231 // it fixes also ^[0-9]
1232 while(left >= 1 && substring(theText, left-1, 1) == "^")
1235 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1237 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1239 ch = str2chr(theText, left-1);
1240 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1243 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1245 ch = str2chr(theText, left-2);
1246 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1248 ch = str2chr(theText, left-1);
1249 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1258 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1261 // The following function is SLOW.
1262 // For your safety and for the protection of those around you...
1263 // DO NOT CALL THIS AT HOME.
1264 // No really, don't.
1265 if(w(theText) <= maxWidth)
1266 return strlen(theText); // yeah!
1268 // binary search for right place to cut string
1270 float left, right, middle; // this always works
1272 right = strlen(theText); // this always fails
1275 middle = floor((left + right) / 2);
1276 if(w(substring(theText, 0, middle)) <= maxWidth)
1281 while(left < right - 1);
1283 if(w("^7") == 0) // detect color codes support in the width function
1285 // NOTE: when color codes are involved, this binary search is,
1286 // mathematically, BROKEN. However, it is obviously guaranteed to
1287 // terminate, as the range still halves each time - but nevertheless, it is
1288 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1289 // range, and "right" is outside).
1291 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1292 // and decrease left on the basis of the chars detected of the truncated tag
1293 // Even if the ^xrgb tag is not complete/correct, left is decreased
1294 // (sometimes too much but with a correct result)
1295 // it fixes also ^[0-9]
1296 while(left >= 1 && substring(theText, left-1, 1) == "^")
1299 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1301 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1303 ch = str2chr(theText, left-1);
1304 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1307 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1309 ch = str2chr(theText, left-2);
1310 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1312 ch = str2chr(theText, left-1);
1313 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1322 string find_last_color_code(string s)
1324 int start = strstrofs(s, "^", 0);
1325 if (start == -1) // no caret found
1327 int len = strlen(s)-1;
1329 for(i = len; i >= start; --i)
1331 if(substring(s, i, 1) != "^")
1335 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1338 // check if carets aren't all escaped
1342 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1343 return substring(s, i, 2);
1346 if(substring(s, i+1, 1) == "x")
1347 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1348 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1349 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1350 return substring(s, i, 5);
1352 i -= carets; // this also skips one char before the carets
1358 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1364 s = getWrappedLine_remaining;
1368 getWrappedLine_remaining = string_null;
1369 return s; // the line has no size ANYWAY, nothing would be displayed.
1372 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1373 if(cantake > 0 && cantake < strlen(s))
1376 while(take > 0 && substring(s, take, 1) != " ")
1380 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1381 if(getWrappedLine_remaining == "")
1382 getWrappedLine_remaining = string_null;
1383 else if (tw("^7", theFontSize) == 0)
1384 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1385 return substring(s, 0, cantake);
1389 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1390 if(getWrappedLine_remaining == "")
1391 getWrappedLine_remaining = string_null;
1392 else if (tw("^7", theFontSize) == 0)
1393 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1394 return substring(s, 0, take);
1399 getWrappedLine_remaining = string_null;
1404 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1410 s = getWrappedLine_remaining;
1414 getWrappedLine_remaining = string_null;
1415 return s; // the line has no size ANYWAY, nothing would be displayed.
1418 cantake = textLengthUpToLength(s, w, tw);
1419 if(cantake > 0 && cantake < strlen(s))
1422 while(take > 0 && substring(s, take, 1) != " ")
1426 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1427 if(getWrappedLine_remaining == "")
1428 getWrappedLine_remaining = string_null;
1429 else if (tw("^7") == 0)
1430 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1431 return substring(s, 0, cantake);
1435 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1436 if(getWrappedLine_remaining == "")
1437 getWrappedLine_remaining = string_null;
1438 else if (tw("^7") == 0)
1439 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1440 return substring(s, 0, take);
1445 getWrappedLine_remaining = string_null;
1450 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1452 if(tw(theText, theFontSize) <= maxWidth)
1455 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1458 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1460 if(tw(theText) <= maxWidth)
1463 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1466 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1468 string subpattern, subpattern2, subpattern3, subpattern4;
1469 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1471 subpattern2 = ",teams,";
1473 subpattern2 = ",noteams,";
1475 subpattern3 = ",teamspawns,";
1477 subpattern3 = ",noteamspawns,";
1478 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1479 subpattern4 = ",race,";
1481 subpattern4 = string_null;
1483 if(substring(pattern, 0, 1) == "-")
1485 pattern = substring(pattern, 1, strlen(pattern) - 1);
1486 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1488 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1490 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1492 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1497 if(substring(pattern, 0, 1) == "+")
1498 pattern = substring(pattern, 1, strlen(pattern) - 1);
1499 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1500 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1501 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1505 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1512 void shuffle(float n, swapfunc_t swap, entity pass)
1515 for(i = 1; i < n; ++i)
1517 // swap i-th item at a random position from 0 to i
1518 // proof for even distribution:
1521 // item n+1 gets at any position with chance 1/(n+1)
1522 // all others will get their 1/n chance reduced by factor n/(n+1)
1523 // to be on place n+1, their chance will be 1/(n+1)
1524 // 1/n * n/(n+1) = 1/(n+1)
1526 j = floor(random() * (i + 1));
1532 string substring_range(string s, float b, float e)
1534 return substring(s, b, e - b);
1537 string swapwords(string str, float i, float j)
1540 string s1, s2, s3, s4, s5;
1541 float si, ei, sj, ej, s0, en;
1542 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1543 si = argv_start_index(i);
1544 sj = argv_start_index(j);
1545 ei = argv_end_index(i);
1546 ej = argv_end_index(j);
1547 s0 = argv_start_index(0);
1548 en = argv_end_index(n-1);
1549 s1 = substring_range(str, s0, si);
1550 s2 = substring_range(str, si, ei);
1551 s3 = substring_range(str, ei, sj);
1552 s4 = substring_range(str, sj, ej);
1553 s5 = substring_range(str, ej, en);
1554 return strcat(s1, s4, s3, s2, s5);
1557 string _shufflewords_str;
1558 void _shufflewords_swapfunc(float i, float j, entity pass)
1560 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1562 string shufflewords(string str)
1565 _shufflewords_str = str;
1566 n = tokenizebyseparator(str, " ");
1567 shuffle(n, _shufflewords_swapfunc, world);
1568 str = _shufflewords_str;
1569 _shufflewords_str = string_null;
1573 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1589 // actually, every number solves the equation!
1600 if(a > 0) // put the smaller solution first
1602 v.x = ((-b)-D) / (2*a);
1603 v.y = ((-b)+D) / (2*a);
1607 v.x = (-b+D) / (2*a);
1608 v.y = (-b-D) / (2*a);
1614 // complex solutions!
1627 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1631 // make origin and speed relative
1636 // now solve for ret, ret normalized:
1637 // eorg + t * evel == t * ret * spd
1638 // or, rather, solve for t:
1639 // |eorg + t * evel| == t * spd
1640 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1641 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1642 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1643 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1644 // q = (eorg * eorg) / (evel * evel - spd * spd)
1645 if(!solution.z) // no real solution
1648 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1649 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1650 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1651 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1652 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1653 // spd < |evel| * sin angle(evel, eorg)
1656 else if(solution.x > 0)
1658 // both solutions > 0: take the smaller one
1659 // happens if p < 0 and q > 0
1660 ret = normalize(eorg + solution.x * evel);
1662 else if(solution.y > 0)
1664 // one solution > 0: take the larger one
1665 // happens if q < 0 or q == 0 and p < 0
1666 ret = normalize(eorg + solution.y * evel);
1670 // no solution > 0: reject
1671 // happens if p > 0 and q >= 0
1672 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1673 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1678 // "Enemy is moving away from me at more than spd"
1682 // NOTE: we always got a solution if spd > |evel|
1684 if(newton_style == 2)
1685 ret = normalize(ret * spd + myvel);
1690 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1695 if(newton_style == 2)
1697 // true Newtonian projectiles with automatic aim adjustment
1699 // solve: |outspeed * mydir - myvel| = spd
1700 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1701 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1705 // myvel^2 - (mydir * myvel)^2 > spd^2
1706 // velocity without mydir component > spd
1707 // fire at smallest possible spd that works?
1708 // |(mydir * myvel) * myvel - myvel| = spd
1710 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1714 outspeed = solution.y; // the larger one
1717 //outspeed = 0; // slowest possible shot
1718 outspeed = solution.x; // the real part (that is, the average!)
1719 //dprint("impossible shot, adjusting\n");
1722 outspeed = bound(spd * mi, outspeed, spd * ma);
1723 return mydir * outspeed;
1727 return myvel + spd * mydir;
1730 float compressShotOrigin(vector v)
1734 y = rint(v.y * 4) + 128;
1735 z = rint(v.z * 4) + 128;
1736 if(x > 255 || x < 0)
1738 print("shot origin ", vtos(v), " x out of bounds\n");
1739 x = bound(0, x, 255);
1741 if(y > 255 || y < 0)
1743 print("shot origin ", vtos(v), " y out of bounds\n");
1744 y = bound(0, y, 255);
1746 if(z > 255 || z < 0)
1748 print("shot origin ", vtos(v), " z out of bounds\n");
1749 z = bound(0, z, 255);
1751 return x * 0x10000 + y * 0x100 + z;
1753 vector decompressShotOrigin(int f)
1756 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1757 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1758 v.z = ((f & 0xFF) - 128) / 4;
1762 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1764 float start, end, root, child;
1767 start = floor((n - 2) / 2);
1770 // siftdown(start, count-1);
1772 while(root * 2 + 1 <= n-1)
1774 child = root * 2 + 1;
1776 if(cmp(child, child+1, pass) < 0)
1778 if(cmp(root, child, pass) < 0)
1780 swap(root, child, pass);
1796 // siftdown(0, end);
1798 while(root * 2 + 1 <= end)
1800 child = root * 2 + 1;
1801 if(child < end && cmp(child, child+1, pass) < 0)
1803 if(cmp(root, child, pass) < 0)
1805 swap(root, child, pass);
1815 void RandomSelection_Init()
1817 RandomSelection_totalweight = 0;
1818 RandomSelection_chosen_ent = world;
1819 RandomSelection_chosen_float = 0;
1820 RandomSelection_chosen_string = string_null;
1821 RandomSelection_best_priority = -1;
1823 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1825 if(priority > RandomSelection_best_priority)
1827 RandomSelection_best_priority = priority;
1828 RandomSelection_chosen_ent = e;
1829 RandomSelection_chosen_float = f;
1830 RandomSelection_chosen_string = s;
1831 RandomSelection_totalweight = weight;
1833 else if(priority == RandomSelection_best_priority)
1835 RandomSelection_totalweight += weight;
1836 if(random() * RandomSelection_totalweight <= weight)
1838 RandomSelection_chosen_ent = e;
1839 RandomSelection_chosen_float = f;
1840 RandomSelection_chosen_string = s;
1846 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1848 // NOTE: we'll always choose the SMALLER value...
1849 float healthdamage, armordamage, armorideal;
1850 if (deathtype == DEATH_DROWN) // Why should armor help here...
1853 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1854 armordamage = a + (h - 1); // damage we can take if we could use more armor
1855 armorideal = healthdamage * armorblock;
1857 if(armordamage < healthdamage)
1870 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1873 if (deathtype == DEATH_DROWN) // Why should armor help here...
1875 v.y = bound(0, damage * armorblock, a); // save
1876 v.x = bound(0, damage - v.y, damage); // take
1882 string getcurrentmod()
1886 m = cvar_string("fs_gamedir");
1887 n = tokenize_console(m);
1898 int v = ReadShort() * 256; // note: this is signed
1899 v += ReadByte(); // note: this is unsigned
1902 vector ReadInt48_t()
1905 v.x = ReadInt24_t();
1906 v.y = ReadInt24_t();
1910 vector ReadInt72_t()
1913 v.x = ReadInt24_t();
1914 v.y = ReadInt24_t();
1915 v.z = ReadInt24_t();
1919 void WriteInt24_t(float dst, float val)
1922 WriteShort(dst, (v = floor(val / 256)));
1923 WriteByte(dst, val - v * 256); // 0..255
1925 void WriteInt48_t(float dst, vector val)
1927 WriteInt24_t(dst, val.x);
1928 WriteInt24_t(dst, val.y);
1930 void WriteInt72_t(float dst, vector val)
1932 WriteInt24_t(dst, val.x);
1933 WriteInt24_t(dst, val.y);
1934 WriteInt24_t(dst, val.z);
1939 float float2range11(float f)
1941 // continuous function mapping all reals into -1..1
1942 return f / (fabs(f) + 1);
1945 float float2range01(float f)
1947 // continuous function mapping all reals into 0..1
1948 return 0.5 + 0.5 * float2range11(f);
1951 // from the GNU Scientific Library
1952 float gsl_ran_gaussian_lastvalue;
1953 float gsl_ran_gaussian_lastvalue_set;
1954 float gsl_ran_gaussian(float sigma)
1957 if(gsl_ran_gaussian_lastvalue_set)
1959 gsl_ran_gaussian_lastvalue_set = 0;
1960 return sigma * gsl_ran_gaussian_lastvalue;
1964 a = random() * 2 * M_PI;
1965 b = sqrt(-2 * log(random()));
1966 gsl_ran_gaussian_lastvalue = cos(a) * b;
1967 gsl_ran_gaussian_lastvalue_set = 1;
1968 return sigma * sin(a) * b;
1972 string car(string s)
1975 o = strstrofs(s, " ", 0);
1978 return substring(s, 0, o);
1980 string cdr(string s)
1983 o = strstrofs(s, " ", 0);
1986 return substring(s, o + 1, strlen(s) - (o + 1));
1988 float matchacl(string acl, string str)
1995 t = car(acl); acl = cdr(acl);
1998 if(substring(t, 0, 1) == "-")
2001 t = substring(t, 1, strlen(t) - 1);
2003 else if(substring(t, 0, 1) == "+")
2004 t = substring(t, 1, strlen(t) - 1);
2006 if(substring(t, -1, 1) == "*")
2008 t = substring(t, 0, strlen(t) - 1);
2009 s = substring(str, 0, strlen(t));
2021 float startsWith(string haystack, string needle)
2023 return substring(haystack, 0, strlen(needle)) == needle;
2025 float startsWithNocase(string haystack, string needle)
2027 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2030 string get_model_datafilename(string m, float sk, string fil)
2035 m = "models/player/*_";
2037 m = strcat(m, ftos(sk));
2040 return strcat(m, ".", fil);
2043 float get_model_parameters(string m, float sk)
2045 get_model_parameters_modelname = string_null;
2046 get_model_parameters_modelskin = -1;
2047 get_model_parameters_name = string_null;
2048 get_model_parameters_species = -1;
2049 get_model_parameters_sex = string_null;
2050 get_model_parameters_weight = -1;
2051 get_model_parameters_age = -1;
2052 get_model_parameters_desc = string_null;
2053 get_model_parameters_bone_upperbody = string_null;
2054 get_model_parameters_bone_weapon = string_null;
2055 for(int i = 0; i < MAX_AIM_BONES; ++i)
2057 get_model_parameters_bone_aim[i] = string_null;
2058 get_model_parameters_bone_aimweight[i] = 0;
2060 get_model_parameters_fixbone = 0;
2065 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2066 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2070 if(substring(m, -4, -1) != ".txt")
2072 if(substring(m, -6, 1) != "_")
2074 sk = stof(substring(m, -5, 1));
2075 m = substring(m, 0, -7);
2078 string fn = get_model_datafilename(m, sk, "txt");
2079 int fh = fopen(fn, FILE_READ);
2083 fn = get_model_datafilename(m, sk, "txt");
2084 fh = fopen(fn, FILE_READ);
2089 get_model_parameters_modelname = m;
2090 get_model_parameters_modelskin = sk;
2092 while((s = fgets(fh)))
2095 break; // next lines will be description
2099 get_model_parameters_name = s;
2103 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2104 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2105 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2106 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2107 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2108 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2109 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2112 get_model_parameters_sex = s;
2114 get_model_parameters_weight = stof(s);
2116 get_model_parameters_age = stof(s);
2117 if(c == "description")
2118 get_model_parameters_description = s;
2119 if(c == "bone_upperbody")
2120 get_model_parameters_bone_upperbody = s;
2121 if(c == "bone_weapon")
2122 get_model_parameters_bone_weapon = s;
2123 for(int i = 0; i < MAX_AIM_BONES; ++i)
2124 if(c == strcat("bone_aim", ftos(i)))
2126 get_model_parameters_bone_aimweight[i] = stof(car(s));
2127 get_model_parameters_bone_aim[i] = cdr(s);
2130 get_model_parameters_fixbone = stof(s);
2133 while((s = fgets(fh)))
2135 if(get_model_parameters_desc)
2136 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2138 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2146 vector vec2(vector v)
2153 vector NearestPointOnBox(entity box, vector org)
2155 vector m1, m2, nearest;
2157 m1 = box.mins + box.origin;
2158 m2 = box.maxs + box.origin;
2160 nearest.x = bound(m1_x, org.x, m2_x);
2161 nearest.y = bound(m1_y, org.y, m2_y);
2162 nearest.z = bound(m1_z, org.z, m2_z);
2168 float vercmp_recursive(string v1, string v2)
2174 dot1 = strstrofs(v1, ".", 0);
2175 dot2 = strstrofs(v2, ".", 0);
2179 s1 = substring(v1, 0, dot1);
2183 s2 = substring(v2, 0, dot2);
2185 r = stof(s1) - stof(s2);
2189 r = strcasecmp(s1, s2);
2202 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2205 float vercmp(string v1, string v2)
2207 if(strcasecmp(v1, v2) == 0) // early out check
2216 return vercmp_recursive(v1, v2);
2219 float u8_strsize(string s)
2239 // translation helpers
2240 string language_filename(string s)
2245 if(fn == "" || fn == "dump")
2247 fn = strcat(s, ".", fn);
2248 if((fh = fopen(fn, FILE_READ)) >= 0)
2255 string CTX(string s)
2257 float p = strstrofs(s, "^", 0);
2260 return substring(s, p+1, -1);
2263 // x-encoding (encoding as zero length invisible string)
2264 const string XENCODE_2 = "xX";
2265 const string XENCODE_22 = "0123456789abcdefABCDEF";
2266 string xencode(int f)
2269 d = f % 22; f = floor(f / 22);
2270 c = f % 22; f = floor(f / 22);
2271 b = f % 22; f = floor(f / 22);
2272 a = f % 2; // f = floor(f / 2);
2275 substring(XENCODE_2, a, 1),
2276 substring(XENCODE_22, b, 1),
2277 substring(XENCODE_22, c, 1),
2278 substring(XENCODE_22, d, 1)
2281 float xdecode(string s)
2284 if(substring(s, 0, 1) != "^")
2288 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2289 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2290 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2291 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2292 if(a < 0 || b < 0 || c < 0 || d < 0)
2294 return ((a * 22 + b) * 22 + c) * 22 + d;
2297 float lowestbit(int f)
2308 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2310 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2313 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2316 // escape the string to make it safe for consoles
2317 string MakeConsoleSafe(string input)
2319 input = strreplace("\n", "", input);
2320 input = strreplace("\\", "\\\\", input);
2321 input = strreplace("$", "$$", input);
2322 input = strreplace("\"", "\\\"", input);
2327 // get true/false value of a string with multiple different inputs
2328 float InterpretBoolean(string input)
2330 switch(strtolower(input))
2342 default: return stof(input);
2348 entity ReadCSQCEntity()
2350 int f = ReadShort();
2353 return findfloat(world, entnum, f);
2357 float shutdown_running;
2362 void CSQC_Shutdown()
2368 if(shutdown_running)
2370 print("Recursive shutdown detected! Only restoring cvars...\n");
2374 shutdown_running = 1;
2377 cvar_settemp_restore(); // this must be done LAST, but in any case
2380 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2381 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2382 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2383 // this will use the value:
2385 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2386 // accuracy at x is 1/derivative, i.e.
2387 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2389 void WriteApproxPastTime(float dst, float t)
2391 float dt = time - t;
2393 // warning: this is approximate; do not resend when you don't have to!
2394 // be careful with sendflags here!
2395 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2398 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2401 dt = rint(bound(0, dt, 255));
2407 float ReadApproxPastTime()
2409 float dt = ReadByte();
2411 // map from range...PPROXPASTTIME_MAX / 256
2412 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2414 return servertime - dt;
2419 .float skeleton_bones_index;
2420 void Skeleton_SetBones(entity e)
2422 // set skeleton_bones to the total number of bones on the model
2423 if(e.skeleton_bones_index == e.modelindex)
2424 return; // same model, nothing to update
2427 skelindex = skel_create(e.modelindex);
2428 e.skeleton_bones = skel_get_numbones(skelindex);
2429 skel_delete(skelindex);
2430 e.skeleton_bones_index = e.modelindex;
2434 string to_execute_next_frame;
2435 void execute_next_frame()
2437 if(to_execute_next_frame)
2439 localcmd("\n", to_execute_next_frame, "\n");
2440 strunzone(to_execute_next_frame);
2441 to_execute_next_frame = string_null;
2444 void queue_to_execute_next_frame(string s)
2446 if(to_execute_next_frame)
2448 s = strcat(s, "\n", to_execute_next_frame);
2449 strunzone(to_execute_next_frame);
2451 to_execute_next_frame = strzone(s);
2454 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2457 ((( startspeedfactor + endspeedfactor - 2
2458 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2459 ) * x + startspeedfactor
2463 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2465 if(startspeedfactor < 0 || endspeedfactor < 0)
2469 // if this is the case, the possible zeros of the first derivative are outside
2471 We can calculate this condition as condition
2476 // better, see below:
2477 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2480 // if this is the case, the first derivative has no zeros at all
2481 float se = startspeedfactor + endspeedfactor;
2482 float s_e = startspeedfactor - endspeedfactor;
2483 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2486 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2487 // we also get s_e <= 6 - se
2488 // 3 * (se - 4)^2 + (6 - se)^2
2489 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2490 // Therefore, above "better" check works!
2494 // known good cases:
2502 // (3.5, [0.2..2.3])
2507 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2509 s + e - 2 == 0: no inflection
2512 0 < inflection < 1 if:
2513 0 < 2s + e - 3 < 3s + 3e - 6
2514 2s + e > 3 and 2e + s > 3
2517 0 < inflection < 1 if:
2518 0 > 2s + e - 3 > 3s + 3e - 6
2519 2s + e < 3 and 2e + s < 3
2521 Therefore: there is an inflection point iff:
2522 e outside (3 - s)/2 .. 3 - s*2
2524 in other words, if (s,e) in triangle (1,1)(0,3)(0,1.5) or in triangle (1,1)(3,0)(1.5,0)
2528 .float FindConnectedComponent_processing;
2529 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2531 entity queue_start, queue_end;
2533 // we build a queue of to-be-processed entities.
2534 // queue_start is the next entity to be checked for neighbors
2535 // queue_end is the last entity added
2537 if(e.FindConnectedComponent_processing)
2538 error("recursion or broken cleanup");
2540 // start with a 1-element queue
2541 queue_start = queue_end = e;
2542 queue_end.fld = world;
2543 queue_end.FindConnectedComponent_processing = 1;
2545 // for each queued item:
2546 for (; queue_start; queue_start = queue_start.fld)
2548 // find all neighbors of queue_start
2550 for(t = world; (t = nxt(t, queue_start, pass)); )
2552 if(t.FindConnectedComponent_processing)
2554 if(iscon(t, queue_start, pass))
2556 // it is connected? ADD IT. It will look for neighbors soon too.
2559 queue_end.fld = world;
2560 queue_end.FindConnectedComponent_processing = 1;
2566 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2567 queue_start.FindConnectedComponent_processing = 0;
2571 vector combine_to_vector(float x, float y, float z)
2573 vector result; result.x = x; result.y = y; result.z = z;
2577 vector get_corner_position(entity box, float corner)
2581 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2582 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2583 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2584 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2585 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2586 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2587 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2588 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2589 default: return '0 0 0';
2594 // todo: this sucks, lets find a better way to do backtraces?
2595 void backtrace(string msg)
2599 dev = autocvar_developer;
2600 war = autocvar_prvm_backtraceforwarnings;
2602 dev = cvar("developer");
2603 war = cvar("prvm_backtraceforwarnings");
2605 cvar_set("developer", "1");
2606 cvar_set("prvm_backtraceforwarnings", "1");
2608 print("--- CUT HERE ---\nWARNING: ");
2611 remove(world); // isn't there any better way to cause a backtrace?
2612 print("\n--- CUT UNTIL HERE ---\n");
2613 cvar_set("developer", ftos(dev));
2614 cvar_set("prvm_backtraceforwarnings", ftos(war));
2617 // color code replace, place inside of sprintf and parse the string
2618 string CCR(string input)
2620 // See the autocvar declarations in util.qh for default values
2622 // foreground/normal colors
2623 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2624 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2625 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2626 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2629 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2630 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2631 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2633 // background colors
2634 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2635 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2639 vector vec3(float x, float y, float z)
2649 vector animfixfps(entity e, vector a, vector b)
2651 // multi-frame anim: keep as-is
2655 dur = frameduration(e.modelindex, a.x);
2659 dur = frameduration(e.modelindex, a.x);
2669 void dedicated_print(string input) // print(), but only print if the server is not local
2671 if(server_is_dedicated) { print(input); }
2676 float Announcer_PickNumber(float type, float num)
2684 case 10: return ANNCE_NUM_GAMESTART_10;
2685 case 9: return ANNCE_NUM_GAMESTART_9;
2686 case 8: return ANNCE_NUM_GAMESTART_8;
2687 case 7: return ANNCE_NUM_GAMESTART_7;
2688 case 6: return ANNCE_NUM_GAMESTART_6;
2689 case 5: return ANNCE_NUM_GAMESTART_5;
2690 case 4: return ANNCE_NUM_GAMESTART_4;
2691 case 3: return ANNCE_NUM_GAMESTART_3;
2692 case 2: return ANNCE_NUM_GAMESTART_2;
2693 case 1: return ANNCE_NUM_GAMESTART_1;
2701 case 10: return ANNCE_NUM_IDLE_10;
2702 case 9: return ANNCE_NUM_IDLE_9;
2703 case 8: return ANNCE_NUM_IDLE_8;
2704 case 7: return ANNCE_NUM_IDLE_7;
2705 case 6: return ANNCE_NUM_IDLE_6;
2706 case 5: return ANNCE_NUM_IDLE_5;
2707 case 4: return ANNCE_NUM_IDLE_4;
2708 case 3: return ANNCE_NUM_IDLE_3;
2709 case 2: return ANNCE_NUM_IDLE_2;
2710 case 1: return ANNCE_NUM_IDLE_1;
2718 case 10: return ANNCE_NUM_KILL_10;
2719 case 9: return ANNCE_NUM_KILL_9;
2720 case 8: return ANNCE_NUM_KILL_8;
2721 case 7: return ANNCE_NUM_KILL_7;
2722 case 6: return ANNCE_NUM_KILL_6;
2723 case 5: return ANNCE_NUM_KILL_5;
2724 case 4: return ANNCE_NUM_KILL_4;
2725 case 3: return ANNCE_NUM_KILL_3;
2726 case 2: return ANNCE_NUM_KILL_2;
2727 case 1: return ANNCE_NUM_KILL_1;
2735 case 10: return ANNCE_NUM_RESPAWN_10;
2736 case 9: return ANNCE_NUM_RESPAWN_9;
2737 case 8: return ANNCE_NUM_RESPAWN_8;
2738 case 7: return ANNCE_NUM_RESPAWN_7;
2739 case 6: return ANNCE_NUM_RESPAWN_6;
2740 case 5: return ANNCE_NUM_RESPAWN_5;
2741 case 4: return ANNCE_NUM_RESPAWN_4;
2742 case 3: return ANNCE_NUM_RESPAWN_3;
2743 case 2: return ANNCE_NUM_RESPAWN_2;
2744 case 1: return ANNCE_NUM_RESPAWN_1;
2748 case CNT_ROUNDSTART:
2752 case 10: return ANNCE_NUM_ROUNDSTART_10;
2753 case 9: return ANNCE_NUM_ROUNDSTART_9;
2754 case 8: return ANNCE_NUM_ROUNDSTART_8;
2755 case 7: return ANNCE_NUM_ROUNDSTART_7;
2756 case 6: return ANNCE_NUM_ROUNDSTART_6;
2757 case 5: return ANNCE_NUM_ROUNDSTART_5;
2758 case 4: return ANNCE_NUM_ROUNDSTART_4;
2759 case 3: return ANNCE_NUM_ROUNDSTART_3;
2760 case 2: return ANNCE_NUM_ROUNDSTART_2;
2761 case 1: return ANNCE_NUM_ROUNDSTART_1;
2769 case 10: return ANNCE_NUM_10;
2770 case 9: return ANNCE_NUM_9;
2771 case 8: return ANNCE_NUM_8;
2772 case 7: return ANNCE_NUM_7;
2773 case 6: return ANNCE_NUM_6;
2774 case 5: return ANNCE_NUM_5;
2775 case 4: return ANNCE_NUM_4;
2776 case 3: return ANNCE_NUM_3;
2777 case 2: return ANNCE_NUM_2;
2778 case 1: return ANNCE_NUM_1;
2783 return NOTIF_ABORT; // abort sending if none of these numbers were right
2788 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2790 switch(nativecontents)
2795 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2797 return DPCONTENTS_WATER;
2799 return DPCONTENTS_SLIME;
2801 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2803 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2808 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2810 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2811 return CONTENT_SOLID;
2812 if(supercontents & DPCONTENTS_SKY)
2814 if(supercontents & DPCONTENTS_LAVA)
2815 return CONTENT_LAVA;
2816 if(supercontents & DPCONTENTS_SLIME)
2817 return CONTENT_SLIME;
2818 if(supercontents & DPCONTENTS_WATER)
2819 return CONTENT_WATER;
2820 return CONTENT_EMPTY;
2824 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2827 (c - 2 * b + a) * (t * t) +
2832 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2835 (c - 2 * b + a) * (2 * t) +