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 "../warpzonelib/mathlib.qh"
15 #include "constants.qh"
17 #include "../server/autocvars.qh"
18 #include "../server/defs.qh"
19 #include "notifications.qh"
20 #include "deathtypes.qh"
24 string wordwrap_buffer;
26 void wordwrap_buffer_put(string s)
28 wordwrap_buffer = strcat(wordwrap_buffer, s);
31 string wordwrap(string s, float l)
35 wordwrap_cb(s, l, wordwrap_buffer_put);
43 void wordwrap_buffer_sprint(string s)
45 wordwrap_buffer = strcat(wordwrap_buffer, s);
48 sprint(self, wordwrap_buffer);
53 void wordwrap_sprint(string s, float l)
56 wordwrap_cb(s, l, wordwrap_buffer_sprint);
57 if(wordwrap_buffer != "")
58 sprint(self, strcat(wordwrap_buffer, "\n"));
66 string draw_UseSkinFor(string pic)
68 if(substring(pic, 0, 1) == "/")
69 return substring(pic, 1, strlen(pic)-1);
71 return strcat(draw_currentSkin, "/", pic);
75 string unescape(string in)
80 // but it doesn't seem to be necessary in my tests at least
85 for(i = 0; i < len; ++i)
87 s = substring(in, i, 1);
90 s = substring(in, i+1, 1);
92 str = strcat(str, "\n");
94 str = strcat(str, "\\");
96 str = strcat(str, substring(in, i, 2));
106 void wordwrap_cb(string s, float l, void(string) callback)
109 float lleft, i, j, wlen;
113 for (i = 0;i < strlen(s);++i)
115 if (substring(s, i, 2) == "\\n")
121 else if (substring(s, i, 1) == "\n")
126 else if (substring(s, i, 1) == " ")
136 for (j = i+1;j < strlen(s);++j)
137 // ^^ this skips over the first character of a word, which
138 // is ALWAYS part of the word
139 // this is safe since if i+1 == strlen(s), i will become
140 // strlen(s)-1 at the end of this block and the function
141 // will terminate. A space can't be the first character we
142 // read here, and neither can a \n be the start, since these
143 // two cases have been handled above.
145 c = substring(s, j, 1);
152 // we need to keep this tempstring alive even if substring is
153 // called repeatedly, so call strcat even though we're not
163 callback(substring(s, i, wlen));
164 lleft = lleft - wlen;
171 float dist_point_line(vector p, vector l0, vector ldir)
173 ldir = normalize(ldir);
175 // remove the component in line direction
176 p = p - (p * ldir) * ldir;
178 // vlen of the remaining vector
182 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
211 float median(float a, float b, float c)
214 return bound(a, b, c);
215 return bound(c, b, a);
218 // converts a number to a string with the indicated number of decimals
219 // works for up to 10 decimals!
220 string ftos_decimals(float number, float decimals)
222 // inhibit stupid negative zero
225 // we have sprintf...
226 return sprintf("%.*f", decimals, number);
229 vector colormapPaletteColor(float c, float isPants)
233 case 0: return '1.000000 1.000000 1.000000';
234 case 1: return '1.000000 0.333333 0.000000';
235 case 2: return '0.000000 1.000000 0.501961';
236 case 3: return '0.000000 1.000000 0.000000';
237 case 4: return '1.000000 0.000000 0.000000';
238 case 5: return '0.000000 0.666667 1.000000';
239 case 6: return '0.000000 1.000000 1.000000';
240 case 7: return '0.501961 1.000000 0.000000';
241 case 8: return '0.501961 0.000000 1.000000';
242 case 9: return '1.000000 0.000000 1.000000';
243 case 10: return '1.000000 0.000000 0.501961';
244 case 11: return '0.000000 0.000000 1.000000';
245 case 12: return '1.000000 1.000000 0.000000';
246 case 13: return '0.000000 0.333333 1.000000';
247 case 14: return '1.000000 0.666667 0.000000';
251 '1 0 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 0.0000000000))
252 + '0 1 0' * (0.502 + 0.498 * sin(time / 2.7182818285 + 2.0943951024))
253 + '0 0 1' * (0.502 + 0.498 * sin(time / 2.7182818285 + 4.1887902048));
256 '1 0 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 5.2359877560))
257 + '0 1 0' * (0.502 + 0.498 * sin(time / 3.1415926536 + 3.1415926536))
258 + '0 0 1' * (0.502 + 0.498 * sin(time / 3.1415926536 + 1.0471975512));
259 default: return '0.000 0.000 0.000';
263 // unzone the string, and return it as tempstring. Safe to be called on string_null
264 string fstrunzone(string s)
274 bool fexists(string f)
276 int fh = fopen(f, FILE_READ);
283 // Databases (hash tables)
284 const float DB_BUCKETS = 8192;
285 void db_save(float db, string pFilename)
288 fh = fopen(pFilename, FILE_WRITE);
291 print(strcat("^1Can't write DB to ", pFilename));
295 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
296 for(i = 0; i < n; ++i)
297 fputs(fh, strcat(bufstr_get(db, i), "\n"));
306 float db_load(string pFilename)
308 float db, fh, i, j, n;
313 fh = fopen(pFilename, FILE_READ);
317 if(stof(l) == DB_BUCKETS)
320 while((l = fgets(fh)))
323 bufstr_set(db, i, l);
329 // different count of buckets, or a dump?
330 // need to reorganize the database then (SLOW)
332 // note: we also parse the first line (l) in case the DB file is
333 // missing the bucket count
336 n = tokenizebyseparator(l, "\\");
337 for(j = 2; j < n; j += 2)
338 db_put(db, argv(j-1), uri_unescape(argv(j)));
340 while((l = fgets(fh)));
346 void db_dump(float db, string pFilename)
348 float fh, i, j, n, m;
349 fh = fopen(pFilename, FILE_WRITE);
351 error(strcat("Can't dump DB to ", pFilename));
354 for(i = 0; i < n; ++i)
356 m = tokenizebyseparator(bufstr_get(db, i), "\\");
357 for(j = 2; j < m; j += 2)
358 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
363 void db_close(float db)
368 string db_get(float db, string pKey)
371 h = crc16(false, pKey) % DB_BUCKETS;
372 return uri_unescape(infoget(bufstr_get(db, h), pKey));
375 void db_put(float db, string pKey, string pValue)
378 h = crc16(false, pKey) % DB_BUCKETS;
379 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
386 db = db_load("foo.db");
387 print("LOADED. FILL...\n");
388 for(i = 0; i < DB_BUCKETS; ++i)
389 db_put(db, ftos(random()), "X");
390 print("FILLED. SAVE...\n");
391 db_save(db, "foo.db");
392 print("SAVED. CLOSE...\n");
397 // Multiline text file buffers
398 float buf_load(string pFilename)
405 fh = fopen(pFilename, FILE_READ);
412 while((l = fgets(fh)))
414 bufstr_set(buf, i, l);
421 void buf_save(float buf, string pFilename)
424 fh = fopen(pFilename, FILE_WRITE);
426 error(strcat("Can't write buf to ", pFilename));
427 n = buf_getsize(buf);
428 for(i = 0; i < n; ++i)
429 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
433 string format_time(float seconds)
435 float days, hours, minutes;
436 seconds = floor(seconds + 0.5);
437 days = floor(seconds / 864000);
438 seconds -= days * 864000;
439 hours = floor(seconds / 36000);
440 seconds -= hours * 36000;
441 minutes = floor(seconds / 600);
442 seconds -= minutes * 600;
444 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
446 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
449 string mmsss(float tenths)
453 tenths = floor(tenths + 0.5);
454 minutes = floor(tenths / 600);
455 tenths -= minutes * 600;
456 s = ftos(1000 + tenths);
457 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
460 string mmssss(float hundredths)
464 hundredths = floor(hundredths + 0.5);
465 minutes = floor(hundredths / 6000);
466 hundredths -= minutes * 6000;
467 s = ftos(10000 + hundredths);
468 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
471 string ScoreString(int pFlags, float pValue)
476 pValue = floor(pValue + 0.5); // round
478 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
480 else if(pFlags & SFL_RANK)
482 valstr = ftos(pValue);
484 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
485 valstr = strcat(valstr, "th");
486 else if(substring(valstr, l - 1, 1) == "1")
487 valstr = strcat(valstr, "st");
488 else if(substring(valstr, l - 1, 1) == "2")
489 valstr = strcat(valstr, "nd");
490 else if(substring(valstr, l - 1, 1) == "3")
491 valstr = strcat(valstr, "rd");
493 valstr = strcat(valstr, "th");
495 else if(pFlags & SFL_TIME)
496 valstr = TIME_ENCODED_TOSTRING(pValue);
498 valstr = ftos(pValue);
503 float dotproduct(vector a, vector b)
505 return a.x * b.x + a.y * b.y + a.z * b.z;
508 vector cross(vector a, vector b)
511 '1 0 0' * (a.y * b.z - a.z * b.y)
512 + '0 1 0' * (a.z * b.x - a.x * b.z)
513 + '0 0 1' * (a.x * b.y - a.y * b.x);
516 // compressed vector format:
517 // like MD3, just even shorter
518 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
519 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
520 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
521 // length = 2^(length_encoded/8) / 8
522 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
523 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
524 // the special value 0 indicates the zero vector
526 float lengthLogTable[128];
528 float invertLengthLog(float x)
532 if(x >= lengthLogTable[127])
534 if(x <= lengthLogTable[0])
542 m = floor((l + r) / 2);
543 if(lengthLogTable[m] < x)
549 // now: r is >=, l is <
550 float lerr = (x - lengthLogTable[l]);
551 float rerr = (lengthLogTable[r] - x);
557 vector decompressShortVector(int data)
562 float p = (data & 0xF000) / 0x1000;
563 float y = (data & 0x0F80) / 0x80;
564 int len = (data & 0x007F);
566 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
579 y = .19634954084936207740 * y;
580 p = .19634954084936207740 * p - 1.57079632679489661922;
581 out_x = cos(y) * cos(p);
582 out_y = sin(y) * cos(p);
586 //print("decompressed: ", vtos(out), "\n");
588 return out * lengthLogTable[len];
591 float compressShortVector(vector vec)
597 //print("compress: ", vtos(vec), "\n");
598 ang = vectoangles(vec);
602 if(ang.x < -90 && ang.x > +90)
603 error("BOGUS vectoangles");
604 //print("angles: ", vtos(ang), "\n");
606 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
615 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
616 len = invertLengthLog(vlen(vec));
618 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
620 return (p * 0x1000) + (y * 0x80) + len;
623 void compressShortVector_init()
626 float f = pow(2, 1/8);
628 for(i = 0; i < 128; ++i)
630 lengthLogTable[i] = l;
634 if(cvar("developer"))
636 print("Verifying vector compression table...\n");
637 for(i = 0x0F00; i < 0xFFFF; ++i)
638 if(i != compressShortVector(decompressShortVector(i)))
640 print("BROKEN vector compression: ", ftos(i));
641 print(" -> ", vtos(decompressShortVector(i)));
642 print(" -> ", ftos(compressShortVector(decompressShortVector(i))));
651 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
653 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
654 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
655 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
656 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
657 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
658 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
659 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
660 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
661 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
662 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
663 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
664 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
669 string fixPriorityList(string order, float from, float to, float subtract, float complete)
674 n = tokenize_console(order);
676 for(i = 0; i < n; ++i)
681 if(w >= from && w <= to)
682 neworder = strcat(neworder, ftos(w), " ");
686 if(w >= from && w <= to)
687 neworder = strcat(neworder, ftos(w), " ");
694 n = tokenize_console(neworder);
695 for(w = to; w >= from; --w)
697 for(i = 0; i < n; ++i)
698 if(stof(argv(i)) == w)
700 if(i == n) // not found
701 neworder = strcat(neworder, ftos(w), " ");
705 return substring(neworder, 0, strlen(neworder) - 1);
708 string mapPriorityList(string order, string(string) mapfunc)
713 n = tokenize_console(order);
715 for(i = 0; i < n; ++i)
716 neworder = strcat(neworder, mapfunc(argv(i)), " ");
718 return substring(neworder, 0, strlen(neworder) - 1);
721 string swapInPriorityList(string order, float i, float j)
726 n = tokenize_console(order);
728 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
731 for(w = 0; w < n; ++w)
734 s = strcat(s, argv(j), " ");
736 s = strcat(s, argv(i), " ");
738 s = strcat(s, argv(w), " ");
740 return substring(s, 0, strlen(s) - 1);
746 float cvar_value_issafe(string s)
748 if(strstrofs(s, "\"", 0) >= 0)
750 if(strstrofs(s, "\\", 0) >= 0)
752 if(strstrofs(s, ";", 0) >= 0)
754 if(strstrofs(s, "$", 0) >= 0)
756 if(strstrofs(s, "\r", 0) >= 0)
758 if(strstrofs(s, "\n", 0) >= 0)
764 void get_mi_min_max(float mode)
769 strunzone(mi_shortname);
770 mi_shortname = mapname;
771 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
772 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
773 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
774 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
775 mi_shortname = strzone(mi_shortname);
787 MapInfo_Get_ByName(mi_shortname, 0, 0);
788 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
790 mi_min = MapInfo_Map_mins;
791 mi_max = MapInfo_Map_maxs;
799 tracebox('1 0 0' * mi.x,
800 '0 1 0' * mi.y + '0 0 1' * mi.z,
801 '0 1 0' * ma.y + '0 0 1' * ma.z,
805 if(!trace_startsolid)
806 mi_min_x = trace_endpos.x;
808 tracebox('0 1 0' * mi.y,
809 '1 0 0' * mi.x + '0 0 1' * mi.z,
810 '1 0 0' * ma.x + '0 0 1' * ma.z,
814 if(!trace_startsolid)
815 mi_min_y = trace_endpos.y;
817 tracebox('0 0 1' * mi.z,
818 '1 0 0' * mi.x + '0 1 0' * mi.y,
819 '1 0 0' * ma.x + '0 1 0' * ma.y,
823 if(!trace_startsolid)
824 mi_min_z = trace_endpos.z;
826 tracebox('1 0 0' * ma.x,
827 '0 1 0' * mi.y + '0 0 1' * mi.z,
828 '0 1 0' * ma.y + '0 0 1' * ma.z,
832 if(!trace_startsolid)
833 mi_max_x = trace_endpos.x;
835 tracebox('0 1 0' * ma.y,
836 '1 0 0' * mi.x + '0 0 1' * mi.z,
837 '1 0 0' * ma.x + '0 0 1' * ma.z,
841 if(!trace_startsolid)
842 mi_max_y = trace_endpos.y;
844 tracebox('0 0 1' * ma.z,
845 '1 0 0' * mi.x + '0 1 0' * mi.y,
846 '1 0 0' * ma.x + '0 1 0' * ma.y,
850 if(!trace_startsolid)
851 mi_max_z = trace_endpos.z;
856 void get_mi_min_max_texcoords(float mode)
860 get_mi_min_max(mode);
865 // extend mi_picmax to get a square aspect ratio
866 // center the map in that area
867 extend = mi_picmax - mi_picmin;
868 if(extend.y > extend.x)
870 mi_picmin.x -= (extend.y - extend.x) * 0.5;
871 mi_picmax.x += (extend.y - extend.x) * 0.5;
875 mi_picmin.y -= (extend.x - extend.y) * 0.5;
876 mi_picmax.y += (extend.x - extend.y) * 0.5;
879 // add another some percent
880 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
884 // calculate the texcoords
885 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
886 // first the two corners of the origin
887 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
888 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
889 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
890 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
891 // then the other corners
892 mi_pictexcoord1_x = mi_pictexcoord0_x;
893 mi_pictexcoord1_y = mi_pictexcoord2_y;
894 mi_pictexcoord3_x = mi_pictexcoord2_x;
895 mi_pictexcoord3_y = mi_pictexcoord0_y;
899 float cvar_settemp(string tmp_cvar, string tmp_value)
901 float created_saved_value;
904 created_saved_value = 0;
906 if (!(tmp_cvar || tmp_value))
908 dprint("Error: Invalid usage of cvar_settemp(string, string); !\n");
912 if(!cvar_type(tmp_cvar))
914 printf("Error: cvar %s doesn't exist!\n", tmp_cvar);
918 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
919 if(e.netname == tmp_cvar)
920 created_saved_value = -1; // skip creation
922 if(created_saved_value != -1)
924 // creating a new entity to keep track of this cvar
926 e.classname = "saved_cvar_value";
927 e.netname = strzone(tmp_cvar);
928 e.message = strzone(cvar_string(tmp_cvar));
929 created_saved_value = 1;
932 // update the cvar to the value given
933 cvar_set(tmp_cvar, tmp_value);
935 return created_saved_value;
938 float cvar_settemp_restore()
942 while((e = find(e, classname, "saved_cvar_value")))
944 if(cvar_type(e.netname))
946 cvar_set(e.netname, e.message);
951 printf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
957 float almost_equals(float a, float b)
960 eps = (max(a, -a) + max(b, -b)) * 0.001;
961 if(a - b < eps && b - a < eps)
966 float almost_in_bounds(float a, float b, float c)
969 eps = (max(a, -a) + max(c, -c)) * 0.001;
972 return b == median(a - eps, b, c + eps);
975 float power2of(float e)
979 float log2of(float x)
981 // NOTE: generated code
1054 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1058 else if(ma == rgb.x)
1061 return (rgb.y - rgb.z) / (ma - mi);
1063 return (rgb.y - rgb.z) / (ma - mi) + 6;
1065 else if(ma == rgb.y)
1066 return (rgb.z - rgb.x) / (ma - mi) + 2;
1067 else // if(ma == rgb_z)
1068 return (rgb.x - rgb.y) / (ma - mi) + 4;
1071 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1075 hue -= 6 * floor(hue / 6);
1077 //else if(ma == rgb_x)
1078 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1082 rgb_y = hue * (ma - mi) + mi;
1085 //else if(ma == rgb_y)
1086 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1089 rgb_x = (2 - hue) * (ma - mi) + mi;
1097 rgb_z = (hue - 2) * (ma - mi) + mi;
1099 //else // if(ma == rgb_z)
1100 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1104 rgb_y = (4 - hue) * (ma - mi) + mi;
1109 rgb_x = (hue - 4) * (ma - mi) + mi;
1113 //else if(ma == rgb_x)
1114 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1115 else // if(hue <= 6)
1119 rgb_z = (6 - hue) * (ma - mi) + mi;
1125 vector rgb_to_hsv(vector rgb)
1130 mi = min(rgb.x, rgb.y, rgb.z);
1131 ma = max(rgb.x, rgb.y, rgb.z);
1133 hsv_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1144 vector hsv_to_rgb(vector hsv)
1146 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1149 vector rgb_to_hsl(vector rgb)
1154 mi = min(rgb.x, rgb.y, rgb.z);
1155 ma = max(rgb.x, rgb.y, rgb.z);
1157 hsl_x = rgb_mi_ma_to_hue(rgb, mi, ma);
1159 hsl_z = 0.5 * (mi + ma);
1162 else if(hsl.z <= 0.5)
1163 hsl_y = (ma - mi) / (2*hsl.z);
1164 else // if(hsl_z > 0.5)
1165 hsl_y = (ma - mi) / (2 - 2*hsl.z);
1170 vector hsl_to_rgb(vector hsl)
1172 float mi, ma, maminusmi;
1175 maminusmi = hsl.y * 2 * hsl.z;
1177 maminusmi = hsl.y * (2 - 2 * hsl.z);
1179 // hsl_z = 0.5 * mi + 0.5 * ma
1180 // maminusmi = - mi + ma
1181 mi = hsl.z - 0.5 * maminusmi;
1182 ma = hsl.z + 0.5 * maminusmi;
1184 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1187 string rgb_to_hexcolor(vector rgb)
1192 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1193 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1194 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1198 // requires that m2>m1 in all coordinates, and that m4>m3
1199 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;}
1201 // requires the same, but is a stronger condition
1202 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;}
1207 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1210 // The following function is SLOW.
1211 // For your safety and for the protection of those around you...
1212 // DO NOT CALL THIS AT HOME.
1213 // No really, don't.
1214 if(w(theText, theSize) <= maxWidth)
1215 return strlen(theText); // yeah!
1217 // binary search for right place to cut string
1219 float left, right, middle; // this always works
1221 right = strlen(theText); // this always fails
1224 middle = floor((left + right) / 2);
1225 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1230 while(left < right - 1);
1232 if(w("^7", theSize) == 0) // detect color codes support in the width function
1234 // NOTE: when color codes are involved, this binary search is,
1235 // mathematically, BROKEN. However, it is obviously guaranteed to
1236 // terminate, as the range still halves each time - but nevertheless, it is
1237 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1238 // range, and "right" is outside).
1240 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1241 // and decrease left on the basis of the chars detected of the truncated tag
1242 // Even if the ^xrgb tag is not complete/correct, left is decreased
1243 // (sometimes too much but with a correct result)
1244 // it fixes also ^[0-9]
1245 while(left >= 1 && substring(theText, left-1, 1) == "^")
1248 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1250 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1252 ch = str2chr(theText, left-1);
1253 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1256 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1258 ch = str2chr(theText, left-2);
1259 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1261 ch = str2chr(theText, left-1);
1262 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1271 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1274 // The following function is SLOW.
1275 // For your safety and for the protection of those around you...
1276 // DO NOT CALL THIS AT HOME.
1277 // No really, don't.
1278 if(w(theText) <= maxWidth)
1279 return strlen(theText); // yeah!
1281 // binary search for right place to cut string
1283 float left, right, middle; // this always works
1285 right = strlen(theText); // this always fails
1288 middle = floor((left + right) / 2);
1289 if(w(substring(theText, 0, middle)) <= maxWidth)
1294 while(left < right - 1);
1296 if(w("^7") == 0) // detect color codes support in the width function
1298 // NOTE: when color codes are involved, this binary search is,
1299 // mathematically, BROKEN. However, it is obviously guaranteed to
1300 // terminate, as the range still halves each time - but nevertheless, it is
1301 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1302 // range, and "right" is outside).
1304 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1305 // and decrease left on the basis of the chars detected of the truncated tag
1306 // Even if the ^xrgb tag is not complete/correct, left is decreased
1307 // (sometimes too much but with a correct result)
1308 // it fixes also ^[0-9]
1309 while(left >= 1 && substring(theText, left-1, 1) == "^")
1312 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1314 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1316 ch = str2chr(theText, left-1);
1317 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1320 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1322 ch = str2chr(theText, left-2);
1323 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1325 ch = str2chr(theText, left-1);
1326 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1335 string find_last_color_code(string s)
1337 int start = strstrofs(s, "^", 0);
1338 if (start == -1) // no caret found
1340 int len = strlen(s)-1;
1342 for(i = len; i >= start; --i)
1344 if(substring(s, i, 1) != "^")
1348 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1351 // check if carets aren't all escaped
1355 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1356 return substring(s, i, 2);
1359 if(substring(s, i+1, 1) == "x")
1360 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1361 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1362 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1363 return substring(s, i, 5);
1365 i -= carets; // this also skips one char before the carets
1371 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1377 s = getWrappedLine_remaining;
1381 getWrappedLine_remaining = string_null;
1382 return s; // the line has no size ANYWAY, nothing would be displayed.
1385 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1386 if(cantake > 0 && cantake < strlen(s))
1389 while(take > 0 && substring(s, take, 1) != " ")
1393 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1394 if(getWrappedLine_remaining == "")
1395 getWrappedLine_remaining = string_null;
1396 else if (tw("^7", theFontSize) == 0)
1397 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1398 return substring(s, 0, cantake);
1402 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1403 if(getWrappedLine_remaining == "")
1404 getWrappedLine_remaining = string_null;
1405 else if (tw("^7", theFontSize) == 0)
1406 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1407 return substring(s, 0, take);
1412 getWrappedLine_remaining = string_null;
1417 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1423 s = getWrappedLine_remaining;
1427 getWrappedLine_remaining = string_null;
1428 return s; // the line has no size ANYWAY, nothing would be displayed.
1431 cantake = textLengthUpToLength(s, w, tw);
1432 if(cantake > 0 && cantake < strlen(s))
1435 while(take > 0 && substring(s, take, 1) != " ")
1439 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1440 if(getWrappedLine_remaining == "")
1441 getWrappedLine_remaining = string_null;
1442 else if (tw("^7") == 0)
1443 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1444 return substring(s, 0, cantake);
1448 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1449 if(getWrappedLine_remaining == "")
1450 getWrappedLine_remaining = string_null;
1451 else if (tw("^7") == 0)
1452 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1453 return substring(s, 0, take);
1458 getWrappedLine_remaining = string_null;
1463 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1465 if(tw(theText, theFontSize) <= maxWidth)
1468 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1471 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1473 if(tw(theText) <= maxWidth)
1476 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1479 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1481 string subpattern, subpattern2, subpattern3, subpattern4;
1482 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1484 subpattern2 = ",teams,";
1486 subpattern2 = ",noteams,";
1488 subpattern3 = ",teamspawns,";
1490 subpattern3 = ",noteamspawns,";
1491 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1492 subpattern4 = ",race,";
1494 subpattern4 = string_null;
1496 if(substring(pattern, 0, 1) == "-")
1498 pattern = substring(pattern, 1, strlen(pattern) - 1);
1499 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1501 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1503 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1505 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1510 if(substring(pattern, 0, 1) == "+")
1511 pattern = substring(pattern, 1, strlen(pattern) - 1);
1512 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1513 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1514 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1518 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1525 void shuffle(float n, swapfunc_t swap, entity pass)
1528 for(i = 1; i < n; ++i)
1530 // swap i-th item at a random position from 0 to i
1531 // proof for even distribution:
1534 // item n+1 gets at any position with chance 1/(n+1)
1535 // all others will get their 1/n chance reduced by factor n/(n+1)
1536 // to be on place n+1, their chance will be 1/(n+1)
1537 // 1/n * n/(n+1) = 1/(n+1)
1539 j = floor(random() * (i + 1));
1545 string substring_range(string s, float b, float e)
1547 return substring(s, b, e - b);
1550 string swapwords(string str, float i, float j)
1553 string s1, s2, s3, s4, s5;
1554 float si, ei, sj, ej, s0, en;
1555 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1556 si = argv_start_index(i);
1557 sj = argv_start_index(j);
1558 ei = argv_end_index(i);
1559 ej = argv_end_index(j);
1560 s0 = argv_start_index(0);
1561 en = argv_end_index(n-1);
1562 s1 = substring_range(str, s0, si);
1563 s2 = substring_range(str, si, ei);
1564 s3 = substring_range(str, ei, sj);
1565 s4 = substring_range(str, sj, ej);
1566 s5 = substring_range(str, ej, en);
1567 return strcat(s1, s4, s3, s2, s5);
1570 string _shufflewords_str;
1571 void _shufflewords_swapfunc(float i, float j, entity pass)
1573 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1575 string shufflewords(string str)
1578 _shufflewords_str = str;
1579 n = tokenizebyseparator(str, " ");
1580 shuffle(n, _shufflewords_swapfunc, world);
1581 str = _shufflewords_str;
1582 _shufflewords_str = string_null;
1586 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1602 // actually, every number solves the equation!
1613 if(a > 0) // put the smaller solution first
1615 v_x = ((-b)-D) / (2*a);
1616 v_y = ((-b)+D) / (2*a);
1620 v_x = (-b+D) / (2*a);
1621 v_y = (-b-D) / (2*a);
1627 // complex solutions!
1640 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1644 // make origin and speed relative
1649 // now solve for ret, ret normalized:
1650 // eorg + t * evel == t * ret * spd
1651 // or, rather, solve for t:
1652 // |eorg + t * evel| == t * spd
1653 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1654 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1655 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1656 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1657 // q = (eorg * eorg) / (evel * evel - spd * spd)
1658 if(!solution.z) // no real solution
1661 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1662 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1663 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1664 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1665 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1666 // spd < |evel| * sin angle(evel, eorg)
1669 else if(solution.x > 0)
1671 // both solutions > 0: take the smaller one
1672 // happens if p < 0 and q > 0
1673 ret = normalize(eorg + solution.x * evel);
1675 else if(solution.y > 0)
1677 // one solution > 0: take the larger one
1678 // happens if q < 0 or q == 0 and p < 0
1679 ret = normalize(eorg + solution.y * evel);
1683 // no solution > 0: reject
1684 // happens if p > 0 and q >= 0
1685 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1686 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1691 // "Enemy is moving away from me at more than spd"
1695 // NOTE: we always got a solution if spd > |evel|
1697 if(newton_style == 2)
1698 ret = normalize(ret * spd + myvel);
1703 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1708 if(newton_style == 2)
1710 // true Newtonian projectiles with automatic aim adjustment
1712 // solve: |outspeed * mydir - myvel| = spd
1713 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1714 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1718 // myvel^2 - (mydir * myvel)^2 > spd^2
1719 // velocity without mydir component > spd
1720 // fire at smallest possible spd that works?
1721 // |(mydir * myvel) * myvel - myvel| = spd
1723 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1727 outspeed = solution.y; // the larger one
1730 //outspeed = 0; // slowest possible shot
1731 outspeed = solution.x; // the real part (that is, the average!)
1732 //dprint("impossible shot, adjusting\n");
1735 outspeed = bound(spd * mi, outspeed, spd * ma);
1736 return mydir * outspeed;
1740 return myvel + spd * mydir;
1743 float compressShotOrigin(vector v)
1747 y = rint(v.y * 4) + 128;
1748 z = rint(v.z * 4) + 128;
1749 if(x > 255 || x < 0)
1751 print("shot origin ", vtos(v), " x out of bounds\n");
1752 x = bound(0, x, 255);
1754 if(y > 255 || y < 0)
1756 print("shot origin ", vtos(v), " y out of bounds\n");
1757 y = bound(0, y, 255);
1759 if(z > 255 || z < 0)
1761 print("shot origin ", vtos(v), " z out of bounds\n");
1762 z = bound(0, z, 255);
1764 return x * 0x10000 + y * 0x100 + z;
1766 vector decompressShotOrigin(int f)
1769 v_x = ((f & 0xFF0000) / 0x10000) / 2;
1770 v_y = ((f & 0xFF00) / 0x100 - 128) / 4;
1771 v_z = ((f & 0xFF) - 128) / 4;
1775 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1777 float start, end, root, child;
1780 start = floor((n - 2) / 2);
1783 // siftdown(start, count-1);
1785 while(root * 2 + 1 <= n-1)
1787 child = root * 2 + 1;
1789 if(cmp(child, child+1, pass) < 0)
1791 if(cmp(root, child, pass) < 0)
1793 swap(root, child, pass);
1809 // siftdown(0, end);
1811 while(root * 2 + 1 <= end)
1813 child = root * 2 + 1;
1814 if(child < end && cmp(child, child+1, pass) < 0)
1816 if(cmp(root, child, pass) < 0)
1818 swap(root, child, pass);
1828 void RandomSelection_Init()
1830 RandomSelection_totalweight = 0;
1831 RandomSelection_chosen_ent = world;
1832 RandomSelection_chosen_float = 0;
1833 RandomSelection_chosen_string = string_null;
1834 RandomSelection_best_priority = -1;
1836 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1838 if(priority > RandomSelection_best_priority)
1840 RandomSelection_best_priority = priority;
1841 RandomSelection_chosen_ent = e;
1842 RandomSelection_chosen_float = f;
1843 RandomSelection_chosen_string = s;
1844 RandomSelection_totalweight = weight;
1846 else if(priority == RandomSelection_best_priority)
1848 RandomSelection_totalweight += weight;
1849 if(random() * RandomSelection_totalweight <= weight)
1851 RandomSelection_chosen_ent = e;
1852 RandomSelection_chosen_float = f;
1853 RandomSelection_chosen_string = s;
1859 vector healtharmor_maxdamage(float h, float a, float armorblock, float deathtype)
1861 // NOTE: we'll always choose the SMALLER value...
1862 float healthdamage, armordamage, armorideal;
1863 if (deathtype == DEATH_DROWN) // Why should armor help here...
1866 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1867 armordamage = a + (h - 1); // damage we can take if we could use more armor
1868 armorideal = healthdamage * armorblock;
1870 if(armordamage < healthdamage)
1883 vector healtharmor_applydamage(float a, float armorblock, float deathtype, float damage)
1886 if (deathtype == DEATH_DROWN) // Why should armor help here...
1888 v_y = bound(0, damage * armorblock, a); // save
1889 v_x = bound(0, damage - v.y, damage); // take
1895 string getcurrentmod()
1899 m = cvar_string("fs_gamedir");
1900 n = tokenize_console(m);
1912 v = ReadShort() * 256; // note: this is signed
1913 v += ReadByte(); // note: this is unsigned
1916 vector ReadInt48_t()
1919 v_x = ReadInt24_t();
1920 v_y = ReadInt24_t();
1924 vector ReadInt72_t()
1927 v_x = ReadInt24_t();
1928 v_y = ReadInt24_t();
1929 v_z = ReadInt24_t();
1933 void WriteInt24_t(float dst, float val)
1936 WriteShort(dst, (v = floor(val / 256)));
1937 WriteByte(dst, val - v * 256); // 0..255
1939 void WriteInt48_t(float dst, vector val)
1941 WriteInt24_t(dst, val.x);
1942 WriteInt24_t(dst, val.y);
1944 void WriteInt72_t(float dst, vector val)
1946 WriteInt24_t(dst, val.x);
1947 WriteInt24_t(dst, val.y);
1948 WriteInt24_t(dst, val.z);
1953 float float2range11(float f)
1955 // continuous function mapping all reals into -1..1
1956 return f / (fabs(f) + 1);
1959 float float2range01(float f)
1961 // continuous function mapping all reals into 0..1
1962 return 0.5 + 0.5 * float2range11(f);
1965 // from the GNU Scientific Library
1966 float gsl_ran_gaussian_lastvalue;
1967 float gsl_ran_gaussian_lastvalue_set;
1968 float gsl_ran_gaussian(float sigma)
1971 if(gsl_ran_gaussian_lastvalue_set)
1973 gsl_ran_gaussian_lastvalue_set = 0;
1974 return sigma * gsl_ran_gaussian_lastvalue;
1978 a = random() * 2 * M_PI;
1979 b = sqrt(-2 * log(random()));
1980 gsl_ran_gaussian_lastvalue = cos(a) * b;
1981 gsl_ran_gaussian_lastvalue_set = 1;
1982 return sigma * sin(a) * b;
1986 string car(string s)
1989 o = strstrofs(s, " ", 0);
1992 return substring(s, 0, o);
1994 string cdr(string s)
1997 o = strstrofs(s, " ", 0);
2000 return substring(s, o + 1, strlen(s) - (o + 1));
2002 float matchacl(string acl, string str)
2009 t = car(acl); acl = cdr(acl);
2012 if(substring(t, 0, 1) == "-")
2015 t = substring(t, 1, strlen(t) - 1);
2017 else if(substring(t, 0, 1) == "+")
2018 t = substring(t, 1, strlen(t) - 1);
2020 if(substring(t, -1, 1) == "*")
2022 t = substring(t, 0, strlen(t) - 1);
2023 s = substring(str, 0, strlen(t));
2035 float startsWith(string haystack, string needle)
2037 return substring(haystack, 0, strlen(needle)) == needle;
2039 float startsWithNocase(string haystack, string needle)
2041 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2044 string get_model_datafilename(string m, float sk, string fil)
2049 m = "models/player/*_";
2051 m = strcat(m, ftos(sk));
2054 return strcat(m, ".", fil);
2057 float get_model_parameters(string m, float sk)
2062 get_model_parameters_modelname = string_null;
2063 get_model_parameters_modelskin = -1;
2064 get_model_parameters_name = string_null;
2065 get_model_parameters_species = -1;
2066 get_model_parameters_sex = string_null;
2067 get_model_parameters_weight = -1;
2068 get_model_parameters_age = -1;
2069 get_model_parameters_desc = string_null;
2070 get_model_parameters_bone_upperbody = string_null;
2071 get_model_parameters_bone_weapon = string_null;
2072 for(i = 0; i < MAX_AIM_BONES; ++i)
2074 get_model_parameters_bone_aim[i] = string_null;
2075 get_model_parameters_bone_aimweight[i] = 0;
2077 get_model_parameters_fixbone = 0;
2082 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2083 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2087 if(substring(m, -4, -1) != ".txt")
2089 if(substring(m, -6, 1) != "_")
2091 sk = stof(substring(m, -5, 1));
2092 m = substring(m, 0, -7);
2095 fn = get_model_datafilename(m, sk, "txt");
2096 fh = fopen(fn, FILE_READ);
2100 fn = get_model_datafilename(m, sk, "txt");
2101 fh = fopen(fn, FILE_READ);
2106 get_model_parameters_modelname = m;
2107 get_model_parameters_modelskin = sk;
2108 while((s = fgets(fh)))
2111 break; // next lines will be description
2115 get_model_parameters_name = s;
2119 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2120 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2121 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2122 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2123 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2124 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2125 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2128 get_model_parameters_sex = s;
2130 get_model_parameters_weight = stof(s);
2132 get_model_parameters_age = stof(s);
2133 if(c == "description")
2134 get_model_parameters_description = s;
2135 if(c == "bone_upperbody")
2136 get_model_parameters_bone_upperbody = s;
2137 if(c == "bone_weapon")
2138 get_model_parameters_bone_weapon = s;
2139 for(i = 0; i < MAX_AIM_BONES; ++i)
2140 if(c == strcat("bone_aim", ftos(i)))
2142 get_model_parameters_bone_aimweight[i] = stof(car(s));
2143 get_model_parameters_bone_aim[i] = cdr(s);
2146 get_model_parameters_fixbone = stof(s);
2149 while((s = fgets(fh)))
2151 if(get_model_parameters_desc)
2152 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2154 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2162 vector vec2(vector v)
2169 vector NearestPointOnBox(entity box, vector org)
2171 vector m1, m2, nearest;
2173 m1 = box.mins + box.origin;
2174 m2 = box.maxs + box.origin;
2176 nearest_x = bound(m1_x, org.x, m2_x);
2177 nearest_y = bound(m1_y, org.y, m2_y);
2178 nearest_z = bound(m1_z, org.z, m2_z);
2184 float vercmp_recursive(string v1, string v2)
2190 dot1 = strstrofs(v1, ".", 0);
2191 dot2 = strstrofs(v2, ".", 0);
2195 s1 = substring(v1, 0, dot1);
2199 s2 = substring(v2, 0, dot2);
2201 r = stof(s1) - stof(s2);
2205 r = strcasecmp(s1, s2);
2218 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2221 float vercmp(string v1, string v2)
2223 if(strcasecmp(v1, v2) == 0) // early out check
2232 return vercmp_recursive(v1, v2);
2235 float u8_strsize(string s)
2255 // translation helpers
2256 string language_filename(string s)
2261 if(fn == "" || fn == "dump")
2263 fn = strcat(s, ".", fn);
2264 if((fh = fopen(fn, FILE_READ)) >= 0)
2271 string CTX(string s)
2273 float p = strstrofs(s, "^", 0);
2276 return substring(s, p+1, -1);
2279 // x-encoding (encoding as zero length invisible string)
2280 const string XENCODE_2 = "xX";
2281 const string XENCODE_22 = "0123456789abcdefABCDEF";
2282 string xencode(int f)
2285 d = f % 22; f = floor(f / 22);
2286 c = f % 22; f = floor(f / 22);
2287 b = f % 22; f = floor(f / 22);
2288 a = f % 2; // f = floor(f / 2);
2291 substring(XENCODE_2, a, 1),
2292 substring(XENCODE_22, b, 1),
2293 substring(XENCODE_22, c, 1),
2294 substring(XENCODE_22, d, 1)
2297 float xdecode(string s)
2300 if(substring(s, 0, 1) != "^")
2304 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2305 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2306 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2307 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2308 if(a < 0 || b < 0 || c < 0 || d < 0)
2310 return ((a * 22 + b) * 22 + c) * 22 + d;
2313 float lowestbit(int f)
2324 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2326 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2329 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2332 // escape the string to make it safe for consoles
2333 string MakeConsoleSafe(string input)
2335 input = strreplace("\n", "", input);
2336 input = strreplace("\\", "\\\\", input);
2337 input = strreplace("$", "$$", input);
2338 input = strreplace("\"", "\\\"", input);
2343 // get true/false value of a string with multiple different inputs
2344 float InterpretBoolean(string input)
2346 switch(strtolower(input))
2358 default: return stof(input);
2364 entity ReadCSQCEntity()
2370 return findfloat(world, entnum, f);
2374 float shutdown_running;
2379 void CSQC_Shutdown()
2385 if(shutdown_running)
2387 print("Recursive shutdown detected! Only restoring cvars...\n");
2391 shutdown_running = 1;
2394 cvar_settemp_restore(); // this must be done LAST, but in any case
2397 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2398 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2399 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2400 // this will use the value:
2402 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2403 // accuracy at x is 1/derivative, i.e.
2404 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2406 void WriteApproxPastTime(float dst, float t)
2408 float dt = time - t;
2410 // warning: this is approximate; do not resend when you don't have to!
2411 // be careful with sendflags here!
2412 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2415 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2418 dt = rint(bound(0, dt, 255));
2424 float ReadApproxPastTime()
2426 float dt = ReadByte();
2428 // map from range...PPROXPASTTIME_MAX / 256
2429 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2431 return servertime - dt;
2436 .float skeleton_bones_index;
2437 void Skeleton_SetBones(entity e)
2439 // set skeleton_bones to the total number of bones on the model
2440 if(e.skeleton_bones_index == e.modelindex)
2441 return; // same model, nothing to update
2444 skelindex = skel_create(e.modelindex);
2445 e.skeleton_bones = skel_get_numbones(skelindex);
2446 skel_delete(skelindex);
2447 e.skeleton_bones_index = e.modelindex;
2451 string to_execute_next_frame;
2452 void execute_next_frame()
2454 if(to_execute_next_frame)
2456 localcmd("\n", to_execute_next_frame, "\n");
2457 strunzone(to_execute_next_frame);
2458 to_execute_next_frame = string_null;
2461 void queue_to_execute_next_frame(string s)
2463 if(to_execute_next_frame)
2465 s = strcat(s, "\n", to_execute_next_frame);
2466 strunzone(to_execute_next_frame);
2468 to_execute_next_frame = strzone(s);
2471 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2474 ((( startspeedfactor + endspeedfactor - 2
2475 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2476 ) * x + startspeedfactor
2480 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2482 if(startspeedfactor < 0 || endspeedfactor < 0)
2486 // if this is the case, the possible zeros of the first derivative are outside
2488 We can calculate this condition as condition
2493 // better, see below:
2494 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2497 // if this is the case, the first derivative has no zeros at all
2498 float se = startspeedfactor + endspeedfactor;
2499 float s_e = startspeedfactor - endspeedfactor;
2500 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2503 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2504 // we also get s_e <= 6 - se
2505 // 3 * (se - 4)^2 + (6 - se)^2
2506 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2507 // Therefore, above "better" check works!
2511 // known good cases:
2519 // (3.5, [0.2..2.3])
2524 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2526 s + e - 2 == 0: no inflection
2529 0 < inflection < 1 if:
2530 0 < 2s + e - 3 < 3s + 3e - 6
2531 2s + e > 3 and 2e + s > 3
2534 0 < inflection < 1 if:
2535 0 > 2s + e - 3 > 3s + 3e - 6
2536 2s + e < 3 and 2e + s < 3
2538 Therefore: there is an inflection point iff:
2539 e outside (3 - s)/2 .. 3 - s*2
2541 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)
2545 .float FindConnectedComponent_processing;
2546 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2548 entity queue_start, queue_end;
2550 // we build a queue of to-be-processed entities.
2551 // queue_start is the next entity to be checked for neighbors
2552 // queue_end is the last entity added
2554 if(e.FindConnectedComponent_processing)
2555 error("recursion or broken cleanup");
2557 // start with a 1-element queue
2558 queue_start = queue_end = e;
2559 queue_end.fld = world;
2560 queue_end.FindConnectedComponent_processing = 1;
2562 // for each queued item:
2563 for(0; queue_start; queue_start = queue_start.fld)
2565 // find all neighbors of queue_start
2567 for(t = world; (t = nxt(t, queue_start, pass)); )
2569 if(t.FindConnectedComponent_processing)
2571 if(iscon(t, queue_start, pass))
2573 // it is connected? ADD IT. It will look for neighbors soon too.
2576 queue_end.fld = world;
2577 queue_end.FindConnectedComponent_processing = 1;
2583 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2584 queue_start.FindConnectedComponent_processing = 0;
2588 vector combine_to_vector(float x, float y, float z)
2590 vector result; result_x = x; result_y = y; result_z = z;
2594 vector get_corner_position(entity box, float corner)
2598 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2599 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2600 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2601 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2602 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2603 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2604 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2605 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2606 default: return '0 0 0';
2611 // todo: this sucks, lets find a better way to do backtraces?
2612 void backtrace(string msg)
2616 dev = autocvar_developer;
2617 war = autocvar_prvm_backtraceforwarnings;
2619 dev = cvar("developer");
2620 war = cvar("prvm_backtraceforwarnings");
2622 cvar_set("developer", "1");
2623 cvar_set("prvm_backtraceforwarnings", "1");
2625 print("--- CUT HERE ---\nWARNING: ");
2628 remove(world); // isn't there any better way to cause a backtrace?
2629 print("\n--- CUT UNTIL HERE ---\n");
2630 cvar_set("developer", ftos(dev));
2631 cvar_set("prvm_backtraceforwarnings", ftos(war));
2634 // color code replace, place inside of sprintf and parse the string
2635 string CCR(string input)
2637 // See the autocvar declarations in util.qh for default values
2639 // foreground/normal colors
2640 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2641 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2642 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2643 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2646 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2647 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2648 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2650 // background colors
2651 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2652 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2656 vector vec3(float x, float y, float z)
2666 vector animfixfps(entity e, vector a, vector b)
2668 // multi-frame anim: keep as-is
2672 dur = frameduration(e.modelindex, a.x);
2676 dur = frameduration(e.modelindex, a.x);
2686 void dedicated_print(string input) // print(), but only print if the server is not local
2688 if(server_is_dedicated) { print(input); }
2693 float Announcer_PickNumber(float type, float num)
2701 case 10: return ANNCE_NUM_GAMESTART_10;
2702 case 9: return ANNCE_NUM_GAMESTART_9;
2703 case 8: return ANNCE_NUM_GAMESTART_8;
2704 case 7: return ANNCE_NUM_GAMESTART_7;
2705 case 6: return ANNCE_NUM_GAMESTART_6;
2706 case 5: return ANNCE_NUM_GAMESTART_5;
2707 case 4: return ANNCE_NUM_GAMESTART_4;
2708 case 3: return ANNCE_NUM_GAMESTART_3;
2709 case 2: return ANNCE_NUM_GAMESTART_2;
2710 case 1: return ANNCE_NUM_GAMESTART_1;
2718 case 10: return ANNCE_NUM_IDLE_10;
2719 case 9: return ANNCE_NUM_IDLE_9;
2720 case 8: return ANNCE_NUM_IDLE_8;
2721 case 7: return ANNCE_NUM_IDLE_7;
2722 case 6: return ANNCE_NUM_IDLE_6;
2723 case 5: return ANNCE_NUM_IDLE_5;
2724 case 4: return ANNCE_NUM_IDLE_4;
2725 case 3: return ANNCE_NUM_IDLE_3;
2726 case 2: return ANNCE_NUM_IDLE_2;
2727 case 1: return ANNCE_NUM_IDLE_1;
2735 case 10: return ANNCE_NUM_KILL_10;
2736 case 9: return ANNCE_NUM_KILL_9;
2737 case 8: return ANNCE_NUM_KILL_8;
2738 case 7: return ANNCE_NUM_KILL_7;
2739 case 6: return ANNCE_NUM_KILL_6;
2740 case 5: return ANNCE_NUM_KILL_5;
2741 case 4: return ANNCE_NUM_KILL_4;
2742 case 3: return ANNCE_NUM_KILL_3;
2743 case 2: return ANNCE_NUM_KILL_2;
2744 case 1: return ANNCE_NUM_KILL_1;
2752 case 10: return ANNCE_NUM_RESPAWN_10;
2753 case 9: return ANNCE_NUM_RESPAWN_9;
2754 case 8: return ANNCE_NUM_RESPAWN_8;
2755 case 7: return ANNCE_NUM_RESPAWN_7;
2756 case 6: return ANNCE_NUM_RESPAWN_6;
2757 case 5: return ANNCE_NUM_RESPAWN_5;
2758 case 4: return ANNCE_NUM_RESPAWN_4;
2759 case 3: return ANNCE_NUM_RESPAWN_3;
2760 case 2: return ANNCE_NUM_RESPAWN_2;
2761 case 1: return ANNCE_NUM_RESPAWN_1;
2765 case CNT_ROUNDSTART:
2769 case 10: return ANNCE_NUM_ROUNDSTART_10;
2770 case 9: return ANNCE_NUM_ROUNDSTART_9;
2771 case 8: return ANNCE_NUM_ROUNDSTART_8;
2772 case 7: return ANNCE_NUM_ROUNDSTART_7;
2773 case 6: return ANNCE_NUM_ROUNDSTART_6;
2774 case 5: return ANNCE_NUM_ROUNDSTART_5;
2775 case 4: return ANNCE_NUM_ROUNDSTART_4;
2776 case 3: return ANNCE_NUM_ROUNDSTART_3;
2777 case 2: return ANNCE_NUM_ROUNDSTART_2;
2778 case 1: return ANNCE_NUM_ROUNDSTART_1;
2786 case 10: return ANNCE_NUM_10;
2787 case 9: return ANNCE_NUM_9;
2788 case 8: return ANNCE_NUM_8;
2789 case 7: return ANNCE_NUM_7;
2790 case 6: return ANNCE_NUM_6;
2791 case 5: return ANNCE_NUM_5;
2792 case 4: return ANNCE_NUM_4;
2793 case 3: return ANNCE_NUM_3;
2794 case 2: return ANNCE_NUM_2;
2795 case 1: return ANNCE_NUM_1;
2800 return NOTIF_ABORT; // abort sending if none of these numbers were right
2805 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2807 switch(nativecontents)
2812 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2814 return DPCONTENTS_WATER;
2816 return DPCONTENTS_SLIME;
2818 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2820 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2825 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2827 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2828 return CONTENT_SOLID;
2829 if(supercontents & DPCONTENTS_SKY)
2831 if(supercontents & DPCONTENTS_LAVA)
2832 return CONTENT_LAVA;
2833 if(supercontents & DPCONTENTS_SLIME)
2834 return CONTENT_SLIME;
2835 if(supercontents & DPCONTENTS_WATER)
2836 return CONTENT_WATER;
2837 return CONTENT_EMPTY;
2841 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2844 (c - 2 * b + a) * (t * t) +
2849 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2852 (c - 2 * b + a) * (2 * t) +