4 #include "../dpdefs/csprogsdefs.qh"
5 #include "../client/defs.qh"
6 #include "constants.qh"
7 #include "../warpzonelib/mathlib.qh"
8 #include "../client/mutators/events.qh"
10 #include "notifications.qh"
11 #include "deathtypes.qh"
14 #include "../dpdefs/progsdefs.qh"
15 #include "../dpdefs/dpextensions.qh"
16 #include "../warpzonelib/mathlib.qh"
17 #include "constants.qh"
18 #include "../server/autocvars.qh"
19 #include "../server/defs.qh"
20 #include "../server/mutators/events.qh"
21 #include "notifications.qh"
22 #include "deathtypes.qh"
26 string wordwrap_buffer;
28 void wordwrap_buffer_put(string s)
30 wordwrap_buffer = strcat(wordwrap_buffer, s);
33 string wordwrap(string s, float l)
37 wordwrap_cb(s, l, wordwrap_buffer_put);
45 void wordwrap_buffer_sprint(string s)
47 wordwrap_buffer = strcat(wordwrap_buffer, s);
50 sprint(self, wordwrap_buffer);
55 void wordwrap_sprint(string s, float l)
58 wordwrap_cb(s, l, wordwrap_buffer_sprint);
59 if(wordwrap_buffer != "")
60 sprint(self, strcat(wordwrap_buffer, "\n"));
68 string draw_UseSkinFor(string pic)
70 if(substring(pic, 0, 1) == "/")
71 return substring(pic, 1, strlen(pic)-1);
73 return strcat(draw_currentSkin, "/", pic);
77 string unescape(string in)
82 // but it doesn't seem to be necessary in my tests at least
87 for(i = 0; i < len; ++i)
89 s = substring(in, i, 1);
92 s = substring(in, i+1, 1);
94 str = strcat(str, "\n");
96 str = strcat(str, "\\");
98 str = strcat(str, substring(in, i, 2));
101 str = strcat(str, s);
108 void wordwrap_cb(string s, float l, void(string) callback)
111 float lleft, i, j, wlen;
115 for (i = 0;i < strlen(s);++i)
117 if (substring(s, i, 2) == "\\n")
123 else if (substring(s, i, 1) == "\n")
128 else if (substring(s, i, 1) == " ")
138 for (j = i+1;j < strlen(s);++j)
139 // ^^ this skips over the first character of a word, which
140 // is ALWAYS part of the word
141 // this is safe since if i+1 == strlen(s), i will become
142 // strlen(s)-1 at the end of this block and the function
143 // will terminate. A space can't be the first character we
144 // read here, and neither can a \n be the start, since these
145 // two cases have been handled above.
147 c = substring(s, j, 1);
154 // we need to keep this tempstring alive even if substring is
155 // called repeatedly, so call strcat even though we're not
165 callback(substring(s, i, wlen));
166 lleft = lleft - wlen;
173 float dist_point_line(vector p, vector l0, vector ldir)
175 ldir = normalize(ldir);
177 // remove the component in line direction
178 p = p - (p * ldir) * ldir;
180 // vlen of the remaining vector
184 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
213 float median(float a, float b, float c)
216 return bound(a, b, c);
217 return bound(c, b, a);
220 // converts a number to a string with the indicated number of decimals
221 // works for up to 10 decimals!
222 string ftos_decimals(float number, float decimals)
224 // inhibit stupid negative zero
227 // we have sprintf...
228 return sprintf("%.*f", decimals, number);
231 vector colormapPaletteColor(float c, float isPants)
235 case 0: return '1.000000 1.000000 1.000000';
236 case 1: return '1.000000 0.333333 0.000000';
237 case 2: return '0.000000 1.000000 0.501961';
238 case 3: return '0.000000 1.000000 0.000000';
239 case 4: return '1.000000 0.000000 0.000000';
240 case 5: return '0.000000 0.666667 1.000000';
241 case 6: return '0.000000 1.000000 1.000000';
242 case 7: return '0.501961 1.000000 0.000000';
243 case 8: return '0.501961 0.000000 1.000000';
244 case 9: return '1.000000 0.000000 1.000000';
245 case 10: return '1.000000 0.000000 0.501961';
246 case 11: return '0.000000 0.000000 1.000000';
247 case 12: return '1.000000 1.000000 0.000000';
248 case 13: return '0.000000 0.333333 1.000000';
249 case 14: return '1.000000 0.666667 0.000000';
253 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
254 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
255 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
258 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
259 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
260 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
261 default: return '0.000 0.000 0.000';
265 // unzone the string, and return it as tempstring. Safe to be called on string_null
266 string fstrunzone(string s)
276 bool fexists(string f)
278 int fh = fopen(f, FILE_READ);
285 // Databases (hash tables)
286 const float DB_BUCKETS = 8192;
287 void db_save(float db, string pFilename)
290 fh = fopen(pFilename, FILE_WRITE);
293 print(strcat("^1Can't write DB to ", pFilename));
297 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
298 for(i = 0; i < n; ++i)
299 fputs(fh, strcat(bufstr_get(db, i), "\n"));
308 int db_load(string pFilename)
310 float db, fh, i, j, n;
315 fh = fopen(pFilename, FILE_READ);
319 if(stof(l) == DB_BUCKETS)
322 while((l = fgets(fh)))
325 bufstr_set(db, i, l);
331 // different count of buckets, or a dump?
332 // need to reorganize the database then (SLOW)
334 // note: we also parse the first line (l) in case the DB file is
335 // missing the bucket count
338 n = tokenizebyseparator(l, "\\");
339 for(j = 2; j < n; j += 2)
340 db_put(db, argv(j-1), uri_unescape(argv(j)));
342 while((l = fgets(fh)));
348 void db_dump(float db, string pFilename)
350 float fh, i, j, n, m;
351 fh = fopen(pFilename, FILE_WRITE);
353 error(strcat("Can't dump DB to ", pFilename));
356 for(i = 0; i < n; ++i)
358 m = tokenizebyseparator(bufstr_get(db, i), "\\");
359 for(j = 2; j < m; j += 2)
360 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
365 void db_close(float db)
370 string db_get(float db, string pKey)
373 h = crc16(false, pKey) % DB_BUCKETS;
374 return uri_unescape(infoget(bufstr_get(db, h), pKey));
377 void db_put(float db, string pKey, string pValue)
380 h = crc16(false, pKey) % DB_BUCKETS;
381 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
388 db = db_load("foo.db");
389 print("LOADED. FILL...\n");
390 for(i = 0; i < DB_BUCKETS; ++i)
391 db_put(db, ftos(random()), "X");
392 print("FILLED. SAVE...\n");
393 db_save(db, "foo.db");
394 print("SAVED. CLOSE...\n");
399 // Multiline text file buffers
400 int buf_load(string pFilename)
407 fh = fopen(pFilename, FILE_READ);
414 while((l = fgets(fh)))
416 bufstr_set(buf, i, l);
423 void buf_save(float buf, string pFilename)
426 fh = fopen(pFilename, FILE_WRITE);
428 error(strcat("Can't write buf to ", pFilename));
429 n = buf_getsize(buf);
430 for(i = 0; i < n; ++i)
431 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
435 string format_time(float seconds)
437 float days, hours, minutes;
438 seconds = floor(seconds + 0.5);
439 days = floor(seconds / 864000);
440 seconds -= days * 864000;
441 hours = floor(seconds / 36000);
442 seconds -= hours * 36000;
443 minutes = floor(seconds / 600);
444 seconds -= minutes * 600;
446 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
448 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
451 string mmsss(float tenths)
455 tenths = floor(tenths + 0.5);
456 minutes = floor(tenths / 600);
457 tenths -= minutes * 600;
458 s = ftos(1000 + tenths);
459 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
462 string mmssss(float hundredths)
466 hundredths = floor(hundredths + 0.5);
467 minutes = floor(hundredths / 6000);
468 hundredths -= minutes * 6000;
469 s = ftos(10000 + hundredths);
470 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
473 string ScoreString(int pFlags, float pValue)
478 pValue = floor(pValue + 0.5); // round
480 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
482 else if(pFlags & SFL_RANK)
484 valstr = ftos(pValue);
486 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
487 valstr = strcat(valstr, "th");
488 else if(substring(valstr, l - 1, 1) == "1")
489 valstr = strcat(valstr, "st");
490 else if(substring(valstr, l - 1, 1) == "2")
491 valstr = strcat(valstr, "nd");
492 else if(substring(valstr, l - 1, 1) == "3")
493 valstr = strcat(valstr, "rd");
495 valstr = strcat(valstr, "th");
497 else if(pFlags & SFL_TIME)
498 valstr = TIME_ENCODED_TOSTRING(pValue);
500 valstr = ftos(pValue);
505 // compressed vector format:
506 // like MD3, just even shorter
507 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
508 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
509 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
510 // length = 2^(length_encoded/8) / 8
511 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
512 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
513 // the special value 0 indicates the zero vector
515 float lengthLogTable[128];
517 float invertLengthLog(float x)
521 if(x >= lengthLogTable[127])
523 if(x <= lengthLogTable[0])
531 m = floor((l + r) / 2);
532 if(lengthLogTable[m] < x)
538 // now: r is >=, l is <
539 float lerr = (x - lengthLogTable[l]);
540 float rerr = (lengthLogTable[r] - x);
546 vector decompressShortVector(int data)
551 float p = (data & 0xF000) / 0x1000;
552 float y = (data & 0x0F80) / 0x80;
553 int len = (data & 0x007F);
555 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
568 y = .19634954084936207740 * y;
569 p = .19634954084936207740 * p - 1.57079632679489661922;
570 out.x = cos(y) * cos(p);
571 out.y = sin(y) * cos(p);
575 //print("decompressed: ", vtos(out), "\n");
577 return out * lengthLogTable[len];
580 float compressShortVector(vector vec)
586 //print("compress: ", vtos(vec), "\n");
587 ang = vectoangles(vec);
591 if(ang.x < -90 && ang.x > +90)
592 error("BOGUS vectoangles");
593 //print("angles: ", vtos(ang), "\n");
595 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
604 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
605 len = invertLengthLog(vlen(vec));
607 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
609 return (p * 0x1000) + (y * 0x80) + len;
612 void compressShortVector_init()
615 float f = pow(2, 1/8);
617 for(i = 0; i < 128; ++i)
619 lengthLogTable[i] = l;
623 if(cvar("developer"))
625 print("Verifying vector compression table...\n");
626 for(i = 0x0F00; i < 0xFFFF; ++i)
627 if(i != compressShortVector(decompressShortVector(i)))
629 print("BROKEN vector compression: ", ftos(i));
630 print(" -> ", vtos(decompressShortVector(i)));
631 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
640 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
642 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
643 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
644 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
645 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
646 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
647 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
648 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
649 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
650 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
651 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
652 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
653 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
658 string fixPriorityList(string order, float from, float to, float subtract, float complete)
663 n = tokenize_console(order);
665 for(i = 0; i < n; ++i)
670 if(w >= from && w <= to)
671 neworder = strcat(neworder, ftos(w), " ");
675 if(w >= from && w <= to)
676 neworder = strcat(neworder, ftos(w), " ");
683 n = tokenize_console(neworder);
684 for(w = to; w >= from; --w)
686 for(i = 0; i < n; ++i)
687 if(stof(argv(i)) == w)
689 if(i == n) // not found
690 neworder = strcat(neworder, ftos(w), " ");
694 return substring(neworder, 0, strlen(neworder) - 1);
697 string mapPriorityList(string order, string(string) mapfunc)
702 n = tokenize_console(order);
704 for(i = 0; i < n; ++i)
705 neworder = strcat(neworder, mapfunc(argv(i)), " ");
707 return substring(neworder, 0, strlen(neworder) - 1);
710 string swapInPriorityList(string order, float i, float j)
715 n = tokenize_console(order);
717 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
720 for(w = 0; w < n; ++w)
723 s = strcat(s, argv(j), " ");
725 s = strcat(s, argv(i), " ");
727 s = strcat(s, argv(w), " ");
729 return substring(s, 0, strlen(s) - 1);
735 float cvar_value_issafe(string s)
737 if(strstrofs(s, "\"", 0) >= 0)
739 if(strstrofs(s, "\\", 0) >= 0)
741 if(strstrofs(s, ";", 0) >= 0)
743 if(strstrofs(s, "$", 0) >= 0)
745 if(strstrofs(s, "\r", 0) >= 0)
747 if(strstrofs(s, "\n", 0) >= 0)
753 void get_mi_min_max(float mode)
758 strunzone(mi_shortname);
759 mi_shortname = mapname;
760 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
761 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
762 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
763 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
764 mi_shortname = strzone(mi_shortname);
776 MapInfo_Get_ByName(mi_shortname, 0, 0);
777 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
779 mi_min = MapInfo_Map_mins;
780 mi_max = MapInfo_Map_maxs;
788 tracebox('1 0 0' * mi.x,
789 '0 1 0' * mi.y + '0 0 1' * mi.z,
790 '0 1 0' * ma.y + '0 0 1' * ma.z,
794 if(!trace_startsolid)
795 mi_min.x = trace_endpos.x;
797 tracebox('0 1 0' * mi.y,
798 '1 0 0' * mi.x + '0 0 1' * mi.z,
799 '1 0 0' * ma.x + '0 0 1' * ma.z,
803 if(!trace_startsolid)
804 mi_min.y = trace_endpos.y;
806 tracebox('0 0 1' * mi.z,
807 '1 0 0' * mi.x + '0 1 0' * mi.y,
808 '1 0 0' * ma.x + '0 1 0' * ma.y,
812 if(!trace_startsolid)
813 mi_min.z = trace_endpos.z;
815 tracebox('1 0 0' * ma.x,
816 '0 1 0' * mi.y + '0 0 1' * mi.z,
817 '0 1 0' * ma.y + '0 0 1' * ma.z,
821 if(!trace_startsolid)
822 mi_max.x = trace_endpos.x;
824 tracebox('0 1 0' * ma.y,
825 '1 0 0' * mi.x + '0 0 1' * mi.z,
826 '1 0 0' * ma.x + '0 0 1' * ma.z,
830 if(!trace_startsolid)
831 mi_max.y = trace_endpos.y;
833 tracebox('0 0 1' * ma.z,
834 '1 0 0' * mi.x + '0 1 0' * mi.y,
835 '1 0 0' * ma.x + '0 1 0' * ma.y,
839 if(!trace_startsolid)
840 mi_max.z = trace_endpos.z;
845 void get_mi_min_max_texcoords(float mode)
849 get_mi_min_max(mode);
854 // extend mi_picmax to get a square aspect ratio
855 // center the map in that area
856 extend = mi_picmax - mi_picmin;
857 if(extend.y > extend.x)
859 mi_picmin.x -= (extend.y - extend.x) * 0.5;
860 mi_picmax.x += (extend.y - extend.x) * 0.5;
864 mi_picmin.y -= (extend.x - extend.y) * 0.5;
865 mi_picmax.y += (extend.x - extend.y) * 0.5;
868 // add another some percent
869 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
873 // calculate the texcoords
874 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
875 // first the two corners of the origin
876 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
877 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
878 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
879 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
880 // then the other corners
881 mi_pictexcoord1_x = mi_pictexcoord0_x;
882 mi_pictexcoord1_y = mi_pictexcoord2_y;
883 mi_pictexcoord3_x = mi_pictexcoord2_x;
884 mi_pictexcoord3_y = mi_pictexcoord0_y;
888 float cvar_settemp(string tmp_cvar, string tmp_value)
890 float created_saved_value;
893 created_saved_value = 0;
895 if (!(tmp_cvar || tmp_value))
897 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
901 if(!cvar_type(tmp_cvar))
903 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
907 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
908 if(e.netname == tmp_cvar)
909 created_saved_value = -1; // skip creation
911 if(created_saved_value != -1)
913 // creating a new entity to keep track of this cvar
915 e.classname = "saved_cvar_value";
916 e.netname = strzone(tmp_cvar);
917 e.message = strzone(cvar_string(tmp_cvar));
918 created_saved_value = 1;
921 // update the cvar to the value given
922 cvar_set(tmp_cvar, tmp_value);
924 return created_saved_value;
927 float cvar_settemp_restore()
931 while((e = find(e, classname, "saved_cvar_value")))
933 if(cvar_type(e.netname))
935 cvar_set(e.netname, e.message);
940 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
946 float almost_equals(float a, float b)
949 eps = (max(a, -a) + max(b, -b)) * 0.001;
950 if(a - b < eps && b - a < eps)
955 float almost_in_bounds(float a, float b, float c)
958 eps = (max(a, -a) + max(c, -c)) * 0.001;
961 return b == median(a - eps, b, c + eps);
964 float power2of(float e)
968 float log2of(float x)
970 // NOTE: generated code
1043 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1047 else if(ma == rgb.x)
1050 return (rgb.y - rgb.z) / (ma - mi);
1052 return (rgb.y - rgb.z) / (ma - mi) + 6;
1054 else if(ma == rgb.y)
1055 return (rgb.z - rgb.x) / (ma - mi) + 2;
1056 else // if(ma == rgb_z)
1057 return (rgb.x - rgb.y) / (ma - mi) + 4;
1060 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1064 hue -= 6 * floor(hue / 6);
1066 //else if(ma == rgb_x)
1067 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1071 rgb.y = hue * (ma - mi) + mi;
1074 //else if(ma == rgb_y)
1075 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1078 rgb.x = (2 - hue) * (ma - mi) + mi;
1086 rgb.z = (hue - 2) * (ma - mi) + mi;
1088 //else // if(ma == rgb_z)
1089 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1093 rgb.y = (4 - hue) * (ma - mi) + mi;
1098 rgb.x = (hue - 4) * (ma - mi) + mi;
1102 //else if(ma == rgb_x)
1103 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1104 else // if(hue <= 6)
1108 rgb.z = (6 - hue) * (ma - mi) + mi;
1114 vector rgb_to_hsv(vector rgb)
1119 mi = min(rgb.x, rgb.y, rgb.z);
1120 ma = max(rgb.x, rgb.y, rgb.z);
1122 hsv.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1133 vector hsv_to_rgb(vector hsv)
1135 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1138 vector rgb_to_hsl(vector rgb)
1143 mi = min(rgb.x, rgb.y, rgb.z);
1144 ma = max(rgb.x, rgb.y, rgb.z);
1146 hsl.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1148 hsl.z = 0.5 * (mi + ma);
1151 else if(hsl.z <= 0.5)
1152 hsl.y = (ma - mi) / (2*hsl.z);
1153 else // if(hsl_z > 0.5)
1154 hsl.y = (ma - mi) / (2 - 2*hsl.z);
1159 vector hsl_to_rgb(vector hsl)
1161 float mi, ma, maminusmi;
1164 maminusmi = hsl.y * 2 * hsl.z;
1166 maminusmi = hsl.y * (2 - 2 * hsl.z);
1168 // hsl_z = 0.5 * mi + 0.5 * ma
1169 // maminusmi = - mi + ma
1170 mi = hsl.z - 0.5 * maminusmi;
1171 ma = hsl.z + 0.5 * maminusmi;
1173 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1176 string rgb_to_hexcolor(vector rgb)
1181 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1182 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1183 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1187 // requires that m2>m1 in all coordinates, and that m4>m3
1188 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;}
1190 // requires the same, but is a stronger condition
1191 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;}
1196 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1199 // The following function is SLOW.
1200 // For your safety and for the protection of those around you...
1201 // DO NOT CALL THIS AT HOME.
1202 // No really, don't.
1203 if(w(theText, theSize) <= maxWidth)
1204 return strlen(theText); // yeah!
1206 // binary search for right place to cut string
1208 float left, right, middle; // this always works
1210 right = strlen(theText); // this always fails
1213 middle = floor((left + right) / 2);
1214 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1219 while(left < right - 1);
1221 if(w("^7", theSize) == 0) // detect color codes support in the width function
1223 // NOTE: when color codes are involved, this binary search is,
1224 // mathematically, BROKEN. However, it is obviously guaranteed to
1225 // terminate, as the range still halves each time - but nevertheless, it is
1226 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1227 // range, and "right" is outside).
1229 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1230 // and decrease left on the basis of the chars detected of the truncated tag
1231 // Even if the ^xrgb tag is not complete/correct, left is decreased
1232 // (sometimes too much but with a correct result)
1233 // it fixes also ^[0-9]
1234 while(left >= 1 && substring(theText, left-1, 1) == "^")
1237 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1239 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1241 ch = str2chr(theText, left-1);
1242 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1245 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1247 ch = str2chr(theText, left-2);
1248 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1250 ch = str2chr(theText, left-1);
1251 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1260 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1263 // The following function is SLOW.
1264 // For your safety and for the protection of those around you...
1265 // DO NOT CALL THIS AT HOME.
1266 // No really, don't.
1267 if(w(theText) <= maxWidth)
1268 return strlen(theText); // yeah!
1270 // binary search for right place to cut string
1272 float left, right, middle; // this always works
1274 right = strlen(theText); // this always fails
1277 middle = floor((left + right) / 2);
1278 if(w(substring(theText, 0, middle)) <= maxWidth)
1283 while(left < right - 1);
1285 if(w("^7") == 0) // detect color codes support in the width function
1287 // NOTE: when color codes are involved, this binary search is,
1288 // mathematically, BROKEN. However, it is obviously guaranteed to
1289 // terminate, as the range still halves each time - but nevertheless, it is
1290 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1291 // range, and "right" is outside).
1293 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1294 // and decrease left on the basis of the chars detected of the truncated tag
1295 // Even if the ^xrgb tag is not complete/correct, left is decreased
1296 // (sometimes too much but with a correct result)
1297 // it fixes also ^[0-9]
1298 while(left >= 1 && substring(theText, left-1, 1) == "^")
1301 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1303 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1305 ch = str2chr(theText, left-1);
1306 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1309 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1311 ch = str2chr(theText, left-2);
1312 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1314 ch = str2chr(theText, left-1);
1315 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1324 string find_last_color_code(string s)
1326 int start = strstrofs(s, "^", 0);
1327 if (start == -1) // no caret found
1329 int len = strlen(s)-1;
1331 for(i = len; i >= start; --i)
1333 if(substring(s, i, 1) != "^")
1337 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1340 // check if carets aren't all escaped
1344 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1345 return substring(s, i, 2);
1348 if(substring(s, i+1, 1) == "x")
1349 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1350 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1351 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1352 return substring(s, i, 5);
1354 i -= carets; // this also skips one char before the carets
1360 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1366 s = getWrappedLine_remaining;
1370 getWrappedLine_remaining = string_null;
1371 return s; // the line has no size ANYWAY, nothing would be displayed.
1374 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1375 if(cantake > 0 && cantake < strlen(s))
1378 while(take > 0 && substring(s, take, 1) != " ")
1382 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1383 if(getWrappedLine_remaining == "")
1384 getWrappedLine_remaining = string_null;
1385 else if (tw("^7", theFontSize) == 0)
1386 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1387 return substring(s, 0, cantake);
1391 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1392 if(getWrappedLine_remaining == "")
1393 getWrappedLine_remaining = string_null;
1394 else if (tw("^7", theFontSize) == 0)
1395 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1396 return substring(s, 0, take);
1401 getWrappedLine_remaining = string_null;
1406 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1412 s = getWrappedLine_remaining;
1416 getWrappedLine_remaining = string_null;
1417 return s; // the line has no size ANYWAY, nothing would be displayed.
1420 cantake = textLengthUpToLength(s, w, tw);
1421 if(cantake > 0 && cantake < strlen(s))
1424 while(take > 0 && substring(s, take, 1) != " ")
1428 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1429 if(getWrappedLine_remaining == "")
1430 getWrappedLine_remaining = string_null;
1431 else if (tw("^7") == 0)
1432 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1433 return substring(s, 0, cantake);
1437 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1438 if(getWrappedLine_remaining == "")
1439 getWrappedLine_remaining = string_null;
1440 else if (tw("^7") == 0)
1441 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1442 return substring(s, 0, take);
1447 getWrappedLine_remaining = string_null;
1452 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1454 if(tw(theText, theFontSize) <= maxWidth)
1457 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1460 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1462 if(tw(theText) <= maxWidth)
1465 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1468 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1470 string subpattern, subpattern2, subpattern3, subpattern4;
1471 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1473 subpattern2 = ",teams,";
1475 subpattern2 = ",noteams,";
1477 subpattern3 = ",teamspawns,";
1479 subpattern3 = ",noteamspawns,";
1480 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1481 subpattern4 = ",race,";
1483 subpattern4 = string_null;
1485 if(substring(pattern, 0, 1) == "-")
1487 pattern = substring(pattern, 1, strlen(pattern) - 1);
1488 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1490 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1492 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1494 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1499 if(substring(pattern, 0, 1) == "+")
1500 pattern = substring(pattern, 1, strlen(pattern) - 1);
1501 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1502 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1503 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1507 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1514 void shuffle(float n, swapfunc_t swap, entity pass)
1517 for(i = 1; i < n; ++i)
1519 // swap i-th item at a random position from 0 to i
1520 // proof for even distribution:
1523 // item n+1 gets at any position with chance 1/(n+1)
1524 // all others will get their 1/n chance reduced by factor n/(n+1)
1525 // to be on place n+1, their chance will be 1/(n+1)
1526 // 1/n * n/(n+1) = 1/(n+1)
1528 j = floor(random() * (i + 1));
1534 string substring_range(string s, float b, float e)
1536 return substring(s, b, e - b);
1539 string swapwords(string str, float i, float j)
1542 string s1, s2, s3, s4, s5;
1543 float si, ei, sj, ej, s0, en;
1544 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1545 si = argv_start_index(i);
1546 sj = argv_start_index(j);
1547 ei = argv_end_index(i);
1548 ej = argv_end_index(j);
1549 s0 = argv_start_index(0);
1550 en = argv_end_index(n-1);
1551 s1 = substring_range(str, s0, si);
1552 s2 = substring_range(str, si, ei);
1553 s3 = substring_range(str, ei, sj);
1554 s4 = substring_range(str, sj, ej);
1555 s5 = substring_range(str, ej, en);
1556 return strcat(s1, s4, s3, s2, s5);
1559 string _shufflewords_str;
1560 void _shufflewords_swapfunc(float i, float j, entity pass)
1562 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1564 string shufflewords(string str)
1567 _shufflewords_str = str;
1568 n = tokenizebyseparator(str, " ");
1569 shuffle(n, _shufflewords_swapfunc, world);
1570 str = _shufflewords_str;
1571 _shufflewords_str = string_null;
1575 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1591 // actually, every number solves the equation!
1602 if(a > 0) // put the smaller solution first
1604 v.x = ((-b)-D) / (2*a);
1605 v.y = ((-b)+D) / (2*a);
1609 v.x = (-b+D) / (2*a);
1610 v.y = (-b-D) / (2*a);
1616 // complex solutions!
1629 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1633 // make origin and speed relative
1638 // now solve for ret, ret normalized:
1639 // eorg + t * evel == t * ret * spd
1640 // or, rather, solve for t:
1641 // |eorg + t * evel| == t * spd
1642 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1643 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1644 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1645 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1646 // q = (eorg * eorg) / (evel * evel - spd * spd)
1647 if(!solution.z) // no real solution
1650 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1651 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1652 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1653 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1654 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1655 // spd < |evel| * sin angle(evel, eorg)
1658 else if(solution.x > 0)
1660 // both solutions > 0: take the smaller one
1661 // happens if p < 0 and q > 0
1662 ret = normalize(eorg + solution.x * evel);
1664 else if(solution.y > 0)
1666 // one solution > 0: take the larger one
1667 // happens if q < 0 or q == 0 and p < 0
1668 ret = normalize(eorg + solution.y * evel);
1672 // no solution > 0: reject
1673 // happens if p > 0 and q >= 0
1674 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1675 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1680 // "Enemy is moving away from me at more than spd"
1684 // NOTE: we always got a solution if spd > |evel|
1686 if(newton_style == 2)
1687 ret = normalize(ret * spd + myvel);
1692 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1697 if(newton_style == 2)
1699 // true Newtonian projectiles with automatic aim adjustment
1701 // solve: |outspeed * mydir - myvel| = spd
1702 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1703 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1707 // myvel^2 - (mydir * myvel)^2 > spd^2
1708 // velocity without mydir component > spd
1709 // fire at smallest possible spd that works?
1710 // |(mydir * myvel) * myvel - myvel| = spd
1712 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1716 outspeed = solution.y; // the larger one
1719 //outspeed = 0; // slowest possible shot
1720 outspeed = solution.x; // the real part (that is, the average!)
1721 //dprint("impossible shot, adjusting\n");
1724 outspeed = bound(spd * mi, outspeed, spd * ma);
1725 return mydir * outspeed;
1729 return myvel + spd * mydir;
1732 float compressShotOrigin(vector v)
1736 y = rint(v.y * 4) + 128;
1737 z = rint(v.z * 4) + 128;
1738 if(x > 255 || x < 0)
1740 print("shot origin ", vtos(v), " x out of bounds\n");
1741 x = bound(0, x, 255);
1743 if(y > 255 || y < 0)
1745 print("shot origin ", vtos(v), " y out of bounds\n");
1746 y = bound(0, y, 255);
1748 if(z > 255 || z < 0)
1750 print("shot origin ", vtos(v), " z out of bounds\n");
1751 z = bound(0, z, 255);
1753 return x * 0x10000 + y * 0x100 + z;
1755 vector decompressShotOrigin(int f)
1758 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1759 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1760 v.z = ((f & 0xFF) - 128) / 4;
1764 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1766 float start, end, root, child;
1769 start = floor((n - 2) / 2);
1772 // siftdown(start, count-1);
1774 while(root * 2 + 1 <= n-1)
1776 child = root * 2 + 1;
1778 if(cmp(child, child+1, pass) < 0)
1780 if(cmp(root, child, pass) < 0)
1782 swap(root, child, pass);
1798 // siftdown(0, end);
1800 while(root * 2 + 1 <= end)
1802 child = root * 2 + 1;
1803 if(child < end && cmp(child, child+1, pass) < 0)
1805 if(cmp(root, child, pass) < 0)
1807 swap(root, child, pass);
1817 void RandomSelection_Init()
1819 RandomSelection_totalweight = 0;
1820 RandomSelection_chosen_ent = world;
1821 RandomSelection_chosen_float = 0;
1822 RandomSelection_chosen_string = string_null;
1823 RandomSelection_best_priority = -1;
1825 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1827 if(priority > RandomSelection_best_priority)
1829 RandomSelection_best_priority = priority;
1830 RandomSelection_chosen_ent = e;
1831 RandomSelection_chosen_float = f;
1832 RandomSelection_chosen_string = s;
1833 RandomSelection_totalweight = weight;
1835 else if(priority == RandomSelection_best_priority)
1837 RandomSelection_totalweight += weight;
1838 if(random() * RandomSelection_totalweight <= weight)
1840 RandomSelection_chosen_ent = e;
1841 RandomSelection_chosen_float = f;
1842 RandomSelection_chosen_string = s;
1848 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1850 // NOTE: we'll always choose the SMALLER value...
1851 float healthdamage, armordamage, armorideal;
1852 if (deathtype == DEATH_DROWN) // Why should armor help here...
1855 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1856 armordamage = a + (h - 1); // damage we can take if we could use more armor
1857 armorideal = healthdamage * armorblock;
1859 if(armordamage < healthdamage)
1872 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1875 if (deathtype == DEATH_DROWN) // Why should armor help here...
1877 v.y = bound(0, damage * armorblock, a); // save
1878 v.x = bound(0, damage - v.y, damage); // take
1884 string getcurrentmod()
1888 m = cvar_string("fs_gamedir");
1889 n = tokenize_console(m);
1900 int v = ReadShort() * 256; // note: this is signed
1901 v += ReadByte(); // note: this is unsigned
1904 vector ReadInt48_t()
1907 v.x = ReadInt24_t();
1908 v.y = ReadInt24_t();
1912 vector ReadInt72_t()
1915 v.x = ReadInt24_t();
1916 v.y = ReadInt24_t();
1917 v.z = ReadInt24_t();
1921 void WriteInt24_t(float dst, float val)
1924 WriteShort(dst, (v = floor(val / 256)));
1925 WriteByte(dst, val - v * 256); // 0..255
1927 void WriteInt48_t(float dst, vector val)
1929 WriteInt24_t(dst, val.x);
1930 WriteInt24_t(dst, val.y);
1932 void WriteInt72_t(float dst, vector val)
1934 WriteInt24_t(dst, val.x);
1935 WriteInt24_t(dst, val.y);
1936 WriteInt24_t(dst, val.z);
1941 float float2range11(float f)
1943 // continuous function mapping all reals into -1..1
1944 return f / (fabs(f) + 1);
1947 float float2range01(float f)
1949 // continuous function mapping all reals into 0..1
1950 return 0.5 + 0.5 * float2range11(f);
1953 // from the GNU Scientific Library
1954 float gsl_ran_gaussian_lastvalue;
1955 float gsl_ran_gaussian_lastvalue_set;
1956 float gsl_ran_gaussian(float sigma)
1959 if(gsl_ran_gaussian_lastvalue_set)
1961 gsl_ran_gaussian_lastvalue_set = 0;
1962 return sigma * gsl_ran_gaussian_lastvalue;
1966 a = random() * 2 * M_PI;
1967 b = sqrt(-2 * log(random()));
1968 gsl_ran_gaussian_lastvalue = cos(a) * b;
1969 gsl_ran_gaussian_lastvalue_set = 1;
1970 return sigma * sin(a) * b;
1974 string car(string s)
1977 o = strstrofs(s, " ", 0);
1980 return substring(s, 0, o);
1982 string cdr(string s)
1985 o = strstrofs(s, " ", 0);
1988 return substring(s, o + 1, strlen(s) - (o + 1));
1990 float matchacl(string acl, string str)
1997 t = car(acl); acl = cdr(acl);
2000 if(substring(t, 0, 1) == "-")
2003 t = substring(t, 1, strlen(t) - 1);
2005 else if(substring(t, 0, 1) == "+")
2006 t = substring(t, 1, strlen(t) - 1);
2008 if(substring(t, -1, 1) == "*")
2010 t = substring(t, 0, strlen(t) - 1);
2011 s = substring(str, 0, strlen(t));
2023 float startsWith(string haystack, string needle)
2025 return substring(haystack, 0, strlen(needle)) == needle;
2027 float startsWithNocase(string haystack, string needle)
2029 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2032 string get_model_datafilename(string m, float sk, string fil)
2037 m = "models/player/*_";
2039 m = strcat(m, ftos(sk));
2042 return strcat(m, ".", fil);
2045 float get_model_parameters(string m, float sk)
2047 get_model_parameters_modelname = string_null;
2048 get_model_parameters_modelskin = -1;
2049 get_model_parameters_name = string_null;
2050 get_model_parameters_species = -1;
2051 get_model_parameters_sex = string_null;
2052 get_model_parameters_weight = -1;
2053 get_model_parameters_age = -1;
2054 get_model_parameters_desc = string_null;
2055 get_model_parameters_bone_upperbody = string_null;
2056 get_model_parameters_bone_weapon = string_null;
2057 for(int i = 0; i < MAX_AIM_BONES; ++i)
2059 get_model_parameters_bone_aim[i] = string_null;
2060 get_model_parameters_bone_aimweight[i] = 0;
2062 get_model_parameters_fixbone = 0;
2065 MUTATOR_CALLHOOK(ClearModelParams);
2071 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2072 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2076 if(substring(m, -4, -1) != ".txt")
2078 if(substring(m, -6, 1) != "_")
2080 sk = stof(substring(m, -5, 1));
2081 m = substring(m, 0, -7);
2084 string fn = get_model_datafilename(m, sk, "txt");
2085 int fh = fopen(fn, FILE_READ);
2089 fn = get_model_datafilename(m, sk, "txt");
2090 fh = fopen(fn, FILE_READ);
2095 get_model_parameters_modelname = m;
2096 get_model_parameters_modelskin = sk;
2098 while((s = fgets(fh)))
2101 break; // next lines will be description
2105 get_model_parameters_name = s;
2109 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2110 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2111 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2112 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2113 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2114 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2115 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2118 get_model_parameters_sex = s;
2120 get_model_parameters_weight = stof(s);
2122 get_model_parameters_age = stof(s);
2123 if(c == "description")
2124 get_model_parameters_description = s;
2125 if(c == "bone_upperbody")
2126 get_model_parameters_bone_upperbody = s;
2127 if(c == "bone_weapon")
2128 get_model_parameters_bone_weapon = s;
2130 MUTATOR_CALLHOOK(GetModelParams, c, s);
2132 for(int i = 0; i < MAX_AIM_BONES; ++i)
2133 if(c == strcat("bone_aim", ftos(i)))
2135 get_model_parameters_bone_aimweight[i] = stof(car(s));
2136 get_model_parameters_bone_aim[i] = cdr(s);
2139 get_model_parameters_fixbone = stof(s);
2142 while((s = fgets(fh)))
2144 if(get_model_parameters_desc)
2145 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2147 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2155 vector vec2(vector v)
2162 vector NearestPointOnBox(entity box, vector org)
2164 vector m1, m2, nearest;
2166 m1 = box.mins + box.origin;
2167 m2 = box.maxs + box.origin;
2169 nearest.x = bound(m1_x, org.x, m2_x);
2170 nearest.y = bound(m1_y, org.y, m2_y);
2171 nearest.z = bound(m1_z, org.z, m2_z);
2177 float vercmp_recursive(string v1, string v2)
2183 dot1 = strstrofs(v1, ".", 0);
2184 dot2 = strstrofs(v2, ".", 0);
2188 s1 = substring(v1, 0, dot1);
2192 s2 = substring(v2, 0, dot2);
2194 r = stof(s1) - stof(s2);
2198 r = strcasecmp(s1, s2);
2211 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2214 float vercmp(string v1, string v2)
2216 if(strcasecmp(v1, v2) == 0) // early out check
2225 return vercmp_recursive(v1, v2);
2228 float u8_strsize(string s)
2248 // translation helpers
2249 string language_filename(string s)
2254 if(fn == "" || fn == "dump")
2256 fn = strcat(s, ".", fn);
2257 if((fh = fopen(fn, FILE_READ)) >= 0)
2264 string CTX(string s)
2266 float p = strstrofs(s, "^", 0);
2269 return substring(s, p+1, -1);
2272 // x-encoding (encoding as zero length invisible string)
2273 const string XENCODE_2 = "xX";
2274 const string XENCODE_22 = "0123456789abcdefABCDEF";
2275 string xencode(int f)
2278 d = f % 22; f = floor(f / 22);
2279 c = f % 22; f = floor(f / 22);
2280 b = f % 22; f = floor(f / 22);
2281 a = f % 2; // f = floor(f / 2);
2284 substring(XENCODE_2, a, 1),
2285 substring(XENCODE_22, b, 1),
2286 substring(XENCODE_22, c, 1),
2287 substring(XENCODE_22, d, 1)
2290 float xdecode(string s)
2293 if(substring(s, 0, 1) != "^")
2297 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2298 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2299 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2300 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2301 if(a < 0 || b < 0 || c < 0 || d < 0)
2303 return ((a * 22 + b) * 22 + c) * 22 + d;
2306 int lowestbit(int f)
2317 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2319 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2322 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2325 // escape the string to make it safe for consoles
2326 string MakeConsoleSafe(string input)
2328 input = strreplace("\n", "", input);
2329 input = strreplace("\\", "\\\\", input);
2330 input = strreplace("$", "$$", input);
2331 input = strreplace("\"", "\\\"", input);
2336 // get true/false value of a string with multiple different inputs
2337 float InterpretBoolean(string input)
2339 switch(strtolower(input))
2351 default: return stof(input);
2357 entity ReadCSQCEntity()
2359 int f = ReadShort();
2362 return findfloat(world, entnum, f);
2366 float shutdown_running;
2371 void CSQC_Shutdown()
2377 if(shutdown_running)
2379 print("Recursive shutdown detected! Only restoring cvars...\n");
2383 shutdown_running = 1;
2386 cvar_settemp_restore(); // this must be done LAST, but in any case
2389 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2390 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2391 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2392 // this will use the value:
2394 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2395 // accuracy at x is 1/derivative, i.e.
2396 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2398 void WriteApproxPastTime(float dst, float t)
2400 float dt = time - t;
2402 // warning: this is approximate; do not resend when you don't have to!
2403 // be careful with sendflags here!
2404 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2407 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2410 dt = rint(bound(0, dt, 255));
2416 float ReadApproxPastTime()
2418 float dt = ReadByte();
2420 // map from range...PPROXPASTTIME_MAX / 256
2421 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2423 return servertime - dt;
2428 .float skeleton_bones_index;
2429 void Skeleton_SetBones(entity e)
2431 // set skeleton_bones to the total number of bones on the model
2432 if(e.skeleton_bones_index == e.modelindex)
2433 return; // same model, nothing to update
2436 skelindex = skel_create(e.modelindex);
2437 e.skeleton_bones = skel_get_numbones(skelindex);
2438 skel_delete(skelindex);
2439 e.skeleton_bones_index = e.modelindex;
2443 string to_execute_next_frame;
2444 void execute_next_frame()
2446 if(to_execute_next_frame)
2448 localcmd("\n", to_execute_next_frame, "\n");
2449 strunzone(to_execute_next_frame);
2450 to_execute_next_frame = string_null;
2453 void queue_to_execute_next_frame(string s)
2455 if(to_execute_next_frame)
2457 s = strcat(s, "\n", to_execute_next_frame);
2458 strunzone(to_execute_next_frame);
2460 to_execute_next_frame = strzone(s);
2463 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2466 ((( startspeedfactor + endspeedfactor - 2
2467 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2468 ) * x + startspeedfactor
2472 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2474 if(startspeedfactor < 0 || endspeedfactor < 0)
2478 // if this is the case, the possible zeros of the first derivative are outside
2480 We can calculate this condition as condition
2485 // better, see below:
2486 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2489 // if this is the case, the first derivative has no zeros at all
2490 float se = startspeedfactor + endspeedfactor;
2491 float s_e = startspeedfactor - endspeedfactor;
2492 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2495 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2496 // we also get s_e <= 6 - se
2497 // 3 * (se - 4)^2 + (6 - se)^2
2498 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2499 // Therefore, above "better" check works!
2503 // known good cases:
2511 // (3.5, [0.2..2.3])
2516 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2518 s + e - 2 == 0: no inflection
2521 0 < inflection < 1 if:
2522 0 < 2s + e - 3 < 3s + 3e - 6
2523 2s + e > 3 and 2e + s > 3
2526 0 < inflection < 1 if:
2527 0 > 2s + e - 3 > 3s + 3e - 6
2528 2s + e < 3 and 2e + s < 3
2530 Therefore: there is an inflection point iff:
2531 e outside (3 - s)/2 .. 3 - s*2
2533 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)
2537 .float FindConnectedComponent_processing;
2538 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2540 entity queue_start, queue_end;
2542 // we build a queue of to-be-processed entities.
2543 // queue_start is the next entity to be checked for neighbors
2544 // queue_end is the last entity added
2546 if(e.FindConnectedComponent_processing)
2547 error("recursion or broken cleanup");
2549 // start with a 1-element queue
2550 queue_start = queue_end = e;
2551 queue_end.(fld) = world;
2552 queue_end.FindConnectedComponent_processing = 1;
2554 // for each queued item:
2555 for (; queue_start; queue_start = queue_start.(fld))
2557 // find all neighbors of queue_start
2559 for(t = world; (t = nxt(t, queue_start, pass)); )
2561 if(t.FindConnectedComponent_processing)
2563 if(iscon(t, queue_start, pass))
2565 // it is connected? ADD IT. It will look for neighbors soon too.
2566 queue_end.(fld) = t;
2568 queue_end.(fld) = world;
2569 queue_end.FindConnectedComponent_processing = 1;
2575 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
2576 queue_start.FindConnectedComponent_processing = 0;
2580 vector combine_to_vector(float x, float y, float z)
2582 vector result; result_x = x; result_y = y; result_z = z;
2586 vector get_corner_position(entity box, float corner)
2590 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2591 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2592 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2593 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2594 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2595 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2596 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2597 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2598 default: return '0 0 0';
2603 // todo: this sucks, lets find a better way to do backtraces?
2604 void backtrace(string msg)
2608 dev = autocvar_developer;
2609 war = autocvar_prvm_backtraceforwarnings;
2611 dev = cvar("developer");
2612 war = cvar("prvm_backtraceforwarnings");
2614 cvar_set("developer", "1");
2615 cvar_set("prvm_backtraceforwarnings", "1");
2617 print("--- CUT HERE ---\nWARNING: ");
2620 remove(world); // isn't there any better way to cause a backtrace?
2621 print("\n--- CUT UNTIL HERE ---\n");
2622 cvar_set("developer", ftos(dev));
2623 cvar_set("prvm_backtraceforwarnings", ftos(war));
2626 // color code replace, place inside of sprintf and parse the string
2627 string CCR(string input)
2629 // See the autocvar declarations in util.qh for default values
2631 // foreground/normal colors
2632 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2633 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2634 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2635 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2638 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2639 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2640 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2642 // background colors
2643 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2644 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2648 vector vec3(float x, float y, float z)
2658 vector animfixfps(entity e, vector a, vector b)
2660 // multi-frame anim: keep as-is
2664 dur = frameduration(e.modelindex, a.x);
2668 dur = frameduration(e.modelindex, a.x);
2678 void dedicated_print(string input) // print(), but only print if the server is not local
2680 if(server_is_dedicated) { print(input); }
2685 float Announcer_PickNumber(float type, float num)
2693 case 10: return ANNCE_NUM_GAMESTART_10;
2694 case 9: return ANNCE_NUM_GAMESTART_9;
2695 case 8: return ANNCE_NUM_GAMESTART_8;
2696 case 7: return ANNCE_NUM_GAMESTART_7;
2697 case 6: return ANNCE_NUM_GAMESTART_6;
2698 case 5: return ANNCE_NUM_GAMESTART_5;
2699 case 4: return ANNCE_NUM_GAMESTART_4;
2700 case 3: return ANNCE_NUM_GAMESTART_3;
2701 case 2: return ANNCE_NUM_GAMESTART_2;
2702 case 1: return ANNCE_NUM_GAMESTART_1;
2710 case 10: return ANNCE_NUM_IDLE_10;
2711 case 9: return ANNCE_NUM_IDLE_9;
2712 case 8: return ANNCE_NUM_IDLE_8;
2713 case 7: return ANNCE_NUM_IDLE_7;
2714 case 6: return ANNCE_NUM_IDLE_6;
2715 case 5: return ANNCE_NUM_IDLE_5;
2716 case 4: return ANNCE_NUM_IDLE_4;
2717 case 3: return ANNCE_NUM_IDLE_3;
2718 case 2: return ANNCE_NUM_IDLE_2;
2719 case 1: return ANNCE_NUM_IDLE_1;
2727 case 10: return ANNCE_NUM_KILL_10;
2728 case 9: return ANNCE_NUM_KILL_9;
2729 case 8: return ANNCE_NUM_KILL_8;
2730 case 7: return ANNCE_NUM_KILL_7;
2731 case 6: return ANNCE_NUM_KILL_6;
2732 case 5: return ANNCE_NUM_KILL_5;
2733 case 4: return ANNCE_NUM_KILL_4;
2734 case 3: return ANNCE_NUM_KILL_3;
2735 case 2: return ANNCE_NUM_KILL_2;
2736 case 1: return ANNCE_NUM_KILL_1;
2744 case 10: return ANNCE_NUM_RESPAWN_10;
2745 case 9: return ANNCE_NUM_RESPAWN_9;
2746 case 8: return ANNCE_NUM_RESPAWN_8;
2747 case 7: return ANNCE_NUM_RESPAWN_7;
2748 case 6: return ANNCE_NUM_RESPAWN_6;
2749 case 5: return ANNCE_NUM_RESPAWN_5;
2750 case 4: return ANNCE_NUM_RESPAWN_4;
2751 case 3: return ANNCE_NUM_RESPAWN_3;
2752 case 2: return ANNCE_NUM_RESPAWN_2;
2753 case 1: return ANNCE_NUM_RESPAWN_1;
2757 case CNT_ROUNDSTART:
2761 case 10: return ANNCE_NUM_ROUNDSTART_10;
2762 case 9: return ANNCE_NUM_ROUNDSTART_9;
2763 case 8: return ANNCE_NUM_ROUNDSTART_8;
2764 case 7: return ANNCE_NUM_ROUNDSTART_7;
2765 case 6: return ANNCE_NUM_ROUNDSTART_6;
2766 case 5: return ANNCE_NUM_ROUNDSTART_5;
2767 case 4: return ANNCE_NUM_ROUNDSTART_4;
2768 case 3: return ANNCE_NUM_ROUNDSTART_3;
2769 case 2: return ANNCE_NUM_ROUNDSTART_2;
2770 case 1: return ANNCE_NUM_ROUNDSTART_1;
2778 case 10: return ANNCE_NUM_10;
2779 case 9: return ANNCE_NUM_9;
2780 case 8: return ANNCE_NUM_8;
2781 case 7: return ANNCE_NUM_7;
2782 case 6: return ANNCE_NUM_6;
2783 case 5: return ANNCE_NUM_5;
2784 case 4: return ANNCE_NUM_4;
2785 case 3: return ANNCE_NUM_3;
2786 case 2: return ANNCE_NUM_2;
2787 case 1: return ANNCE_NUM_1;
2792 return NOTIF_ABORT; // abort sending if none of these numbers were right
2797 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2799 switch(nativecontents)
2804 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2806 return DPCONTENTS_WATER;
2808 return DPCONTENTS_SLIME;
2810 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2812 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2817 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2819 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2820 return CONTENT_SOLID;
2821 if(supercontents & DPCONTENTS_SKY)
2823 if(supercontents & DPCONTENTS_LAVA)
2824 return CONTENT_LAVA;
2825 if(supercontents & DPCONTENTS_SLIME)
2826 return CONTENT_SLIME;
2827 if(supercontents & DPCONTENTS_WATER)
2828 return CONTENT_WATER;
2829 return CONTENT_EMPTY;
2833 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2836 (c - 2 * b + a) * (t * t) +
2841 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2844 (c - 2 * b + a) * (2 * t) +