4 #include "../dpdefs/csprogsdefs.qh"
5 #include "../client/defs.qh"
6 #include "constants.qh"
7 #include "../warpzonelib/mathlib.qh"
9 #include "notifications.qh"
10 #include "deathtypes.qh"
13 #include "../dpdefs/progsdefs.qh"
14 #include "../dpdefs/dpextensions.qh"
15 #include "../warpzonelib/mathlib.qh"
16 #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)
2058 get_model_parameters_modelname = string_null;
2059 get_model_parameters_modelskin = -1;
2060 get_model_parameters_name = string_null;
2061 get_model_parameters_species = -1;
2062 get_model_parameters_sex = string_null;
2063 get_model_parameters_weight = -1;
2064 get_model_parameters_age = -1;
2065 get_model_parameters_desc = string_null;
2066 get_model_parameters_bone_upperbody = string_null;
2067 get_model_parameters_bone_weapon = string_null;
2068 for(int i = 0; i < MAX_AIM_BONES; ++i)
2070 get_model_parameters_bone_aim[i] = string_null;
2071 get_model_parameters_bone_aimweight[i] = 0;
2073 get_model_parameters_fixbone = 0;
2078 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2079 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2083 if(substring(m, -4, -1) != ".txt")
2085 if(substring(m, -6, 1) != "_")
2087 sk = stof(substring(m, -5, 1));
2088 m = substring(m, 0, -7);
2091 string fn = get_model_datafilename(m, sk, "txt");
2092 int fh = fopen(fn, FILE_READ);
2096 fn = get_model_datafilename(m, sk, "txt");
2097 fh = fopen(fn, FILE_READ);
2102 get_model_parameters_modelname = m;
2103 get_model_parameters_modelskin = sk;
2105 while((s = fgets(fh)))
2108 break; // next lines will be description
2112 get_model_parameters_name = s;
2116 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2117 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2118 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2119 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2120 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2121 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2122 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2125 get_model_parameters_sex = s;
2127 get_model_parameters_weight = stof(s);
2129 get_model_parameters_age = stof(s);
2130 if(c == "description")
2131 get_model_parameters_description = s;
2132 if(c == "bone_upperbody")
2133 get_model_parameters_bone_upperbody = s;
2134 if(c == "bone_weapon")
2135 get_model_parameters_bone_weapon = s;
2136 for(int i = 0; i < MAX_AIM_BONES; ++i)
2137 if(c == strcat("bone_aim", ftos(i)))
2139 get_model_parameters_bone_aimweight[i] = stof(car(s));
2140 get_model_parameters_bone_aim[i] = cdr(s);
2143 get_model_parameters_fixbone = stof(s);
2146 while((s = fgets(fh)))
2148 if(get_model_parameters_desc)
2149 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2151 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2159 vector vec2(vector v)
2166 vector NearestPointOnBox(entity box, vector org)
2168 vector m1, m2, nearest;
2170 m1 = box.mins + box.origin;
2171 m2 = box.maxs + box.origin;
2173 nearest.x = bound(m1_x, org.x, m2_x);
2174 nearest.y = bound(m1_y, org.y, m2_y);
2175 nearest.z = bound(m1_z, org.z, m2_z);
2181 float vercmp_recursive(string v1, string v2)
2187 dot1 = strstrofs(v1, ".", 0);
2188 dot2 = strstrofs(v2, ".", 0);
2192 s1 = substring(v1, 0, dot1);
2196 s2 = substring(v2, 0, dot2);
2198 r = stof(s1) - stof(s2);
2202 r = strcasecmp(s1, s2);
2215 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2218 float vercmp(string v1, string v2)
2220 if(strcasecmp(v1, v2) == 0) // early out check
2229 return vercmp_recursive(v1, v2);
2232 float u8_strsize(string s)
2252 // translation helpers
2253 string language_filename(string s)
2258 if(fn == "" || fn == "dump")
2260 fn = strcat(s, ".", fn);
2261 if((fh = fopen(fn, FILE_READ)) >= 0)
2268 string CTX(string s)
2270 float p = strstrofs(s, "^", 0);
2273 return substring(s, p+1, -1);
2276 // x-encoding (encoding as zero length invisible string)
2277 const string XENCODE_2 = "xX";
2278 const string XENCODE_22 = "0123456789abcdefABCDEF";
2279 string xencode(int f)
2282 d = f % 22; f = floor(f / 22);
2283 c = f % 22; f = floor(f / 22);
2284 b = f % 22; f = floor(f / 22);
2285 a = f % 2; // f = floor(f / 2);
2288 substring(XENCODE_2, a, 1),
2289 substring(XENCODE_22, b, 1),
2290 substring(XENCODE_22, c, 1),
2291 substring(XENCODE_22, d, 1)
2294 float xdecode(string s)
2297 if(substring(s, 0, 1) != "^")
2301 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2302 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2303 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2304 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2305 if(a < 0 || b < 0 || c < 0 || d < 0)
2307 return ((a * 22 + b) * 22 + c) * 22 + d;
2310 float lowestbit(int f)
2321 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2323 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2326 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2329 // escape the string to make it safe for consoles
2330 string MakeConsoleSafe(string input)
2332 input = strreplace("\n", "", input);
2333 input = strreplace("\\", "\\\\", input);
2334 input = strreplace("$", "$$", input);
2335 input = strreplace("\"", "\\\"", input);
2340 // get true/false value of a string with multiple different inputs
2341 float InterpretBoolean(string input)
2343 switch(strtolower(input))
2355 default: return stof(input);
2361 entity ReadCSQCEntity()
2363 int f = ReadShort();
2366 return findfloat(world, entnum, f);
2370 float shutdown_running;
2375 void CSQC_Shutdown()
2381 if(shutdown_running)
2383 print("Recursive shutdown detected! Only restoring cvars...\n");
2387 shutdown_running = 1;
2390 cvar_settemp_restore(); // this must be done LAST, but in any case
2393 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2394 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2395 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2396 // this will use the value:
2398 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2399 // accuracy at x is 1/derivative, i.e.
2400 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2402 void WriteApproxPastTime(float dst, float t)
2404 float dt = time - t;
2406 // warning: this is approximate; do not resend when you don't have to!
2407 // be careful with sendflags here!
2408 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2411 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2414 dt = rint(bound(0, dt, 255));
2420 float ReadApproxPastTime()
2422 float dt = ReadByte();
2424 // map from range...PPROXPASTTIME_MAX / 256
2425 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2427 return servertime - dt;
2432 .float skeleton_bones_index;
2433 void Skeleton_SetBones(entity e)
2435 // set skeleton_bones to the total number of bones on the model
2436 if(e.skeleton_bones_index == e.modelindex)
2437 return; // same model, nothing to update
2440 skelindex = skel_create(e.modelindex);
2441 e.skeleton_bones = skel_get_numbones(skelindex);
2442 skel_delete(skelindex);
2443 e.skeleton_bones_index = e.modelindex;
2447 string to_execute_next_frame;
2448 void execute_next_frame()
2450 if(to_execute_next_frame)
2452 localcmd("\n", to_execute_next_frame, "\n");
2453 strunzone(to_execute_next_frame);
2454 to_execute_next_frame = string_null;
2457 void queue_to_execute_next_frame(string s)
2459 if(to_execute_next_frame)
2461 s = strcat(s, "\n", to_execute_next_frame);
2462 strunzone(to_execute_next_frame);
2464 to_execute_next_frame = strzone(s);
2467 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2470 ((( startspeedfactor + endspeedfactor - 2
2471 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2472 ) * x + startspeedfactor
2476 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2478 if(startspeedfactor < 0 || endspeedfactor < 0)
2482 // if this is the case, the possible zeros of the first derivative are outside
2484 We can calculate this condition as condition
2489 // better, see below:
2490 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2493 // if this is the case, the first derivative has no zeros at all
2494 float se = startspeedfactor + endspeedfactor;
2495 float s_e = startspeedfactor - endspeedfactor;
2496 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2499 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2500 // we also get s_e <= 6 - se
2501 // 3 * (se - 4)^2 + (6 - se)^2
2502 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2503 // Therefore, above "better" check works!
2507 // known good cases:
2515 // (3.5, [0.2..2.3])
2520 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2522 s + e - 2 == 0: no inflection
2525 0 < inflection < 1 if:
2526 0 < 2s + e - 3 < 3s + 3e - 6
2527 2s + e > 3 and 2e + s > 3
2530 0 < inflection < 1 if:
2531 0 > 2s + e - 3 > 3s + 3e - 6
2532 2s + e < 3 and 2e + s < 3
2534 Therefore: there is an inflection point iff:
2535 e outside (3 - s)/2 .. 3 - s*2
2537 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)
2541 .float FindConnectedComponent_processing;
2542 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2544 entity queue_start, queue_end;
2546 // we build a queue of to-be-processed entities.
2547 // queue_start is the next entity to be checked for neighbors
2548 // queue_end is the last entity added
2550 if(e.FindConnectedComponent_processing)
2551 error("recursion or broken cleanup");
2553 // start with a 1-element queue
2554 queue_start = queue_end = e;
2555 queue_end.fld = world;
2556 queue_end.FindConnectedComponent_processing = 1;
2558 // for each queued item:
2559 for (; queue_start; queue_start = queue_start.fld)
2561 // find all neighbors of queue_start
2563 for(t = world; (t = nxt(t, queue_start, pass)); )
2565 if(t.FindConnectedComponent_processing)
2567 if(iscon(t, queue_start, pass))
2569 // it is connected? ADD IT. It will look for neighbors soon too.
2572 queue_end.fld = world;
2573 queue_end.FindConnectedComponent_processing = 1;
2579 for(queue_start = e; queue_start; queue_start = queue_start.fld)
2580 queue_start.FindConnectedComponent_processing = 0;
2584 vector combine_to_vector(float x, float y, float z)
2586 vector result; result_x = x; result_y = y; result_z = z;
2590 vector get_corner_position(entity box, float corner)
2594 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2595 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2596 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2597 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2598 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2599 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2600 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2601 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2602 default: return '0 0 0';
2607 // todo: this sucks, lets find a better way to do backtraces?
2608 void backtrace(string msg)
2612 dev = autocvar_developer;
2613 war = autocvar_prvm_backtraceforwarnings;
2615 dev = cvar("developer");
2616 war = cvar("prvm_backtraceforwarnings");
2618 cvar_set("developer", "1");
2619 cvar_set("prvm_backtraceforwarnings", "1");
2621 print("--- CUT HERE ---\nWARNING: ");
2624 remove(world); // isn't there any better way to cause a backtrace?
2625 print("\n--- CUT UNTIL HERE ---\n");
2626 cvar_set("developer", ftos(dev));
2627 cvar_set("prvm_backtraceforwarnings", ftos(war));
2630 // color code replace, place inside of sprintf and parse the string
2631 string CCR(string input)
2633 // See the autocvar declarations in util.qh for default values
2635 // foreground/normal colors
2636 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2637 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2638 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2639 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2642 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2643 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2644 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2646 // background colors
2647 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2648 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2652 vector vec3(float x, float y, float z)
2662 vector animfixfps(entity e, vector a, vector b)
2664 // multi-frame anim: keep as-is
2668 dur = frameduration(e.modelindex, a.x);
2672 dur = frameduration(e.modelindex, a.x);
2682 void dedicated_print(string input) // print(), but only print if the server is not local
2684 if(server_is_dedicated) { print(input); }
2689 float Announcer_PickNumber(float type, float num)
2697 case 10: return ANNCE_NUM_GAMESTART_10;
2698 case 9: return ANNCE_NUM_GAMESTART_9;
2699 case 8: return ANNCE_NUM_GAMESTART_8;
2700 case 7: return ANNCE_NUM_GAMESTART_7;
2701 case 6: return ANNCE_NUM_GAMESTART_6;
2702 case 5: return ANNCE_NUM_GAMESTART_5;
2703 case 4: return ANNCE_NUM_GAMESTART_4;
2704 case 3: return ANNCE_NUM_GAMESTART_3;
2705 case 2: return ANNCE_NUM_GAMESTART_2;
2706 case 1: return ANNCE_NUM_GAMESTART_1;
2714 case 10: return ANNCE_NUM_IDLE_10;
2715 case 9: return ANNCE_NUM_IDLE_9;
2716 case 8: return ANNCE_NUM_IDLE_8;
2717 case 7: return ANNCE_NUM_IDLE_7;
2718 case 6: return ANNCE_NUM_IDLE_6;
2719 case 5: return ANNCE_NUM_IDLE_5;
2720 case 4: return ANNCE_NUM_IDLE_4;
2721 case 3: return ANNCE_NUM_IDLE_3;
2722 case 2: return ANNCE_NUM_IDLE_2;
2723 case 1: return ANNCE_NUM_IDLE_1;
2731 case 10: return ANNCE_NUM_KILL_10;
2732 case 9: return ANNCE_NUM_KILL_9;
2733 case 8: return ANNCE_NUM_KILL_8;
2734 case 7: return ANNCE_NUM_KILL_7;
2735 case 6: return ANNCE_NUM_KILL_6;
2736 case 5: return ANNCE_NUM_KILL_5;
2737 case 4: return ANNCE_NUM_KILL_4;
2738 case 3: return ANNCE_NUM_KILL_3;
2739 case 2: return ANNCE_NUM_KILL_2;
2740 case 1: return ANNCE_NUM_KILL_1;
2748 case 10: return ANNCE_NUM_RESPAWN_10;
2749 case 9: return ANNCE_NUM_RESPAWN_9;
2750 case 8: return ANNCE_NUM_RESPAWN_8;
2751 case 7: return ANNCE_NUM_RESPAWN_7;
2752 case 6: return ANNCE_NUM_RESPAWN_6;
2753 case 5: return ANNCE_NUM_RESPAWN_5;
2754 case 4: return ANNCE_NUM_RESPAWN_4;
2755 case 3: return ANNCE_NUM_RESPAWN_3;
2756 case 2: return ANNCE_NUM_RESPAWN_2;
2757 case 1: return ANNCE_NUM_RESPAWN_1;
2761 case CNT_ROUNDSTART:
2765 case 10: return ANNCE_NUM_ROUNDSTART_10;
2766 case 9: return ANNCE_NUM_ROUNDSTART_9;
2767 case 8: return ANNCE_NUM_ROUNDSTART_8;
2768 case 7: return ANNCE_NUM_ROUNDSTART_7;
2769 case 6: return ANNCE_NUM_ROUNDSTART_6;
2770 case 5: return ANNCE_NUM_ROUNDSTART_5;
2771 case 4: return ANNCE_NUM_ROUNDSTART_4;
2772 case 3: return ANNCE_NUM_ROUNDSTART_3;
2773 case 2: return ANNCE_NUM_ROUNDSTART_2;
2774 case 1: return ANNCE_NUM_ROUNDSTART_1;
2782 case 10: return ANNCE_NUM_10;
2783 case 9: return ANNCE_NUM_9;
2784 case 8: return ANNCE_NUM_8;
2785 case 7: return ANNCE_NUM_7;
2786 case 6: return ANNCE_NUM_6;
2787 case 5: return ANNCE_NUM_5;
2788 case 4: return ANNCE_NUM_4;
2789 case 3: return ANNCE_NUM_3;
2790 case 2: return ANNCE_NUM_2;
2791 case 1: return ANNCE_NUM_1;
2796 return NOTIF_ABORT; // abort sending if none of these numbers were right
2801 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2803 switch(nativecontents)
2808 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2810 return DPCONTENTS_WATER;
2812 return DPCONTENTS_SLIME;
2814 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2816 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2821 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2823 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2824 return CONTENT_SOLID;
2825 if(supercontents & DPCONTENTS_SKY)
2827 if(supercontents & DPCONTENTS_LAVA)
2828 return CONTENT_LAVA;
2829 if(supercontents & DPCONTENTS_SLIME)
2830 return CONTENT_SLIME;
2831 if(supercontents & DPCONTENTS_WATER)
2832 return CONTENT_WATER;
2833 return CONTENT_EMPTY;
2837 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2840 (c - 2 * b + a) * (t * t) +
2845 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2848 (c - 2 * b + a) * (2 * t) +