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 int db_load(string pFilename)
308 float db, fh, i, j, n;
313 fh = fopen(pFilename, FILE_READ);
317 if(stof(l) == DB_BUCKETS)
320 while((l = fgets(fh)))
323 bufstr_set(db, i, l);
329 // different count of buckets, or a dump?
330 // need to reorganize the database then (SLOW)
332 // note: we also parse the first line (l) in case the DB file is
333 // missing the bucket count
336 n = tokenizebyseparator(l, "\\");
337 for(j = 2; j < n; j += 2)
338 db_put(db, argv(j-1), uri_unescape(argv(j)));
340 while((l = fgets(fh)));
346 void db_dump(float db, string pFilename)
348 float fh, i, j, n, m;
349 fh = fopen(pFilename, FILE_WRITE);
351 error(strcat("Can't dump DB to ", pFilename));
354 for(i = 0; i < n; ++i)
356 m = tokenizebyseparator(bufstr_get(db, i), "\\");
357 for(j = 2; j < m; j += 2)
358 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
363 void db_close(float db)
368 string db_get(float db, string pKey)
371 h = crc16(false, pKey) % DB_BUCKETS;
372 return uri_unescape(infoget(bufstr_get(db, h), pKey));
375 void db_put(float db, string pKey, string pValue)
378 h = crc16(false, pKey) % DB_BUCKETS;
379 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
386 db = db_load("foo.db");
387 print("LOADED. FILL...\n");
388 for(i = 0; i < DB_BUCKETS; ++i)
389 db_put(db, ftos(random()), "X");
390 print("FILLED. SAVE...\n");
391 db_save(db, "foo.db");
392 print("SAVED. CLOSE...\n");
397 // Multiline text file buffers
398 int buf_load(string pFilename)
405 fh = fopen(pFilename, FILE_READ);
412 while((l = fgets(fh)))
414 bufstr_set(buf, i, l);
421 void buf_save(float buf, string pFilename)
424 fh = fopen(pFilename, FILE_WRITE);
426 error(strcat("Can't write buf to ", pFilename));
427 n = buf_getsize(buf);
428 for(i = 0; i < n; ++i)
429 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
433 string format_time(float seconds)
435 float days, hours, minutes;
436 seconds = floor(seconds + 0.5);
437 days = floor(seconds / 864000);
438 seconds -= days * 864000;
439 hours = floor(seconds / 36000);
440 seconds -= hours * 36000;
441 minutes = floor(seconds / 600);
442 seconds -= minutes * 600;
444 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
446 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
449 string mmsss(float tenths)
453 tenths = floor(tenths + 0.5);
454 minutes = floor(tenths / 600);
455 tenths -= minutes * 600;
456 s = ftos(1000 + tenths);
457 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
460 string mmssss(float hundredths)
464 hundredths = floor(hundredths + 0.5);
465 minutes = floor(hundredths / 6000);
466 hundredths -= minutes * 6000;
467 s = ftos(10000 + hundredths);
468 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
471 string ScoreString(int pFlags, float pValue)
476 pValue = floor(pValue + 0.5); // round
478 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
480 else if(pFlags & SFL_RANK)
482 valstr = ftos(pValue);
484 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
485 valstr = strcat(valstr, "th");
486 else if(substring(valstr, l - 1, 1) == "1")
487 valstr = strcat(valstr, "st");
488 else if(substring(valstr, l - 1, 1) == "2")
489 valstr = strcat(valstr, "nd");
490 else if(substring(valstr, l - 1, 1) == "3")
491 valstr = strcat(valstr, "rd");
493 valstr = strcat(valstr, "th");
495 else if(pFlags & SFL_TIME)
496 valstr = TIME_ENCODED_TOSTRING(pValue);
498 valstr = ftos(pValue);
503 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);
1911 int v = ReadShort() * 256; // note: this is signed
1912 v += ReadByte(); // note: this is unsigned
1915 vector ReadInt48_t()
1918 v_x = ReadInt24_t();
1919 v_y = ReadInt24_t();
1923 vector ReadInt72_t()
1926 v_x = ReadInt24_t();
1927 v_y = ReadInt24_t();
1928 v_z = ReadInt24_t();
1932 void WriteInt24_t(float dst, float val)
1935 WriteShort(dst, (v = floor(val / 256)));
1936 WriteByte(dst, val - v * 256); // 0..255
1938 void WriteInt48_t(float dst, vector val)
1940 WriteInt24_t(dst, val.x);
1941 WriteInt24_t(dst, val.y);
1943 void WriteInt72_t(float dst, vector val)
1945 WriteInt24_t(dst, val.x);
1946 WriteInt24_t(dst, val.y);
1947 WriteInt24_t(dst, val.z);
1952 float float2range11(float f)
1954 // continuous function mapping all reals into -1..1
1955 return f / (fabs(f) + 1);
1958 float float2range01(float f)
1960 // continuous function mapping all reals into 0..1
1961 return 0.5 + 0.5 * float2range11(f);
1964 // from the GNU Scientific Library
1965 float gsl_ran_gaussian_lastvalue;
1966 float gsl_ran_gaussian_lastvalue_set;
1967 float gsl_ran_gaussian(float sigma)
1970 if(gsl_ran_gaussian_lastvalue_set)
1972 gsl_ran_gaussian_lastvalue_set = 0;
1973 return sigma * gsl_ran_gaussian_lastvalue;
1977 a = random() * 2 * M_PI;
1978 b = sqrt(-2 * log(random()));
1979 gsl_ran_gaussian_lastvalue = cos(a) * b;
1980 gsl_ran_gaussian_lastvalue_set = 1;
1981 return sigma * sin(a) * b;
1985 string car(string s)
1988 o = strstrofs(s, " ", 0);
1991 return substring(s, 0, o);
1993 string cdr(string s)
1996 o = strstrofs(s, " ", 0);
1999 return substring(s, o + 1, strlen(s) - (o + 1));
2001 float matchacl(string acl, string str)
2008 t = car(acl); acl = cdr(acl);
2011 if(substring(t, 0, 1) == "-")
2014 t = substring(t, 1, strlen(t) - 1);
2016 else if(substring(t, 0, 1) == "+")
2017 t = substring(t, 1, strlen(t) - 1);
2019 if(substring(t, -1, 1) == "*")
2021 t = substring(t, 0, strlen(t) - 1);
2022 s = substring(str, 0, strlen(t));
2034 float startsWith(string haystack, string needle)
2036 return substring(haystack, 0, strlen(needle)) == needle;
2038 float startsWithNocase(string haystack, string needle)
2040 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2043 string get_model_datafilename(string m, float sk, string fil)
2048 m = "models/player/*_";
2050 m = strcat(m, ftos(sk));
2053 return strcat(m, ".", fil);
2056 float get_model_parameters(string m, float sk)
2061 get_model_parameters_modelname = string_null;
2062 get_model_parameters_modelskin = -1;
2063 get_model_parameters_name = string_null;
2064 get_model_parameters_species = -1;
2065 get_model_parameters_sex = string_null;
2066 get_model_parameters_weight = -1;
2067 get_model_parameters_age = -1;
2068 get_model_parameters_desc = string_null;
2069 get_model_parameters_bone_upperbody = string_null;
2070 get_model_parameters_bone_weapon = string_null;
2071 for(i = 0; i < MAX_AIM_BONES; ++i)
2073 get_model_parameters_bone_aim[i] = string_null;
2074 get_model_parameters_bone_aimweight[i] = 0;
2076 get_model_parameters_fixbone = 0;
2081 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2082 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2086 if(substring(m, -4, -1) != ".txt")
2088 if(substring(m, -6, 1) != "_")
2090 sk = stof(substring(m, -5, 1));
2091 m = substring(m, 0, -7);
2094 fn = get_model_datafilename(m, sk, "txt");
2095 fh = fopen(fn, FILE_READ);
2099 fn = get_model_datafilename(m, sk, "txt");
2100 fh = fopen(fn, FILE_READ);
2105 get_model_parameters_modelname = m;
2106 get_model_parameters_modelskin = sk;
2107 while((s = fgets(fh)))
2110 break; // next lines will be description
2114 get_model_parameters_name = s;
2118 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2119 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2120 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2121 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2122 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2123 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2124 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2127 get_model_parameters_sex = s;
2129 get_model_parameters_weight = stof(s);
2131 get_model_parameters_age = stof(s);
2132 if(c == "description")
2133 get_model_parameters_description = s;
2134 if(c == "bone_upperbody")
2135 get_model_parameters_bone_upperbody = s;
2136 if(c == "bone_weapon")
2137 get_model_parameters_bone_weapon = s;
2138 for(i = 0; i < MAX_AIM_BONES; ++i)
2139 if(c == strcat("bone_aim", ftos(i)))
2141 get_model_parameters_bone_aimweight[i] = stof(car(s));
2142 get_model_parameters_bone_aim[i] = cdr(s);
2145 get_model_parameters_fixbone = stof(s);
2148 while((s = fgets(fh)))
2150 if(get_model_parameters_desc)
2151 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2153 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2161 vector vec2(vector v)
2168 vector NearestPointOnBox(entity box, vector org)
2170 vector m1, m2, nearest;
2172 m1 = box.mins + box.origin;
2173 m2 = box.maxs + box.origin;
2175 nearest_x = bound(m1_x, org.x, m2_x);
2176 nearest_y = bound(m1_y, org.y, m2_y);
2177 nearest_z = bound(m1_z, org.z, m2_z);
2183 float vercmp_recursive(string v1, string v2)
2189 dot1 = strstrofs(v1, ".", 0);
2190 dot2 = strstrofs(v2, ".", 0);
2194 s1 = substring(v1, 0, dot1);
2198 s2 = substring(v2, 0, dot2);
2200 r = stof(s1) - stof(s2);
2204 r = strcasecmp(s1, s2);
2217 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2220 float vercmp(string v1, string v2)
2222 if(strcasecmp(v1, v2) == 0) // early out check
2231 return vercmp_recursive(v1, v2);
2234 float u8_strsize(string s)
2254 // translation helpers
2255 string language_filename(string s)
2260 if(fn == "" || fn == "dump")
2262 fn = strcat(s, ".", fn);
2263 if((fh = fopen(fn, FILE_READ)) >= 0)
2270 string CTX(string s)
2272 float p = strstrofs(s, "^", 0);
2275 return substring(s, p+1, -1);
2278 // x-encoding (encoding as zero length invisible string)
2279 const string XENCODE_2 = "xX";
2280 const string XENCODE_22 = "0123456789abcdefABCDEF";
2281 string xencode(int f)
2284 d = f % 22; f = floor(f / 22);
2285 c = f % 22; f = floor(f / 22);
2286 b = f % 22; f = floor(f / 22);
2287 a = f % 2; // f = floor(f / 2);
2290 substring(XENCODE_2, a, 1),
2291 substring(XENCODE_22, b, 1),
2292 substring(XENCODE_22, c, 1),
2293 substring(XENCODE_22, d, 1)
2296 float xdecode(string s)
2299 if(substring(s, 0, 1) != "^")
2303 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2304 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2305 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2306 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2307 if(a < 0 || b < 0 || c < 0 || d < 0)
2309 return ((a * 22 + b) * 22 + c) * 22 + d;
2312 float lowestbit(int f)
2323 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2325 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2328 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2331 // escape the string to make it safe for consoles
2332 string MakeConsoleSafe(string input)
2334 input = strreplace("\n", "", input);
2335 input = strreplace("\\", "\\\\", input);
2336 input = strreplace("$", "$$", input);
2337 input = strreplace("\"", "\\\"", input);
2342 // get true/false value of a string with multiple different inputs
2343 float InterpretBoolean(string input)
2345 switch(strtolower(input))
2357 default: return stof(input);
2363 entity ReadCSQCEntity()
2365 int f = ReadShort();
2368 return findfloat(world, entnum, f);
2372 float shutdown_running;
2377 void CSQC_Shutdown()
2383 if(shutdown_running)
2385 print("Recursive shutdown detected! Only restoring cvars...\n");
2389 shutdown_running = 1;
2392 cvar_settemp_restore(); // this must be done LAST, but in any case
2395 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2396 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2397 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2398 // this will use the value:
2400 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2401 // accuracy at x is 1/derivative, i.e.
2402 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2404 void WriteApproxPastTime(float dst, float t)
2406 float dt = time - t;
2408 // warning: this is approximate; do not resend when you don't have to!
2409 // be careful with sendflags here!
2410 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2413 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2416 dt = rint(bound(0, dt, 255));
2422 float ReadApproxPastTime()
2424 float dt = ReadByte();
2426 // map from range...PPROXPASTTIME_MAX / 256
2427 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2429 return servertime - dt;
2434 .float skeleton_bones_index;
2435 void Skeleton_SetBones(entity e)
2437 // set skeleton_bones to the total number of bones on the model
2438 if(e.skeleton_bones_index == e.modelindex)
2439 return; // same model, nothing to update
2442 skelindex = skel_create(e.modelindex);
2443 e.skeleton_bones = skel_get_numbones(skelindex);
2444 skel_delete(skelindex);
2445 e.skeleton_bones_index = e.modelindex;
2449 string to_execute_next_frame;
2450 void execute_next_frame()
2452 if(to_execute_next_frame)
2454 localcmd("\n", to_execute_next_frame, "\n");
2455 strunzone(to_execute_next_frame);
2456 to_execute_next_frame = string_null;
2459 void queue_to_execute_next_frame(string s)
2461 if(to_execute_next_frame)
2463 s = strcat(s, "\n", to_execute_next_frame);
2464 strunzone(to_execute_next_frame);
2466 to_execute_next_frame = strzone(s);
2469 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2472 ((( startspeedfactor + endspeedfactor - 2
2473 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2474 ) * x + startspeedfactor
2478 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2480 if(startspeedfactor < 0 || endspeedfactor < 0)
2484 // if this is the case, the possible zeros of the first derivative are outside
2486 We can calculate this condition as condition
2491 // better, see below:
2492 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2495 // if this is the case, the first derivative has no zeros at all
2496 float se = startspeedfactor + endspeedfactor;
2497 float s_e = startspeedfactor - endspeedfactor;
2498 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2501 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2502 // we also get s_e <= 6 - se
2503 // 3 * (se - 4)^2 + (6 - se)^2
2504 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2505 // Therefore, above "better" check works!
2509 // known good cases:
2517 // (3.5, [0.2..2.3])
2522 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2524 s + e - 2 == 0: no inflection
2527 0 < inflection < 1 if:
2528 0 < 2s + e - 3 < 3s + 3e - 6
2529 2s + e > 3 and 2e + s > 3
2532 0 < inflection < 1 if:
2533 0 > 2s + e - 3 > 3s + 3e - 6
2534 2s + e < 3 and 2e + s < 3
2536 Therefore: there is an inflection point iff:
2537 e outside (3 - s)/2 .. 3 - s*2
2539 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)
2543 .float FindConnectedComponent_processing;
2544 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2546 entity queue_start, queue_end;
2548 // we build a queue of to-be-processed entities.
2549 // queue_start is the next entity to be checked for neighbors
2550 // queue_end is the last entity added
2552 if(e.FindConnectedComponent_processing)
2553 error("recursion or broken cleanup");
2555 // start with a 1-element queue
2556 queue_start = queue_end = e;
2557 queue_end.fld = world;
2558 queue_end.FindConnectedComponent_processing = 1;
2560 // for each queued item:
2561 for(0; queue_start; queue_start = queue_start.fld)
2563 // find all neighbors of queue_start
2565 for(t = world; (t = nxt(t, queue_start, pass)); )
2567 if(t.FindConnectedComponent_processing)
2569 if(iscon(t, queue_start, pass))
2571 // it is connected? ADD IT. It will look for neighbors soon too.
2574 queue_end.fld = world;
2575 queue_end.FindConnectedComponent_processing = 1;
2581 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2582 queue_start.FindConnectedComponent_processing = 0;
2586 vector combine_to_vector(float x, float y, float z)
2588 vector result; result_x = x; result_y = y; result_z = z;
2592 vector get_corner_position(entity box, float corner)
2596 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2597 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2598 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2599 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2600 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2601 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2602 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2603 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2604 default: return '0 0 0';
2609 // todo: this sucks, lets find a better way to do backtraces?
2610 void backtrace(string msg)
2614 dev = autocvar_developer;
2615 war = autocvar_prvm_backtraceforwarnings;
2617 dev = cvar("developer");
2618 war = cvar("prvm_backtraceforwarnings");
2620 cvar_set("developer", "1");
2621 cvar_set("prvm_backtraceforwarnings", "1");
2623 print("--- CUT HERE ---\nWARNING: ");
2626 remove(world); // isn't there any better way to cause a backtrace?
2627 print("\n--- CUT UNTIL HERE ---\n");
2628 cvar_set("developer", ftos(dev));
2629 cvar_set("prvm_backtraceforwarnings", ftos(war));
2632 // color code replace, place inside of sprintf and parse the string
2633 string CCR(string input)
2635 // See the autocvar declarations in util.qh for default values
2637 // foreground/normal colors
2638 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2639 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2640 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2641 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2644 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2645 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2646 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2648 // background colors
2649 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2650 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2654 vector vec3(float x, float y, float z)
2664 vector animfixfps(entity e, vector a, vector b)
2666 // multi-frame anim: keep as-is
2670 dur = frameduration(e.modelindex, a.x);
2674 dur = frameduration(e.modelindex, a.x);
2684 void dedicated_print(string input) // print(), but only print if the server is not local
2686 if(server_is_dedicated) { print(input); }
2691 float Announcer_PickNumber(float type, float num)
2699 case 10: return ANNCE_NUM_GAMESTART_10;
2700 case 9: return ANNCE_NUM_GAMESTART_9;
2701 case 8: return ANNCE_NUM_GAMESTART_8;
2702 case 7: return ANNCE_NUM_GAMESTART_7;
2703 case 6: return ANNCE_NUM_GAMESTART_6;
2704 case 5: return ANNCE_NUM_GAMESTART_5;
2705 case 4: return ANNCE_NUM_GAMESTART_4;
2706 case 3: return ANNCE_NUM_GAMESTART_3;
2707 case 2: return ANNCE_NUM_GAMESTART_2;
2708 case 1: return ANNCE_NUM_GAMESTART_1;
2716 case 10: return ANNCE_NUM_IDLE_10;
2717 case 9: return ANNCE_NUM_IDLE_9;
2718 case 8: return ANNCE_NUM_IDLE_8;
2719 case 7: return ANNCE_NUM_IDLE_7;
2720 case 6: return ANNCE_NUM_IDLE_6;
2721 case 5: return ANNCE_NUM_IDLE_5;
2722 case 4: return ANNCE_NUM_IDLE_4;
2723 case 3: return ANNCE_NUM_IDLE_3;
2724 case 2: return ANNCE_NUM_IDLE_2;
2725 case 1: return ANNCE_NUM_IDLE_1;
2733 case 10: return ANNCE_NUM_KILL_10;
2734 case 9: return ANNCE_NUM_KILL_9;
2735 case 8: return ANNCE_NUM_KILL_8;
2736 case 7: return ANNCE_NUM_KILL_7;
2737 case 6: return ANNCE_NUM_KILL_6;
2738 case 5: return ANNCE_NUM_KILL_5;
2739 case 4: return ANNCE_NUM_KILL_4;
2740 case 3: return ANNCE_NUM_KILL_3;
2741 case 2: return ANNCE_NUM_KILL_2;
2742 case 1: return ANNCE_NUM_KILL_1;
2750 case 10: return ANNCE_NUM_RESPAWN_10;
2751 case 9: return ANNCE_NUM_RESPAWN_9;
2752 case 8: return ANNCE_NUM_RESPAWN_8;
2753 case 7: return ANNCE_NUM_RESPAWN_7;
2754 case 6: return ANNCE_NUM_RESPAWN_6;
2755 case 5: return ANNCE_NUM_RESPAWN_5;
2756 case 4: return ANNCE_NUM_RESPAWN_4;
2757 case 3: return ANNCE_NUM_RESPAWN_3;
2758 case 2: return ANNCE_NUM_RESPAWN_2;
2759 case 1: return ANNCE_NUM_RESPAWN_1;
2763 case CNT_ROUNDSTART:
2767 case 10: return ANNCE_NUM_ROUNDSTART_10;
2768 case 9: return ANNCE_NUM_ROUNDSTART_9;
2769 case 8: return ANNCE_NUM_ROUNDSTART_8;
2770 case 7: return ANNCE_NUM_ROUNDSTART_7;
2771 case 6: return ANNCE_NUM_ROUNDSTART_6;
2772 case 5: return ANNCE_NUM_ROUNDSTART_5;
2773 case 4: return ANNCE_NUM_ROUNDSTART_4;
2774 case 3: return ANNCE_NUM_ROUNDSTART_3;
2775 case 2: return ANNCE_NUM_ROUNDSTART_2;
2776 case 1: return ANNCE_NUM_ROUNDSTART_1;
2784 case 10: return ANNCE_NUM_10;
2785 case 9: return ANNCE_NUM_9;
2786 case 8: return ANNCE_NUM_8;
2787 case 7: return ANNCE_NUM_7;
2788 case 6: return ANNCE_NUM_6;
2789 case 5: return ANNCE_NUM_5;
2790 case 4: return ANNCE_NUM_4;
2791 case 3: return ANNCE_NUM_3;
2792 case 2: return ANNCE_NUM_2;
2793 case 1: return ANNCE_NUM_1;
2798 return NOTIF_ABORT; // abort sending if none of these numbers were right
2803 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2805 switch(nativecontents)
2810 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2812 return DPCONTENTS_WATER;
2814 return DPCONTENTS_SLIME;
2816 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2818 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2823 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2825 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2826 return CONTENT_SOLID;
2827 if(supercontents & DPCONTENTS_SKY)
2829 if(supercontents & DPCONTENTS_LAVA)
2830 return CONTENT_LAVA;
2831 if(supercontents & DPCONTENTS_SLIME)
2832 return CONTENT_SLIME;
2833 if(supercontents & DPCONTENTS_WATER)
2834 return CONTENT_WATER;
2835 return CONTENT_EMPTY;
2839 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2842 (c - 2 * b + a) * (t * t) +
2847 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2850 (c - 2 * b + a) * (2 * t) +