2 #include "../dpdefs/csprogsdefs.qh"
3 #include "../client/defs.qh"
4 #include "constants.qh"
5 #include "../warpzonelib/mathlib.qh"
8 #include "notifications.qh"
9 #include "deathtypes.qh"
12 #include "../dpdefs/progsdefs.qh"
13 #include "../dpdefs/dpextensions.qh"
14 #include "../server/sys-post.qh"
15 #include "../warpzonelib/mathlib.qh"
16 #include "constants.qh"
18 #include "../server/autocvars.qh"
19 #include "../server/defs.qh"
20 #include "notifications.qh"
21 #include "deathtypes.qh"
25 string wordwrap_buffer;
27 void wordwrap_buffer_put(string s)
29 wordwrap_buffer = strcat(wordwrap_buffer, s);
32 string wordwrap(string s, float l)
36 wordwrap_cb(s, l, wordwrap_buffer_put);
44 void wordwrap_buffer_sprint(string s)
46 wordwrap_buffer = strcat(wordwrap_buffer, s);
49 sprint(self, wordwrap_buffer);
54 void wordwrap_sprint(string s, float l)
57 wordwrap_cb(s, l, wordwrap_buffer_sprint);
58 if(wordwrap_buffer != "")
59 sprint(self, strcat(wordwrap_buffer, "\n"));
67 string draw_UseSkinFor(string pic)
69 if(substring(pic, 0, 1) == "/")
70 return substring(pic, 1, strlen(pic)-1);
72 return strcat(draw_currentSkin, "/", pic);
76 string unescape(string in)
81 // but it doesn't seem to be necessary in my tests at least
86 for(i = 0; i < len; ++i)
88 s = substring(in, i, 1);
91 s = substring(in, i+1, 1);
93 str = strcat(str, "\n");
95 str = strcat(str, "\\");
97 str = strcat(str, substring(in, i, 2));
100 str = strcat(str, s);
107 void wordwrap_cb(string s, float l, void(string) callback)
110 float lleft, i, j, wlen;
114 for (i = 0;i < strlen(s);++i)
116 if (substring(s, i, 2) == "\\n")
122 else if (substring(s, i, 1) == "\n")
127 else if (substring(s, i, 1) == " ")
137 for (j = i+1;j < strlen(s);++j)
138 // ^^ this skips over the first character of a word, which
139 // is ALWAYS part of the word
140 // this is safe since if i+1 == strlen(s), i will become
141 // strlen(s)-1 at the end of this block and the function
142 // will terminate. A space can't be the first character we
143 // read here, and neither can a \n be the start, since these
144 // two cases have been handled above.
146 c = substring(s, j, 1);
153 // we need to keep this tempstring alive even if substring is
154 // called repeatedly, so call strcat even though we're not
164 callback(substring(s, i, wlen));
165 lleft = lleft - wlen;
172 float dist_point_line(vector p, vector l0, vector ldir)
174 ldir = normalize(ldir);
176 // remove the component in line direction
177 p = p - (p * ldir) * ldir;
179 // vlen of the remaining vector
183 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
212 float median(float a, float b, float c)
215 return bound(a, b, c);
216 return bound(c, b, a);
219 // converts a number to a string with the indicated number of decimals
220 // works for up to 10 decimals!
221 string ftos_decimals(float number, float decimals)
223 // inhibit stupid negative zero
226 // we have sprintf...
227 return sprintf("%.*f", decimals, number);
230 vector colormapPaletteColor(float c, float isPants)
234 case 0: return '1.000000 1.000000 1.000000';
235 case 1: return '1.000000 0.333333 0.000000';
236 case 2: return '0.000000 1.000000 0.501961';
237 case 3: return '0.000000 1.000000 0.000000';
238 case 4: return '1.000000 0.000000 0.000000';
239 case 5: return '0.000000 0.666667 1.000000';
240 case 6: return '0.000000 1.000000 1.000000';
241 case 7: return '0.501961 1.000000 0.000000';
242 case 8: return '0.501961 0.000000 1.000000';
243 case 9: return '1.000000 0.000000 1.000000';
244 case 10: return '1.000000 0.000000 0.501961';
245 case 11: return '0.000000 0.000000 1.000000';
246 case 12: return '1.000000 1.000000 0.000000';
247 case 13: return '0.000000 0.333333 1.000000';
248 case 14: return '1.000000 0.666667 0.000000';
252 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
253 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
254 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
257 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
258 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
259 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
260 default: return '0.000 0.000 0.000';
264 // unzone the string, and return it as tempstring. Safe to be called on string_null
265 string fstrunzone(string s)
275 bool fexists(string f)
277 int fh = fopen(f, FILE_READ);
284 // Databases (hash tables)
285 const float DB_BUCKETS = 8192;
286 void db_save(float db, string pFilename)
289 fh = fopen(pFilename, FILE_WRITE);
292 print(strcat("^1Can't write DB to ", pFilename));
296 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
297 for(i = 0; i < n; ++i)
298 fputs(fh, strcat(bufstr_get(db, i), "\n"));
307 float db_load(string pFilename)
309 float db, fh, i, j, n;
314 fh = fopen(pFilename, FILE_READ);
318 if(stof(l) == DB_BUCKETS)
321 while((l = fgets(fh)))
324 bufstr_set(db, i, l);
330 // different count of buckets, or a dump?
331 // need to reorganize the database then (SLOW)
333 // note: we also parse the first line (l) in case the DB file is
334 // missing the bucket count
337 n = tokenizebyseparator(l, "\\");
338 for(j = 2; j < n; j += 2)
339 db_put(db, argv(j-1), uri_unescape(argv(j)));
341 while((l = fgets(fh)));
347 void db_dump(float db, string pFilename)
349 float fh, i, j, n, m;
350 fh = fopen(pFilename, FILE_WRITE);
352 error(strcat("Can't dump DB to ", pFilename));
355 for(i = 0; i < n; ++i)
357 m = tokenizebyseparator(bufstr_get(db, i), "\\");
358 for(j = 2; j < m; j += 2)
359 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
364 void db_close(float db)
369 string db_get(float db, string pKey)
372 h = crc16(false, pKey) % DB_BUCKETS;
373 return uri_unescape(infoget(bufstr_get(db, h), pKey));
376 void db_put(float db, string pKey, string pValue)
379 h = crc16(false, pKey) % DB_BUCKETS;
380 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
387 db = db_load("foo.db");
388 print("LOADED. FILL...\n");
389 for(i = 0; i < DB_BUCKETS; ++i)
390 db_put(db, ftos(random()), "X");
391 print("FILLED. SAVE...\n");
392 db_save(db, "foo.db");
393 print("SAVED. CLOSE...\n");
398 // Multiline text file buffers
399 float buf_load(string pFilename)
406 fh = fopen(pFilename, FILE_READ);
413 while((l = fgets(fh)))
415 bufstr_set(buf, i, l);
422 void buf_save(float buf, string pFilename)
425 fh = fopen(pFilename, FILE_WRITE);
427 error(strcat("Can't write buf to ", pFilename));
428 n = buf_getsize(buf);
429 for(i = 0; i < n; ++i)
430 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
434 string format_time(float seconds)
436 float days, hours, minutes;
437 seconds = floor(seconds + 0.5);
438 days = floor(seconds / 864000);
439 seconds -= days * 864000;
440 hours = floor(seconds / 36000);
441 seconds -= hours * 36000;
442 minutes = floor(seconds / 600);
443 seconds -= minutes * 600;
445 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
447 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
450 string mmsss(float tenths)
454 tenths = floor(tenths + 0.5);
455 minutes = floor(tenths / 600);
456 tenths -= minutes * 600;
457 s = ftos(1000 + tenths);
458 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
461 string mmssss(float hundredths)
465 hundredths = floor(hundredths + 0.5);
466 minutes = floor(hundredths / 6000);
467 hundredths -= minutes * 6000;
468 s = ftos(10000 + hundredths);
469 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
472 string ScoreString(int pFlags, float pValue)
477 pValue = floor(pValue + 0.5); // round
479 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
481 else if(pFlags & SFL_RANK)
483 valstr = ftos(pValue);
485 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
486 valstr = strcat(valstr, "th");
487 else if(substring(valstr, l - 1, 1) == "1")
488 valstr = strcat(valstr, "st");
489 else if(substring(valstr, l - 1, 1) == "2")
490 valstr = strcat(valstr, "nd");
491 else if(substring(valstr, l - 1, 1) == "3")
492 valstr = strcat(valstr, "rd");
494 valstr = strcat(valstr, "th");
496 else if(pFlags & SFL_TIME)
497 valstr = TIME_ENCODED_TOSTRING(pValue);
499 valstr = ftos(pValue);
504 float dotproduct(vector a, vector b)
506 return a.x * b.x + a.y * b.y + a.z * b.z;
509 vector cross(vector a, vector b)
512 '1 0 0' * (a.y * b.z - a.z * b.y)
513 + '0 1 0' * (a.z * b.x - a.x * b.z)
514 + '0 0 1' * (a.x * b.y - a.y * b.x);
517 // compressed vector format:
518 // like MD3, just even shorter
519 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
520 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
521 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
522 // length = 2^(length_encoded/8) / 8
523 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
524 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
525 // the special value 0 indicates the zero vector
527 float lengthLogTable[128];
529 float invertLengthLog(float x)
533 if(x >= lengthLogTable[127])
535 if(x <= lengthLogTable[0])
543 m = floor((l + r) / 2);
544 if(lengthLogTable[m] < x)
550 // now: r is >=, l is <
551 float lerr = (x - lengthLogTable[l]);
552 float rerr = (lengthLogTable[r] - x);
558 vector decompressShortVector(int data)
563 float p = (data & 0xF000) / 0x1000;
564 float y = (data & 0x0F80) / 0x80;
565 int len = (data & 0x007F);
567 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
580 y = .19634954084936207740 * y;
581 p = .19634954084936207740 * p - 1.57079632679489661922;
582 out_x = cos(y) * cos(p);
583 out_y = sin(y) * cos(p);
587 //print("decompressed: ", vtos(out), "\n");
589 return out * lengthLogTable[len];
592 float compressShortVector(vector vec)
598 //print("compress: ", vtos(vec), "\n");
599 ang = vectoangles(vec);
603 if(ang.x < -90 && ang.x > +90)
604 error("BOGUS vectoangles");
605 //print("angles: ", vtos(ang), "\n");
607 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
616 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
617 len = invertLengthLog(vlen(vec));
619 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
621 return (p * 0x1000) + (y * 0x80) + len;
624 void compressShortVector_init()
627 float f = pow(2, 1/8);
629 for(i = 0; i < 128; ++i)
631 lengthLogTable[i] = l;
635 if(cvar("developer"))
637 print("Verifying vector compression table...\n");
638 for(i = 0x0F00; i < 0xFFFF; ++i)
639 if(i != compressShortVector(decompressShortVector(i)))
641 print("BROKEN vector compression: ", ftos(i));
642 print(" -> ", vtos(decompressShortVector(i)));
643 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
652 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
654 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
655 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
656 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
657 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
658 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
659 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
660 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
661 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
662 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
663 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
664 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
665 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
670 string fixPriorityList(string order, float from, float to, float subtract, float complete)
675 n = tokenize_console(order);
677 for(i = 0; i < n; ++i)
682 if(w >= from && w <= to)
683 neworder = strcat(neworder, ftos(w), " ");
687 if(w >= from && w <= to)
688 neworder = strcat(neworder, ftos(w), " ");
695 n = tokenize_console(neworder);
696 for(w = to; w >= from; --w)
698 for(i = 0; i < n; ++i)
699 if(stof(argv(i)) == w)
701 if(i == n) // not found
702 neworder = strcat(neworder, ftos(w), " ");
706 return substring(neworder, 0, strlen(neworder) - 1);
709 string mapPriorityList(string order, string(string) mapfunc)
714 n = tokenize_console(order);
716 for(i = 0; i < n; ++i)
717 neworder = strcat(neworder, mapfunc(argv(i)), " ");
719 return substring(neworder, 0, strlen(neworder) - 1);
722 string swapInPriorityList(string order, float i, float j)
727 n = tokenize_console(order);
729 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
732 for(w = 0; w < n; ++w)
735 s = strcat(s, argv(j), " ");
737 s = strcat(s, argv(i), " ");
739 s = strcat(s, argv(w), " ");
741 return substring(s, 0, strlen(s) - 1);
747 float cvar_value_issafe(string s)
749 if(strstrofs(s, "\"", 0) >= 0)
751 if(strstrofs(s, "\\", 0) >= 0)
753 if(strstrofs(s, ";", 0) >= 0)
755 if(strstrofs(s, "$", 0) >= 0)
757 if(strstrofs(s, "\r", 0) >= 0)
759 if(strstrofs(s, "\n", 0) >= 0)
765 void get_mi_min_max(float mode)
770 strunzone(mi_shortname);
771 mi_shortname = mapname;
772 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
773 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
774 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
775 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
776 mi_shortname = strzone(mi_shortname);
788 MapInfo_Get_ByName(mi_shortname, 0, 0);
789 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
791 mi_min = MapInfo_Map_mins;
792 mi_max = MapInfo_Map_maxs;
800 tracebox('1 0 0' * mi.x,
801 '0 1 0' * mi.y + '0 0 1' * mi.z,
802 '0 1 0' * ma.y + '0 0 1' * ma.z,
806 if(!trace_startsolid)
807 mi_min_x = trace_endpos.x;
809 tracebox('0 1 0' * mi.y,
810 '1 0 0' * mi.x + '0 0 1' * mi.z,
811 '1 0 0' * ma.x + '0 0 1' * ma.z,
815 if(!trace_startsolid)
816 mi_min_y = trace_endpos.y;
818 tracebox('0 0 1' * mi.z,
819 '1 0 0' * mi.x + '0 1 0' * mi.y,
820 '1 0 0' * ma.x + '0 1 0' * ma.y,
824 if(!trace_startsolid)
825 mi_min_z = trace_endpos.z;
827 tracebox('1 0 0' * ma.x,
828 '0 1 0' * mi.y + '0 0 1' * mi.z,
829 '0 1 0' * ma.y + '0 0 1' * ma.z,
833 if(!trace_startsolid)
834 mi_max_x = trace_endpos.x;
836 tracebox('0 1 0' * ma.y,
837 '1 0 0' * mi.x + '0 0 1' * mi.z,
838 '1 0 0' * ma.x + '0 0 1' * ma.z,
842 if(!trace_startsolid)
843 mi_max_y = trace_endpos.y;
845 tracebox('0 0 1' * ma.z,
846 '1 0 0' * mi.x + '0 1 0' * mi.y,
847 '1 0 0' * ma.x + '0 1 0' * ma.y,
851 if(!trace_startsolid)
852 mi_max_z = trace_endpos.z;
857 void get_mi_min_max_texcoords(float mode)
861 get_mi_min_max(mode);
866 // extend mi_picmax to get a square aspect ratio
867 // center the map in that area
868 extend = mi_picmax - mi_picmin;
869 if(extend.y > extend.x)
871 mi_picmin.x -= (extend.y - extend.x) * 0.5;
872 mi_picmax.x += (extend.y - extend.x) * 0.5;
876 mi_picmin.y -= (extend.x - extend.y) * 0.5;
877 mi_picmax.y += (extend.x - extend.y) * 0.5;
880 // add another some percent
881 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
885 // calculate the texcoords
886 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
887 // first the two corners of the origin
888 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
889 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
890 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
891 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
892 // then the other corners
893 mi_pictexcoord1_x = mi_pictexcoord0_x;
894 mi_pictexcoord1_y = mi_pictexcoord2_y;
895 mi_pictexcoord3_x = mi_pictexcoord2_x;
896 mi_pictexcoord3_y = mi_pictexcoord0_y;
900 float cvar_settemp(string tmp_cvar, string tmp_value)
902 float created_saved_value;
905 created_saved_value = 0;
907 if (!(tmp_cvar || tmp_value))
909 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
913 if(!cvar_type(tmp_cvar))
915 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
919 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
920 if(e.netname == tmp_cvar)
921 created_saved_value = -1; // skip creation
923 if(created_saved_value != -1)
925 // creating a new entity to keep track of this cvar
927 e.classname = "saved_cvar_value";
928 e.netname = strzone(tmp_cvar);
929 e.message = strzone(cvar_string(tmp_cvar));
930 created_saved_value = 1;
933 // update the cvar to the value given
934 cvar_set(tmp_cvar, tmp_value);
936 return created_saved_value;
939 float cvar_settemp_restore()
943 while((e = find(e, classname, "saved_cvar_value")))
945 if(cvar_type(e.netname))
947 cvar_set(e.netname, e.message);
952 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
958 float almost_equals(float a, float b)
961 eps = (max(a, -a) + max(b, -b)) * 0.001;
962 if(a - b < eps && b - a < eps)
967 float almost_in_bounds(float a, float b, float c)
970 eps = (max(a, -a) + max(c, -c)) * 0.001;
973 return b == median(a - eps, b, c + eps);
976 float power2of(float e)
980 float log2of(float x)
982 // NOTE: generated code
1055 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1059 else if(ma == rgb.x)
1062 return (rgb.y - rgb.z) / (ma - mi);
1064 return (rgb.y - rgb.z) / (ma - mi) + 6;
1066 else if(ma == rgb.y)
1067 return (rgb.z - rgb.x) / (ma - mi) + 2;
1068 else // if(ma == rgb_z)
1069 return (rgb.x - rgb.y) / (ma - mi) + 4;
1072 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1076 hue -= 6 * floor(hue / 6);
1078 //else if(ma == rgb_x)
1079 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1083 rgb_y = hue * (ma - mi) + mi;
1086 //else if(ma == rgb_y)
1087 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1090 rgb_x = (2 - hue) * (ma - mi) + mi;
1098 rgb_z = (hue - 2) * (ma - mi) + mi;
1100 //else // if(ma == rgb_z)
1101 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1105 rgb_y = (4 - hue) * (ma - mi) + mi;
1110 rgb_x = (hue - 4) * (ma - mi) + mi;
1114 //else if(ma == rgb_x)
1115 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1116 else // if(hue <= 6)
1120 rgb_z = (6 - hue) * (ma - mi) + mi;
1126 vector rgb_to_hsv(vector rgb)
1131 mi = min(rgb.x, rgb.y, rgb.z);
1132 ma = max(rgb.x, rgb.y, rgb.z);
1134 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1145 vector hsv_to_rgb(vector hsv)
1147 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1150 vector rgb_to_hsl(vector rgb)
1155 mi = min(rgb.x, rgb.y, rgb.z);
1156 ma = max(rgb.x, rgb.y, rgb.z);
1158 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1160 hsl_z = 0.5 * (mi + ma);
1163 else if(hsl.z <= 0.5)
1164 hsl_y = (ma - mi) / (2*hsl.z);
1165 else // if(hsl_z > 0.5)
1166 hsl_y = (ma - mi) / (2 - 2*hsl.z);
1171 vector hsl_to_rgb(vector hsl)
1173 float mi, ma, maminusmi;
1176 maminusmi = hsl.y * 2 * hsl.z;
1178 maminusmi = hsl.y * (2 - 2 * hsl.z);
1180 // hsl_z = 0.5 * mi + 0.5 * ma
1181 // maminusmi = - mi + ma
1182 mi = hsl.z - 0.5 * maminusmi;
1183 ma = hsl.z + 0.5 * maminusmi;
1185 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1188 string rgb_to_hexcolor(vector rgb)
1193 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1194 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1195 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1199 // requires that m2>m1 in all coordinates, and that m4>m3
1200 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;}
1202 // requires the same, but is a stronger condition
1203 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;}
1208 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1211 // The following function is SLOW.
1212 // For your safety and for the protection of those around you...
1213 // DO NOT CALL THIS AT HOME.
1214 // No really, don't.
1215 if(w(theText, theSize) <= maxWidth)
1216 return strlen(theText); // yeah!
1218 // binary search for right place to cut string
1220 float left, right, middle; // this always works
1222 right = strlen(theText); // this always fails
1225 middle = floor((left + right) / 2);
1226 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1231 while(left < right - 1);
1233 if(w("^7", theSize) == 0) // detect color codes support in the width function
1235 // NOTE: when color codes are involved, this binary search is,
1236 // mathematically, BROKEN. However, it is obviously guaranteed to
1237 // terminate, as the range still halves each time - but nevertheless, it is
1238 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1239 // range, and "right" is outside).
1241 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1242 // and decrease left on the basis of the chars detected of the truncated tag
1243 // Even if the ^xrgb tag is not complete/correct, left is decreased
1244 // (sometimes too much but with a correct result)
1245 // it fixes also ^[0-9]
1246 while(left >= 1 && substring(theText, left-1, 1) == "^")
1249 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1251 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1253 ch = str2chr(theText, left-1);
1254 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1257 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1259 ch = str2chr(theText, left-2);
1260 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1262 ch = str2chr(theText, left-1);
1263 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1272 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1275 // The following function is SLOW.
1276 // For your safety and for the protection of those around you...
1277 // DO NOT CALL THIS AT HOME.
1278 // No really, don't.
1279 if(w(theText) <= maxWidth)
1280 return strlen(theText); // yeah!
1282 // binary search for right place to cut string
1284 float left, right, middle; // this always works
1286 right = strlen(theText); // this always fails
1289 middle = floor((left + right) / 2);
1290 if(w(substring(theText, 0, middle)) <= maxWidth)
1295 while(left < right - 1);
1297 if(w("^7") == 0) // detect color codes support in the width function
1299 // NOTE: when color codes are involved, this binary search is,
1300 // mathematically, BROKEN. However, it is obviously guaranteed to
1301 // terminate, as the range still halves each time - but nevertheless, it is
1302 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1303 // range, and "right" is outside).
1305 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1306 // and decrease left on the basis of the chars detected of the truncated tag
1307 // Even if the ^xrgb tag is not complete/correct, left is decreased
1308 // (sometimes too much but with a correct result)
1309 // it fixes also ^[0-9]
1310 while(left >= 1 && substring(theText, left-1, 1) == "^")
1313 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1315 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1317 ch = str2chr(theText, left-1);
1318 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1321 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1323 ch = str2chr(theText, left-2);
1324 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1326 ch = str2chr(theText, left-1);
1327 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1336 string find_last_color_code(string s)
1338 int start = strstrofs(s, "^", 0);
1339 if (start == -1) // no caret found
1341 int len = strlen(s)-1;
1343 for(i = len; i >= start; --i)
1345 if(substring(s, i, 1) != "^")
1349 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1352 // check if carets aren't all escaped
1356 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1357 return substring(s, i, 2);
1360 if(substring(s, i+1, 1) == "x")
1361 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1362 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1363 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1364 return substring(s, i, 5);
1366 i -= carets; // this also skips one char before the carets
1372 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1378 s = getWrappedLine_remaining;
1382 getWrappedLine_remaining = string_null;
1383 return s; // the line has no size ANYWAY, nothing would be displayed.
1386 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1387 if(cantake > 0 && cantake < strlen(s))
1390 while(take > 0 && substring(s, take, 1) != " ")
1394 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1395 if(getWrappedLine_remaining == "")
1396 getWrappedLine_remaining = string_null;
1397 else if (tw("^7", theFontSize) == 0)
1398 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1399 return substring(s, 0, cantake);
1403 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1404 if(getWrappedLine_remaining == "")
1405 getWrappedLine_remaining = string_null;
1406 else if (tw("^7", theFontSize) == 0)
1407 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1408 return substring(s, 0, take);
1413 getWrappedLine_remaining = string_null;
1418 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1424 s = getWrappedLine_remaining;
1428 getWrappedLine_remaining = string_null;
1429 return s; // the line has no size ANYWAY, nothing would be displayed.
1432 cantake = textLengthUpToLength(s, w, tw);
1433 if(cantake > 0 && cantake < strlen(s))
1436 while(take > 0 && substring(s, take, 1) != " ")
1440 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1441 if(getWrappedLine_remaining == "")
1442 getWrappedLine_remaining = string_null;
1443 else if (tw("^7") == 0)
1444 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1445 return substring(s, 0, cantake);
1449 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1450 if(getWrappedLine_remaining == "")
1451 getWrappedLine_remaining = string_null;
1452 else if (tw("^7") == 0)
1453 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1454 return substring(s, 0, take);
1459 getWrappedLine_remaining = string_null;
1464 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1466 if(tw(theText, theFontSize) <= maxWidth)
1469 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1472 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1474 if(tw(theText) <= maxWidth)
1477 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1480 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1482 string subpattern, subpattern2, subpattern3, subpattern4;
1483 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1485 subpattern2 = ",teams,";
1487 subpattern2 = ",noteams,";
1489 subpattern3 = ",teamspawns,";
1491 subpattern3 = ",noteamspawns,";
1492 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1493 subpattern4 = ",race,";
1495 subpattern4 = string_null;
1497 if(substring(pattern, 0, 1) == "-")
1499 pattern = substring(pattern, 1, strlen(pattern) - 1);
1500 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1502 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1504 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1506 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1511 if(substring(pattern, 0, 1) == "+")
1512 pattern = substring(pattern, 1, strlen(pattern) - 1);
1513 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1514 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1515 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1519 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1526 void shuffle(float n, swapfunc_t swap, entity pass)
1529 for(i = 1; i < n; ++i)
1531 // swap i-th item at a random position from 0 to i
1532 // proof for even distribution:
1535 // item n+1 gets at any position with chance 1/(n+1)
1536 // all others will get their 1/n chance reduced by factor n/(n+1)
1537 // to be on place n+1, their chance will be 1/(n+1)
1538 // 1/n * n/(n+1) = 1/(n+1)
1540 j = floor(random() * (i + 1));
1546 string substring_range(string s, float b, float e)
1548 return substring(s, b, e - b);
1551 string swapwords(string str, float i, float j)
1554 string s1, s2, s3, s4, s5;
1555 float si, ei, sj, ej, s0, en;
1556 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1557 si = argv_start_index(i);
1558 sj = argv_start_index(j);
1559 ei = argv_end_index(i);
1560 ej = argv_end_index(j);
1561 s0 = argv_start_index(0);
1562 en = argv_end_index(n-1);
1563 s1 = substring_range(str, s0, si);
1564 s2 = substring_range(str, si, ei);
1565 s3 = substring_range(str, ei, sj);
1566 s4 = substring_range(str, sj, ej);
1567 s5 = substring_range(str, ej, en);
1568 return strcat(s1, s4, s3, s2, s5);
1571 string _shufflewords_str;
1572 void _shufflewords_swapfunc(float i, float j, entity pass)
1574 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1576 string shufflewords(string str)
1579 _shufflewords_str = str;
1580 n = tokenizebyseparator(str, " ");
1581 shuffle(n, _shufflewords_swapfunc, world);
1582 str = _shufflewords_str;
1583 _shufflewords_str = string_null;
1587 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1603 // actually, every number solves the equation!
1614 if(a > 0) // put the smaller solution first
1616 v_x = ((-b)-D) / (2*a);
1617 v_y = ((-b)+D) / (2*a);
1621 v_x = (-b+D) / (2*a);
1622 v_y = (-b-D) / (2*a);
1628 // complex solutions!
1641 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1645 // make origin and speed relative
1650 // now solve for ret, ret normalized:
1651 // eorg + t * evel == t * ret * spd
1652 // or, rather, solve for t:
1653 // |eorg + t * evel| == t * spd
1654 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1655 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1656 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1657 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1658 // q = (eorg * eorg) / (evel * evel - spd * spd)
1659 if(!solution.z) // no real solution
1662 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1663 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1664 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1665 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1666 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1667 // spd < |evel| * sin angle(evel, eorg)
1670 else if(solution.x > 0)
1672 // both solutions > 0: take the smaller one
1673 // happens if p < 0 and q > 0
1674 ret = normalize(eorg + solution.x * evel);
1676 else if(solution.y > 0)
1678 // one solution > 0: take the larger one
1679 // happens if q < 0 or q == 0 and p < 0
1680 ret = normalize(eorg + solution.y * evel);
1684 // no solution > 0: reject
1685 // happens if p > 0 and q >= 0
1686 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1687 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1692 // "Enemy is moving away from me at more than spd"
1696 // NOTE: we always got a solution if spd > |evel|
1698 if(newton_style == 2)
1699 ret = normalize(ret * spd + myvel);
1704 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1709 if(newton_style == 2)
1711 // true Newtonian projectiles with automatic aim adjustment
1713 // solve: |outspeed * mydir - myvel| = spd
1714 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1715 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1719 // myvel^2 - (mydir * myvel)^2 > spd^2
1720 // velocity without mydir component > spd
1721 // fire at smallest possible spd that works?
1722 // |(mydir * myvel) * myvel - myvel| = spd
1724 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1728 outspeed = solution.y; // the larger one
1731 //outspeed = 0; // slowest possible shot
1732 outspeed = solution.x; // the real part (that is, the average!)
1733 //dprint("impossible shot, adjusting\n");
1736 outspeed = bound(spd * mi, outspeed, spd * ma);
1737 return mydir * outspeed;
1741 return myvel + spd * mydir;
1744 float compressShotOrigin(vector v)
1748 y = rint(v.y * 4) + 128;
1749 z = rint(v.z * 4) + 128;
1750 if(x > 255 || x < 0)
1752 print("shot origin ", vtos(v), " x out of bounds\n");
1753 x = bound(0, x, 255);
1755 if(y > 255 || y < 0)
1757 print("shot origin ", vtos(v), " y out of bounds\n");
1758 y = bound(0, y, 255);
1760 if(z > 255 || z < 0)
1762 print("shot origin ", vtos(v), " z out of bounds\n");
1763 z = bound(0, z, 255);
1765 return x * 0x10000 + y * 0x100 + z;
1767 vector decompressShotOrigin(int f)
1770 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1771 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1772 v_z = ((f & 0xFF) - 128) / 4;
1776 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1778 float start, end, root, child;
1781 start = floor((n - 2) / 2);
1784 // siftdown(start, count-1);
1786 while(root * 2 + 1 <= n-1)
1788 child = root * 2 + 1;
1790 if(cmp(child, child+1, pass) < 0)
1792 if(cmp(root, child, pass) < 0)
1794 swap(root, child, pass);
1810 // siftdown(0, end);
1812 while(root * 2 + 1 <= end)
1814 child = root * 2 + 1;
1815 if(child < end && cmp(child, child+1, pass) < 0)
1817 if(cmp(root, child, pass) < 0)
1819 swap(root, child, pass);
1829 void RandomSelection_Init()
1831 RandomSelection_totalweight = 0;
1832 RandomSelection_chosen_ent = world;
1833 RandomSelection_chosen_float = 0;
1834 RandomSelection_chosen_string = string_null;
1835 RandomSelection_best_priority = -1;
1837 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1839 if(priority > RandomSelection_best_priority)
1841 RandomSelection_best_priority = priority;
1842 RandomSelection_chosen_ent = e;
1843 RandomSelection_chosen_float = f;
1844 RandomSelection_chosen_string = s;
1845 RandomSelection_totalweight = weight;
1847 else if(priority == RandomSelection_best_priority)
1849 RandomSelection_totalweight += weight;
1850 if(random() * RandomSelection_totalweight <= weight)
1852 RandomSelection_chosen_ent = e;
1853 RandomSelection_chosen_float = f;
1854 RandomSelection_chosen_string = s;
1860 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1862 // NOTE: we'll always choose the SMALLER value...
1863 float healthdamage, armordamage, armorideal;
1864 if (deathtype == DEATH_DROWN) // Why should armor help here...
1867 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1868 armordamage = a + (h - 1); // damage we can take if we could use more armor
1869 armorideal = healthdamage * armorblock;
1871 if(armordamage < healthdamage)
1884 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1887 if (deathtype == DEATH_DROWN) // Why should armor help here...
1889 v_y = bound(0, damage * armorblock, a); // save
1890 v_x = bound(0, damage - v.y, damage); // take
1896 string getcurrentmod()
1900 m = cvar_string("fs_gamedir");
1901 n = tokenize_console(m);
1913 v = ReadShort() * 256; // note: this is signed
1914 v += ReadByte(); // note: this is unsigned
1917 vector ReadInt48_t()
1920 v_x = ReadInt24_t();
1921 v_y = ReadInt24_t();
1925 vector ReadInt72_t()
1928 v_x = ReadInt24_t();
1929 v_y = ReadInt24_t();
1930 v_z = ReadInt24_t();
1934 void WriteInt24_t(float dst, float val)
1937 WriteShort(dst, (v = floor(val / 256)));
1938 WriteByte(dst, val - v * 256); // 0..255
1940 void WriteInt48_t(float dst, vector val)
1942 WriteInt24_t(dst, val.x);
1943 WriteInt24_t(dst, val.y);
1945 void WriteInt72_t(float dst, vector val)
1947 WriteInt24_t(dst, val.x);
1948 WriteInt24_t(dst, val.y);
1949 WriteInt24_t(dst, val.z);
1954 float float2range11(float f)
1956 // continuous function mapping all reals into -1..1
1957 return f / (fabs(f) + 1);
1960 float float2range01(float f)
1962 // continuous function mapping all reals into 0..1
1963 return 0.5 + 0.5 * float2range11(f);
1966 // from the GNU Scientific Library
1967 float gsl_ran_gaussian_lastvalue;
1968 float gsl_ran_gaussian_lastvalue_set;
1969 float gsl_ran_gaussian(float sigma)
1972 if(gsl_ran_gaussian_lastvalue_set)
1974 gsl_ran_gaussian_lastvalue_set = 0;
1975 return sigma * gsl_ran_gaussian_lastvalue;
1979 a = random() * 2 * M_PI;
1980 b = sqrt(-2 * log(random()));
1981 gsl_ran_gaussian_lastvalue = cos(a) * b;
1982 gsl_ran_gaussian_lastvalue_set = 1;
1983 return sigma * sin(a) * b;
1987 string car(string s)
1990 o = strstrofs(s, " ", 0);
1993 return substring(s, 0, o);
1995 string cdr(string s)
1998 o = strstrofs(s, " ", 0);
2001 return substring(s, o + 1, strlen(s) - (o + 1));
2003 float matchacl(string acl, string str)
2010 t = car(acl); acl = cdr(acl);
2013 if(substring(t, 0, 1) == "-")
2016 t = substring(t, 1, strlen(t) - 1);
2018 else if(substring(t, 0, 1) == "+")
2019 t = substring(t, 1, strlen(t) - 1);
2021 if(substring(t, -1, 1) == "*")
2023 t = substring(t, 0, strlen(t) - 1);
2024 s = substring(str, 0, strlen(t));
2036 float startsWith(string haystack, string needle)
2038 return substring(haystack, 0, strlen(needle)) == needle;
2040 float startsWithNocase(string haystack, string needle)
2042 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2045 string get_model_datafilename(string m, float sk, string fil)
2050 m = "models/player/*_";
2052 m = strcat(m, ftos(sk));
2055 return strcat(m, ".", fil);
2058 float get_model_parameters(string m, float sk)
2063 get_model_parameters_modelname = string_null;
2064 get_model_parameters_modelskin = -1;
2065 get_model_parameters_name = string_null;
2066 get_model_parameters_species = -1;
2067 get_model_parameters_sex = string_null;
2068 get_model_parameters_weight = -1;
2069 get_model_parameters_age = -1;
2070 get_model_parameters_desc = string_null;
2071 get_model_parameters_bone_upperbody = string_null;
2072 get_model_parameters_bone_weapon = string_null;
2073 for(i = 0; i < MAX_AIM_BONES; ++i)
2075 get_model_parameters_bone_aim[i] = string_null;
2076 get_model_parameters_bone_aimweight[i] = 0;
2078 get_model_parameters_fixbone = 0;
2083 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2084 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2088 if(substring(m, -4, -1) != ".txt")
2090 if(substring(m, -6, 1) != "_")
2092 sk = stof(substring(m, -5, 1));
2093 m = substring(m, 0, -7);
2096 fn = get_model_datafilename(m, sk, "txt");
2097 fh = fopen(fn, FILE_READ);
2101 fn = get_model_datafilename(m, sk, "txt");
2102 fh = fopen(fn, FILE_READ);
2107 get_model_parameters_modelname = m;
2108 get_model_parameters_modelskin = sk;
2109 while((s = fgets(fh)))
2112 break; // next lines will be description
2116 get_model_parameters_name = s;
2120 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2121 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2122 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2123 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2124 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2125 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2126 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2129 get_model_parameters_sex = s;
2131 get_model_parameters_weight = stof(s);
2133 get_model_parameters_age = stof(s);
2134 if(c == "description")
2135 get_model_parameters_description = s;
2136 if(c == "bone_upperbody")
2137 get_model_parameters_bone_upperbody = s;
2138 if(c == "bone_weapon")
2139 get_model_parameters_bone_weapon = s;
2140 for(i = 0; i < MAX_AIM_BONES; ++i)
2141 if(c == strcat("bone_aim", ftos(i)))
2143 get_model_parameters_bone_aimweight[i] = stof(car(s));
2144 get_model_parameters_bone_aim[i] = cdr(s);
2147 get_model_parameters_fixbone = stof(s);
2150 while((s = fgets(fh)))
2152 if(get_model_parameters_desc)
2153 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2155 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2163 vector vec2(vector v)
2170 vector NearestPointOnBox(entity box, vector org)
2172 vector m1, m2, nearest;
2174 m1 = box.mins + box.origin;
2175 m2 = box.maxs + box.origin;
2177 nearest_x = bound(m1_x, org.x, m2_x);
2178 nearest_y = bound(m1_y, org.y, m2_y);
2179 nearest_z = bound(m1_z, org.z, m2_z);
2185 float vercmp_recursive(string v1, string v2)
2191 dot1 = strstrofs(v1, ".", 0);
2192 dot2 = strstrofs(v2, ".", 0);
2196 s1 = substring(v1, 0, dot1);
2200 s2 = substring(v2, 0, dot2);
2202 r = stof(s1) - stof(s2);
2206 r = strcasecmp(s1, s2);
2219 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2222 float vercmp(string v1, string v2)
2224 if(strcasecmp(v1, v2) == 0) // early out check
2233 return vercmp_recursive(v1, v2);
2236 float u8_strsize(string s)
2256 // translation helpers
2257 string language_filename(string s)
2262 if(fn == "" || fn == "dump")
2264 fn = strcat(s, ".", fn);
2265 if((fh = fopen(fn, FILE_READ)) >= 0)
2272 string CTX(string s)
2274 float p = strstrofs(s, "^", 0);
2277 return substring(s, p+1, -1);
2280 // x-encoding (encoding as zero length invisible string)
2281 const string XENCODE_2 = "xX";
2282 const string XENCODE_22 = "0123456789abcdefABCDEF";
2283 string xencode(int f)
2286 d = f % 22; f = floor(f / 22);
2287 c = f % 22; f = floor(f / 22);
2288 b = f % 22; f = floor(f / 22);
2289 a = f % 2; // f = floor(f / 2);
2292 substring(XENCODE_2, a, 1),
2293 substring(XENCODE_22, b, 1),
2294 substring(XENCODE_22, c, 1),
2295 substring(XENCODE_22, d, 1)
2298 float xdecode(string s)
2301 if(substring(s, 0, 1) != "^")
2305 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2306 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2307 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2308 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2309 if(a < 0 || b < 0 || c < 0 || d < 0)
2311 return ((a * 22 + b) * 22 + c) * 22 + d;
2314 float lowestbit(int f)
2325 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2327 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2330 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2333 // escape the string to make it safe for consoles
2334 string MakeConsoleSafe(string input)
2336 input = strreplace("\n", "", input);
2337 input = strreplace("\\", "\\\\", input);
2338 input = strreplace("$", "$$", input);
2339 input = strreplace("\"", "\\\"", input);
2344 // get true/false value of a string with multiple different inputs
2345 float InterpretBoolean(string input)
2347 switch(strtolower(input))
2359 default: return stof(input);
2365 entity ReadCSQCEntity()
2371 return findfloat(world, entnum, f);
2375 float shutdown_running;
2380 void CSQC_Shutdown()
2386 if(shutdown_running)
2388 print("Recursive shutdown detected! Only restoring cvars...\n");
2392 shutdown_running = 1;
2395 cvar_settemp_restore(); // this must be done LAST, but in any case
2398 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2399 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2400 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2401 // this will use the value:
2403 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2404 // accuracy at x is 1/derivative, i.e.
2405 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2407 void WriteApproxPastTime(float dst, float t)
2409 float dt = time - t;
2411 // warning: this is approximate; do not resend when you don't have to!
2412 // be careful with sendflags here!
2413 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2416 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2419 dt = rint(bound(0, dt, 255));
2425 float ReadApproxPastTime()
2427 float dt = ReadByte();
2429 // map from range...PPROXPASTTIME_MAX / 256
2430 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2432 return servertime - dt;
2437 .float skeleton_bones_index;
2438 void Skeleton_SetBones(entity e)
2440 // set skeleton_bones to the total number of bones on the model
2441 if(e.skeleton_bones_index == e.modelindex)
2442 return; // same model, nothing to update
2445 skelindex = skel_create(e.modelindex);
2446 e.skeleton_bones = skel_get_numbones(skelindex);
2447 skel_delete(skelindex);
2448 e.skeleton_bones_index = e.modelindex;
2452 string to_execute_next_frame;
2453 void execute_next_frame()
2455 if(to_execute_next_frame)
2457 localcmd("\n", to_execute_next_frame, "\n");
2458 strunzone(to_execute_next_frame);
2459 to_execute_next_frame = string_null;
2462 void queue_to_execute_next_frame(string s)
2464 if(to_execute_next_frame)
2466 s = strcat(s, "\n", to_execute_next_frame);
2467 strunzone(to_execute_next_frame);
2469 to_execute_next_frame = strzone(s);
2472 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2475 ((( startspeedfactor + endspeedfactor - 2
2476 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2477 ) * x + startspeedfactor
2481 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2483 if(startspeedfactor < 0 || endspeedfactor < 0)
2487 // if this is the case, the possible zeros of the first derivative are outside
2489 We can calculate this condition as condition
2494 // better, see below:
2495 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2498 // if this is the case, the first derivative has no zeros at all
2499 float se = startspeedfactor + endspeedfactor;
2500 float s_e = startspeedfactor - endspeedfactor;
2501 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2504 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2505 // we also get s_e <= 6 - se
2506 // 3 * (se - 4)^2 + (6 - se)^2
2507 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2508 // Therefore, above "better" check works!
2512 // known good cases:
2520 // (3.5, [0.2..2.3])
2525 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2527 s + e - 2 == 0: no inflection
2530 0 < inflection < 1 if:
2531 0 < 2s + e - 3 < 3s + 3e - 6
2532 2s + e > 3 and 2e + s > 3
2535 0 < inflection < 1 if:
2536 0 > 2s + e - 3 > 3s + 3e - 6
2537 2s + e < 3 and 2e + s < 3
2539 Therefore: there is an inflection point iff:
2540 e outside (3 - s)/2 .. 3 - s*2
2542 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)
2546 .float FindConnectedComponent_processing;
2547 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2549 entity queue_start, queue_end;
2551 // we build a queue of to-be-processed entities.
2552 // queue_start is the next entity to be checked for neighbors
2553 // queue_end is the last entity added
2555 if(e.FindConnectedComponent_processing)
2556 error("recursion or broken cleanup");
2558 // start with a 1-element queue
2559 queue_start = queue_end = e;
2560 queue_end.fld = world;
2561 queue_end.FindConnectedComponent_processing = 1;
2563 // for each queued item:
2564 for(0; queue_start; queue_start = queue_start.fld)
2566 // find all neighbors of queue_start
2568 for(t = world; (t = nxt(t, queue_start, pass)); )
2570 if(t.FindConnectedComponent_processing)
2572 if(iscon(t, queue_start, pass))
2574 // it is connected? ADD IT. It will look for neighbors soon too.
2577 queue_end.fld = world;
2578 queue_end.FindConnectedComponent_processing = 1;
2584 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2585 queue_start.FindConnectedComponent_processing = 0;
2589 vector combine_to_vector(float x, float y, float z)
2591 vector result; result_x = x; result_y = y; result_z = z;
2595 vector get_corner_position(entity box, float corner)
2599 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2600 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2601 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2602 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2603 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2604 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2605 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2606 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2607 default: return '0 0 0';
2612 // todo: this sucks, lets find a better way to do backtraces?
2613 void backtrace(string msg)
2617 dev = autocvar_developer;
2618 war = autocvar_prvm_backtraceforwarnings;
2620 dev = cvar("developer");
2621 war = cvar("prvm_backtraceforwarnings");
2623 cvar_set("developer", "1");
2624 cvar_set("prvm_backtraceforwarnings", "1");
2626 print("--- CUT HERE ---\nWARNING: ");
2629 remove(world); // isn't there any better way to cause a backtrace?
2630 print("\n--- CUT UNTIL HERE ---\n");
2631 cvar_set("developer", ftos(dev));
2632 cvar_set("prvm_backtraceforwarnings", ftos(war));
2635 // color code replace, place inside of sprintf and parse the string
2636 string CCR(string input)
2638 // See the autocvar declarations in util.qh for default values
2640 // foreground/normal colors
2641 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2642 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2643 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2644 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2647 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2648 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2649 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2651 // background colors
2652 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2653 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2657 vector vec3(float x, float y, float z)
2667 vector animfixfps(entity e, vector a, vector b)
2669 // multi-frame anim: keep as-is
2673 dur = frameduration(e.modelindex, a.x);
2677 dur = frameduration(e.modelindex, a.x);
2687 void dedicated_print(string input) // print(), but only print if the server is not local
2689 if(server_is_dedicated) { print(input); }
2694 float Announcer_PickNumber(float type, float num)
2702 case 10: return ANNCE_NUM_GAMESTART_10;
2703 case 9: return ANNCE_NUM_GAMESTART_9;
2704 case 8: return ANNCE_NUM_GAMESTART_8;
2705 case 7: return ANNCE_NUM_GAMESTART_7;
2706 case 6: return ANNCE_NUM_GAMESTART_6;
2707 case 5: return ANNCE_NUM_GAMESTART_5;
2708 case 4: return ANNCE_NUM_GAMESTART_4;
2709 case 3: return ANNCE_NUM_GAMESTART_3;
2710 case 2: return ANNCE_NUM_GAMESTART_2;
2711 case 1: return ANNCE_NUM_GAMESTART_1;
2719 case 10: return ANNCE_NUM_IDLE_10;
2720 case 9: return ANNCE_NUM_IDLE_9;
2721 case 8: return ANNCE_NUM_IDLE_8;
2722 case 7: return ANNCE_NUM_IDLE_7;
2723 case 6: return ANNCE_NUM_IDLE_6;
2724 case 5: return ANNCE_NUM_IDLE_5;
2725 case 4: return ANNCE_NUM_IDLE_4;
2726 case 3: return ANNCE_NUM_IDLE_3;
2727 case 2: return ANNCE_NUM_IDLE_2;
2728 case 1: return ANNCE_NUM_IDLE_1;
2736 case 10: return ANNCE_NUM_KILL_10;
2737 case 9: return ANNCE_NUM_KILL_9;
2738 case 8: return ANNCE_NUM_KILL_8;
2739 case 7: return ANNCE_NUM_KILL_7;
2740 case 6: return ANNCE_NUM_KILL_6;
2741 case 5: return ANNCE_NUM_KILL_5;
2742 case 4: return ANNCE_NUM_KILL_4;
2743 case 3: return ANNCE_NUM_KILL_3;
2744 case 2: return ANNCE_NUM_KILL_2;
2745 case 1: return ANNCE_NUM_KILL_1;
2753 case 10: return ANNCE_NUM_RESPAWN_10;
2754 case 9: return ANNCE_NUM_RESPAWN_9;
2755 case 8: return ANNCE_NUM_RESPAWN_8;
2756 case 7: return ANNCE_NUM_RESPAWN_7;
2757 case 6: return ANNCE_NUM_RESPAWN_6;
2758 case 5: return ANNCE_NUM_RESPAWN_5;
2759 case 4: return ANNCE_NUM_RESPAWN_4;
2760 case 3: return ANNCE_NUM_RESPAWN_3;
2761 case 2: return ANNCE_NUM_RESPAWN_2;
2762 case 1: return ANNCE_NUM_RESPAWN_1;
2766 case CNT_ROUNDSTART:
2770 case 10: return ANNCE_NUM_ROUNDSTART_10;
2771 case 9: return ANNCE_NUM_ROUNDSTART_9;
2772 case 8: return ANNCE_NUM_ROUNDSTART_8;
2773 case 7: return ANNCE_NUM_ROUNDSTART_7;
2774 case 6: return ANNCE_NUM_ROUNDSTART_6;
2775 case 5: return ANNCE_NUM_ROUNDSTART_5;
2776 case 4: return ANNCE_NUM_ROUNDSTART_4;
2777 case 3: return ANNCE_NUM_ROUNDSTART_3;
2778 case 2: return ANNCE_NUM_ROUNDSTART_2;
2779 case 1: return ANNCE_NUM_ROUNDSTART_1;
2787 case 10: return ANNCE_NUM_10;
2788 case 9: return ANNCE_NUM_9;
2789 case 8: return ANNCE_NUM_8;
2790 case 7: return ANNCE_NUM_7;
2791 case 6: return ANNCE_NUM_6;
2792 case 5: return ANNCE_NUM_5;
2793 case 4: return ANNCE_NUM_4;
2794 case 3: return ANNCE_NUM_3;
2795 case 2: return ANNCE_NUM_2;
2796 case 1: return ANNCE_NUM_1;
2801 return NOTIF_ABORT; // abort sending if none of these numbers were right
2806 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2808 switch(nativecontents)
2813 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2815 return DPCONTENTS_WATER;
2817 return DPCONTENTS_SLIME;
2819 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2821 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2826 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2828 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2829 return CONTENT_SOLID;
2830 if(supercontents & DPCONTENTS_SKY)
2832 if(supercontents & DPCONTENTS_LAVA)
2833 return CONTENT_LAVA;
2834 if(supercontents & DPCONTENTS_SLIME)
2835 return CONTENT_SLIME;
2836 if(supercontents & DPCONTENTS_WATER)
2837 return CONTENT_WATER;
2838 return CONTENT_EMPTY;
2842 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2845 (c - 2 * b + a) * (t * t) +
2850 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2853 (c - 2 * b + a) * (2 * t) +