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 // x-encoding (encoding as zero length invisible string)
2249 const string XENCODE_2 = "xX";
2250 const string XENCODE_22 = "0123456789abcdefABCDEF";
2251 string xencode(int f)
2254 d = f % 22; f = floor(f / 22);
2255 c = f % 22; f = floor(f / 22);
2256 b = f % 22; f = floor(f / 22);
2257 a = f % 2; // f = floor(f / 2);
2260 substring(XENCODE_2, a, 1),
2261 substring(XENCODE_22, b, 1),
2262 substring(XENCODE_22, c, 1),
2263 substring(XENCODE_22, d, 1)
2266 float xdecode(string s)
2269 if(substring(s, 0, 1) != "^")
2273 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2274 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2275 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2276 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2277 if(a < 0 || b < 0 || c < 0 || d < 0)
2279 return ((a * 22 + b) * 22 + c) * 22 + d;
2282 int lowestbit(int f)
2293 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2295 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2298 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2301 // escape the string to make it safe for consoles
2302 string MakeConsoleSafe(string input)
2304 input = strreplace("\n", "", input);
2305 input = strreplace("\\", "\\\\", input);
2306 input = strreplace("$", "$$", input);
2307 input = strreplace("\"", "\\\"", input);
2312 // get true/false value of a string with multiple different inputs
2313 float InterpretBoolean(string input)
2315 switch(strtolower(input))
2327 default: return stof(input);
2333 entity ReadCSQCEntity()
2335 int f = ReadShort();
2338 return findfloat(world, entnum, f);
2342 float shutdown_running;
2347 void CSQC_Shutdown()
2353 if(shutdown_running)
2355 print("Recursive shutdown detected! Only restoring cvars...\n");
2359 shutdown_running = 1;
2362 cvar_settemp_restore(); // this must be done LAST, but in any case
2365 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2366 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2367 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2368 // this will use the value:
2370 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2371 // accuracy at x is 1/derivative, i.e.
2372 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2374 void WriteApproxPastTime(float dst, float t)
2376 float dt = time - t;
2378 // warning: this is approximate; do not resend when you don't have to!
2379 // be careful with sendflags here!
2380 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2383 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2386 dt = rint(bound(0, dt, 255));
2392 float ReadApproxPastTime()
2394 float dt = ReadByte();
2396 // map from range...PPROXPASTTIME_MAX / 256
2397 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2399 return servertime - dt;
2404 .float skeleton_bones_index;
2405 void Skeleton_SetBones(entity e)
2407 // set skeleton_bones to the total number of bones on the model
2408 if(e.skeleton_bones_index == e.modelindex)
2409 return; // same model, nothing to update
2412 skelindex = skel_create(e.modelindex);
2413 e.skeleton_bones = skel_get_numbones(skelindex);
2414 skel_delete(skelindex);
2415 e.skeleton_bones_index = e.modelindex;
2419 string to_execute_next_frame;
2420 void execute_next_frame()
2422 if(to_execute_next_frame)
2424 localcmd("\n", to_execute_next_frame, "\n");
2425 strunzone(to_execute_next_frame);
2426 to_execute_next_frame = string_null;
2429 void queue_to_execute_next_frame(string s)
2431 if(to_execute_next_frame)
2433 s = strcat(s, "\n", to_execute_next_frame);
2434 strunzone(to_execute_next_frame);
2436 to_execute_next_frame = strzone(s);
2439 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2442 ((( startspeedfactor + endspeedfactor - 2
2443 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2444 ) * x + startspeedfactor
2448 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2450 if(startspeedfactor < 0 || endspeedfactor < 0)
2454 // if this is the case, the possible zeros of the first derivative are outside
2456 We can calculate this condition as condition
2461 // better, see below:
2462 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2465 // if this is the case, the first derivative has no zeros at all
2466 float se = startspeedfactor + endspeedfactor;
2467 float s_e = startspeedfactor - endspeedfactor;
2468 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2471 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2472 // we also get s_e <= 6 - se
2473 // 3 * (se - 4)^2 + (6 - se)^2
2474 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2475 // Therefore, above "better" check works!
2479 // known good cases:
2487 // (3.5, [0.2..2.3])
2492 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2494 s + e - 2 == 0: no inflection
2497 0 < inflection < 1 if:
2498 0 < 2s + e - 3 < 3s + 3e - 6
2499 2s + e > 3 and 2e + s > 3
2502 0 < inflection < 1 if:
2503 0 > 2s + e - 3 > 3s + 3e - 6
2504 2s + e < 3 and 2e + s < 3
2506 Therefore: there is an inflection point iff:
2507 e outside (3 - s)/2 .. 3 - s*2
2509 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)
2513 .float FindConnectedComponent_processing;
2514 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2516 entity queue_start, queue_end;
2518 // we build a queue of to-be-processed entities.
2519 // queue_start is the next entity to be checked for neighbors
2520 // queue_end is the last entity added
2522 if(e.FindConnectedComponent_processing)
2523 error("recursion or broken cleanup");
2525 // start with a 1-element queue
2526 queue_start = queue_end = e;
2527 queue_end.(fld) = world;
2528 queue_end.FindConnectedComponent_processing = 1;
2530 // for each queued item:
2531 for (; queue_start; queue_start = queue_start.(fld))
2533 // find all neighbors of queue_start
2535 for(t = world; (t = nxt(t, queue_start, pass)); )
2537 if(t.FindConnectedComponent_processing)
2539 if(iscon(t, queue_start, pass))
2541 // it is connected? ADD IT. It will look for neighbors soon too.
2542 queue_end.(fld) = t;
2544 queue_end.(fld) = world;
2545 queue_end.FindConnectedComponent_processing = 1;
2551 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
2552 queue_start.FindConnectedComponent_processing = 0;
2556 vector combine_to_vector(float x, float y, float z)
2558 vector result; result_x = x; result_y = y; result_z = z;
2562 vector get_corner_position(entity box, float corner)
2566 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2567 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2568 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2569 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2570 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2571 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2572 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2573 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2574 default: return '0 0 0';
2579 // todo: this sucks, lets find a better way to do backtraces?
2580 void backtrace(string msg)
2584 dev = autocvar_developer;
2585 war = autocvar_prvm_backtraceforwarnings;
2587 dev = cvar("developer");
2588 war = cvar("prvm_backtraceforwarnings");
2590 cvar_set("developer", "1");
2591 cvar_set("prvm_backtraceforwarnings", "1");
2593 print("--- CUT HERE ---\nWARNING: ");
2596 remove(world); // isn't there any better way to cause a backtrace?
2597 print("\n--- CUT UNTIL HERE ---\n");
2598 cvar_set("developer", ftos(dev));
2599 cvar_set("prvm_backtraceforwarnings", ftos(war));
2602 // color code replace, place inside of sprintf and parse the string
2603 string CCR(string input)
2605 // See the autocvar declarations in util.qh for default values
2607 // foreground/normal colors
2608 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2609 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2610 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2611 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2614 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2615 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2616 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2618 // background colors
2619 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2620 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2624 vector vec3(float x, float y, float z)
2634 vector animfixfps(entity e, vector a, vector b)
2636 // multi-frame anim: keep as-is
2640 dur = frameduration(e.modelindex, a.x);
2644 dur = frameduration(e.modelindex, a.x);
2654 void dedicated_print(string input) // print(), but only print if the server is not local
2656 if(server_is_dedicated) { print(input); }
2661 float Announcer_PickNumber(float type, float num)
2669 case 10: return ANNCE_NUM_GAMESTART_10;
2670 case 9: return ANNCE_NUM_GAMESTART_9;
2671 case 8: return ANNCE_NUM_GAMESTART_8;
2672 case 7: return ANNCE_NUM_GAMESTART_7;
2673 case 6: return ANNCE_NUM_GAMESTART_6;
2674 case 5: return ANNCE_NUM_GAMESTART_5;
2675 case 4: return ANNCE_NUM_GAMESTART_4;
2676 case 3: return ANNCE_NUM_GAMESTART_3;
2677 case 2: return ANNCE_NUM_GAMESTART_2;
2678 case 1: return ANNCE_NUM_GAMESTART_1;
2686 case 10: return ANNCE_NUM_IDLE_10;
2687 case 9: return ANNCE_NUM_IDLE_9;
2688 case 8: return ANNCE_NUM_IDLE_8;
2689 case 7: return ANNCE_NUM_IDLE_7;
2690 case 6: return ANNCE_NUM_IDLE_6;
2691 case 5: return ANNCE_NUM_IDLE_5;
2692 case 4: return ANNCE_NUM_IDLE_4;
2693 case 3: return ANNCE_NUM_IDLE_3;
2694 case 2: return ANNCE_NUM_IDLE_2;
2695 case 1: return ANNCE_NUM_IDLE_1;
2703 case 10: return ANNCE_NUM_KILL_10;
2704 case 9: return ANNCE_NUM_KILL_9;
2705 case 8: return ANNCE_NUM_KILL_8;
2706 case 7: return ANNCE_NUM_KILL_7;
2707 case 6: return ANNCE_NUM_KILL_6;
2708 case 5: return ANNCE_NUM_KILL_5;
2709 case 4: return ANNCE_NUM_KILL_4;
2710 case 3: return ANNCE_NUM_KILL_3;
2711 case 2: return ANNCE_NUM_KILL_2;
2712 case 1: return ANNCE_NUM_KILL_1;
2720 case 10: return ANNCE_NUM_RESPAWN_10;
2721 case 9: return ANNCE_NUM_RESPAWN_9;
2722 case 8: return ANNCE_NUM_RESPAWN_8;
2723 case 7: return ANNCE_NUM_RESPAWN_7;
2724 case 6: return ANNCE_NUM_RESPAWN_6;
2725 case 5: return ANNCE_NUM_RESPAWN_5;
2726 case 4: return ANNCE_NUM_RESPAWN_4;
2727 case 3: return ANNCE_NUM_RESPAWN_3;
2728 case 2: return ANNCE_NUM_RESPAWN_2;
2729 case 1: return ANNCE_NUM_RESPAWN_1;
2733 case CNT_ROUNDSTART:
2737 case 10: return ANNCE_NUM_ROUNDSTART_10;
2738 case 9: return ANNCE_NUM_ROUNDSTART_9;
2739 case 8: return ANNCE_NUM_ROUNDSTART_8;
2740 case 7: return ANNCE_NUM_ROUNDSTART_7;
2741 case 6: return ANNCE_NUM_ROUNDSTART_6;
2742 case 5: return ANNCE_NUM_ROUNDSTART_5;
2743 case 4: return ANNCE_NUM_ROUNDSTART_4;
2744 case 3: return ANNCE_NUM_ROUNDSTART_3;
2745 case 2: return ANNCE_NUM_ROUNDSTART_2;
2746 case 1: return ANNCE_NUM_ROUNDSTART_1;
2754 case 10: return ANNCE_NUM_10;
2755 case 9: return ANNCE_NUM_9;
2756 case 8: return ANNCE_NUM_8;
2757 case 7: return ANNCE_NUM_7;
2758 case 6: return ANNCE_NUM_6;
2759 case 5: return ANNCE_NUM_5;
2760 case 4: return ANNCE_NUM_4;
2761 case 3: return ANNCE_NUM_3;
2762 case 2: return ANNCE_NUM_2;
2763 case 1: return ANNCE_NUM_1;
2768 return NOTIF_ABORT; // abort sending if none of these numbers were right
2773 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2775 switch(nativecontents)
2780 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2782 return DPCONTENTS_WATER;
2784 return DPCONTENTS_SLIME;
2786 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2788 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2793 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2795 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2796 return CONTENT_SOLID;
2797 if(supercontents & DPCONTENTS_SKY)
2799 if(supercontents & DPCONTENTS_LAVA)
2800 return CONTENT_LAVA;
2801 if(supercontents & DPCONTENTS_SLIME)
2802 return CONTENT_SLIME;
2803 if(supercontents & DPCONTENTS_WATER)
2804 return CONTENT_WATER;
2805 return CONTENT_EMPTY;
2809 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2812 (c - 2 * b + a) * (t * t) +
2817 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2820 (c - 2 * b + a) * (2 * t) +