4 #include "../dpdefs/csprogsdefs.qh"
5 #include "../client/defs.qh"
6 #include "constants.qh"
7 #include "../client/mutators/events.qh"
9 #include "notifications.qh"
10 #include "deathtypes.qh"
13 #include "../dpdefs/progsdefs.qh"
14 #include "../dpdefs/dpextensions.qh"
15 #include "constants.qh"
16 #include "../server/autocvars.qh"
17 #include "../server/defs.qh"
18 #include "../server/mutators/events.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 LOG_INFO(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)));
385 LOG_INFO("LOAD...\n");
386 db = db_load("foo.db");
387 LOG_INFO("LOADED. FILL...\n");
388 for(i = 0; i < DB_BUCKETS; ++i)
389 db_put(db, ftos(random()), "X");
390 LOG_INFO("FILLED. SAVE...\n");
391 db_save(db, "foo.db");
392 LOG_INFO("SAVED. CLOSE...\n");
394 LOG_INFO("CLOSED.\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 // compressed vector format:
504 // like MD3, just even shorter
505 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
506 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
507 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
508 // length = 2^(length_encoded/8) / 8
509 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
510 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
511 // the special value 0 indicates the zero vector
513 float lengthLogTable[128];
515 float invertLengthLog(float x)
519 if(x >= lengthLogTable[127])
521 if(x <= lengthLogTable[0])
529 m = floor((l + r) / 2);
530 if(lengthLogTable[m] < x)
536 // now: r is >=, l is <
537 float lerr = (x - lengthLogTable[l]);
538 float rerr = (lengthLogTable[r] - x);
544 vector decompressShortVector(int data)
549 float p = (data & 0xF000) / 0x1000;
550 float y = (data & 0x0F80) / 0x80;
551 int len = (data & 0x007F);
553 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
566 y = .19634954084936207740 * y;
567 p = .19634954084936207740 * p - 1.57079632679489661922;
568 out.x = cos(y) * cos(p);
569 out.y = sin(y) * cos(p);
573 //print("decompressed: ", vtos(out), "\n");
575 return out * lengthLogTable[len];
578 float compressShortVector(vector vec)
584 //print("compress: ", vtos(vec), "\n");
585 ang = vectoangles(vec);
589 if(ang.x < -90 && ang.x > +90)
590 error("BOGUS vectoangles");
591 //print("angles: ", vtos(ang), "\n");
593 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
602 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
603 len = invertLengthLog(vlen(vec));
605 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
607 return (p * 0x1000) + (y * 0x80) + len;
610 void compressShortVector_init()
613 float f = pow(2, 1/8);
615 for(i = 0; i < 128; ++i)
617 lengthLogTable[i] = l;
621 if(cvar("developer"))
623 LOG_INFO("Verifying vector compression table...\n");
624 for(i = 0x0F00; i < 0xFFFF; ++i)
625 if(i != compressShortVector(decompressShortVector(i)))
627 LOG_INFO("BROKEN vector compression: ", ftos(i));
628 LOG_INFO(" -> ", vtos(decompressShortVector(i)));
629 LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
638 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
640 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
641 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
642 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
643 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
644 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
645 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
646 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
647 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
648 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
649 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
650 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
651 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
656 string fixPriorityList(string order, float from, float to, float subtract, float complete)
661 n = tokenize_console(order);
663 for(i = 0; i < n; ++i)
668 if(w >= from && w <= to)
669 neworder = strcat(neworder, ftos(w), " ");
673 if(w >= from && w <= to)
674 neworder = strcat(neworder, ftos(w), " ");
681 n = tokenize_console(neworder);
682 for(w = to; w >= from; --w)
684 for(i = 0; i < n; ++i)
685 if(stof(argv(i)) == w)
687 if(i == n) // not found
688 neworder = strcat(neworder, ftos(w), " ");
692 return substring(neworder, 0, strlen(neworder) - 1);
695 string mapPriorityList(string order, string(string) mapfunc)
700 n = tokenize_console(order);
702 for(i = 0; i < n; ++i)
703 neworder = strcat(neworder, mapfunc(argv(i)), " ");
705 return substring(neworder, 0, strlen(neworder) - 1);
708 string swapInPriorityList(string order, float i, float j)
713 n = tokenize_console(order);
715 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
718 for(w = 0; w < n; ++w)
721 s = strcat(s, argv(j), " ");
723 s = strcat(s, argv(i), " ");
725 s = strcat(s, argv(w), " ");
727 return substring(s, 0, strlen(s) - 1);
733 float cvar_value_issafe(string s)
735 if(strstrofs(s, "\"", 0) >= 0)
737 if(strstrofs(s, "\\", 0) >= 0)
739 if(strstrofs(s, ";", 0) >= 0)
741 if(strstrofs(s, "$", 0) >= 0)
743 if(strstrofs(s, "\r", 0) >= 0)
745 if(strstrofs(s, "\n", 0) >= 0)
751 void get_mi_min_max(float mode)
756 strunzone(mi_shortname);
757 mi_shortname = mapname;
758 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
759 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
760 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
761 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
762 mi_shortname = strzone(mi_shortname);
774 MapInfo_Get_ByName(mi_shortname, 0, 0);
775 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
777 mi_min = MapInfo_Map_mins;
778 mi_max = MapInfo_Map_maxs;
786 tracebox('1 0 0' * mi.x,
787 '0 1 0' * mi.y + '0 0 1' * mi.z,
788 '0 1 0' * ma.y + '0 0 1' * ma.z,
792 if(!trace_startsolid)
793 mi_min.x = trace_endpos.x;
795 tracebox('0 1 0' * mi.y,
796 '1 0 0' * mi.x + '0 0 1' * mi.z,
797 '1 0 0' * ma.x + '0 0 1' * ma.z,
801 if(!trace_startsolid)
802 mi_min.y = trace_endpos.y;
804 tracebox('0 0 1' * mi.z,
805 '1 0 0' * mi.x + '0 1 0' * mi.y,
806 '1 0 0' * ma.x + '0 1 0' * ma.y,
810 if(!trace_startsolid)
811 mi_min.z = trace_endpos.z;
813 tracebox('1 0 0' * ma.x,
814 '0 1 0' * mi.y + '0 0 1' * mi.z,
815 '0 1 0' * ma.y + '0 0 1' * ma.z,
819 if(!trace_startsolid)
820 mi_max.x = trace_endpos.x;
822 tracebox('0 1 0' * ma.y,
823 '1 0 0' * mi.x + '0 0 1' * mi.z,
824 '1 0 0' * ma.x + '0 0 1' * ma.z,
828 if(!trace_startsolid)
829 mi_max.y = trace_endpos.y;
831 tracebox('0 0 1' * ma.z,
832 '1 0 0' * mi.x + '0 1 0' * mi.y,
833 '1 0 0' * ma.x + '0 1 0' * ma.y,
837 if(!trace_startsolid)
838 mi_max.z = trace_endpos.z;
843 void get_mi_min_max_texcoords(float mode)
847 get_mi_min_max(mode);
852 // extend mi_picmax to get a square aspect ratio
853 // center the map in that area
854 extend = mi_picmax - mi_picmin;
855 if(extend.y > extend.x)
857 mi_picmin.x -= (extend.y - extend.x) * 0.5;
858 mi_picmax.x += (extend.y - extend.x) * 0.5;
862 mi_picmin.y -= (extend.x - extend.y) * 0.5;
863 mi_picmax.y += (extend.x - extend.y) * 0.5;
866 // add another some percent
867 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
871 // calculate the texcoords
872 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
873 // first the two corners of the origin
874 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
875 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
876 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
877 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
878 // then the other corners
879 mi_pictexcoord1_x = mi_pictexcoord0_x;
880 mi_pictexcoord1_y = mi_pictexcoord2_y;
881 mi_pictexcoord3_x = mi_pictexcoord2_x;
882 mi_pictexcoord3_y = mi_pictexcoord0_y;
886 float cvar_settemp(string tmp_cvar, string tmp_value)
888 float created_saved_value;
891 created_saved_value = 0;
893 if (!(tmp_cvar || tmp_value))
895 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !\n");
899 if(!cvar_type(tmp_cvar))
901 LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
905 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
906 if(e.netname == tmp_cvar)
907 created_saved_value = -1; // skip creation
909 if(created_saved_value != -1)
911 // creating a new entity to keep track of this cvar
913 e.classname = "saved_cvar_value";
914 e.netname = strzone(tmp_cvar);
915 e.message = strzone(cvar_string(tmp_cvar));
916 created_saved_value = 1;
919 // update the cvar to the value given
920 cvar_set(tmp_cvar, tmp_value);
922 return created_saved_value;
925 float cvar_settemp_restore()
929 while((e = find(e, classname, "saved_cvar_value")))
931 if(cvar_type(e.netname))
933 cvar_set(e.netname, e.message);
938 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
944 float almost_equals(float a, float b)
947 eps = (max(a, -a) + max(b, -b)) * 0.001;
948 if(a - b < eps && b - a < eps)
953 float almost_in_bounds(float a, float b, float c)
956 eps = (max(a, -a) + max(c, -c)) * 0.001;
959 return b == median(a - eps, b, c + eps);
962 float power2of(float e)
966 float log2of(float x)
968 // NOTE: generated code
1041 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1045 else if(ma == rgb.x)
1048 return (rgb.y - rgb.z) / (ma - mi);
1050 return (rgb.y - rgb.z) / (ma - mi) + 6;
1052 else if(ma == rgb.y)
1053 return (rgb.z - rgb.x) / (ma - mi) + 2;
1054 else // if(ma == rgb_z)
1055 return (rgb.x - rgb.y) / (ma - mi) + 4;
1058 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1062 hue -= 6 * floor(hue / 6);
1064 //else if(ma == rgb_x)
1065 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1069 rgb.y = hue * (ma - mi) + mi;
1072 //else if(ma == rgb_y)
1073 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1076 rgb.x = (2 - hue) * (ma - mi) + mi;
1084 rgb.z = (hue - 2) * (ma - mi) + mi;
1086 //else // if(ma == rgb_z)
1087 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1091 rgb.y = (4 - hue) * (ma - mi) + mi;
1096 rgb.x = (hue - 4) * (ma - mi) + mi;
1100 //else if(ma == rgb_x)
1101 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1102 else // if(hue <= 6)
1106 rgb.z = (6 - hue) * (ma - mi) + mi;
1112 vector rgb_to_hsv(vector rgb)
1117 mi = min(rgb.x, rgb.y, rgb.z);
1118 ma = max(rgb.x, rgb.y, rgb.z);
1120 hsv.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1131 vector hsv_to_rgb(vector hsv)
1133 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1136 vector rgb_to_hsl(vector rgb)
1141 mi = min(rgb.x, rgb.y, rgb.z);
1142 ma = max(rgb.x, rgb.y, rgb.z);
1144 hsl.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1146 hsl.z = 0.5 * (mi + ma);
1149 else if(hsl.z <= 0.5)
1150 hsl.y = (ma - mi) / (2*hsl.z);
1151 else // if(hsl_z > 0.5)
1152 hsl.y = (ma - mi) / (2 - 2*hsl.z);
1157 vector hsl_to_rgb(vector hsl)
1159 float mi, ma, maminusmi;
1162 maminusmi = hsl.y * 2 * hsl.z;
1164 maminusmi = hsl.y * (2 - 2 * hsl.z);
1166 // hsl_z = 0.5 * mi + 0.5 * ma
1167 // maminusmi = - mi + ma
1168 mi = hsl.z - 0.5 * maminusmi;
1169 ma = hsl.z + 0.5 * maminusmi;
1171 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1174 string rgb_to_hexcolor(vector rgb)
1179 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1180 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1181 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1185 // requires that m2>m1 in all coordinates, and that m4>m3
1186 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;}
1188 // requires the same, but is a stronger condition
1189 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;}
1194 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1197 // The following function is SLOW.
1198 // For your safety and for the protection of those around you...
1199 // DO NOT CALL THIS AT HOME.
1200 // No really, don't.
1201 if(w(theText, theSize) <= maxWidth)
1202 return strlen(theText); // yeah!
1204 // binary search for right place to cut string
1206 float left, right, middle; // this always works
1208 right = strlen(theText); // this always fails
1211 middle = floor((left + right) / 2);
1212 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1217 while(left < right - 1);
1219 if(w("^7", theSize) == 0) // detect color codes support in the width function
1221 // NOTE: when color codes are involved, this binary search is,
1222 // mathematically, BROKEN. However, it is obviously guaranteed to
1223 // terminate, as the range still halves each time - but nevertheless, it is
1224 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1225 // range, and "right" is outside).
1227 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1228 // and decrease left on the basis of the chars detected of the truncated tag
1229 // Even if the ^xrgb tag is not complete/correct, left is decreased
1230 // (sometimes too much but with a correct result)
1231 // it fixes also ^[0-9]
1232 while(left >= 1 && substring(theText, left-1, 1) == "^")
1235 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1237 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1239 ch = str2chr(theText, left-1);
1240 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1243 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1245 ch = str2chr(theText, left-2);
1246 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1248 ch = str2chr(theText, left-1);
1249 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1258 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1261 // The following function is SLOW.
1262 // For your safety and for the protection of those around you...
1263 // DO NOT CALL THIS AT HOME.
1264 // No really, don't.
1265 if(w(theText) <= maxWidth)
1266 return strlen(theText); // yeah!
1268 // binary search for right place to cut string
1270 float left, right, middle; // this always works
1272 right = strlen(theText); // this always fails
1275 middle = floor((left + right) / 2);
1276 if(w(substring(theText, 0, middle)) <= maxWidth)
1281 while(left < right - 1);
1283 if(w("^7") == 0) // detect color codes support in the width function
1285 // NOTE: when color codes are involved, this binary search is,
1286 // mathematically, BROKEN. However, it is obviously guaranteed to
1287 // terminate, as the range still halves each time - but nevertheless, it is
1288 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1289 // range, and "right" is outside).
1291 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1292 // and decrease left on the basis of the chars detected of the truncated tag
1293 // Even if the ^xrgb tag is not complete/correct, left is decreased
1294 // (sometimes too much but with a correct result)
1295 // it fixes also ^[0-9]
1296 while(left >= 1 && substring(theText, left-1, 1) == "^")
1299 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1301 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1303 ch = str2chr(theText, left-1);
1304 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1307 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1309 ch = str2chr(theText, left-2);
1310 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1312 ch = str2chr(theText, left-1);
1313 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1322 string find_last_color_code(string s)
1324 int start = strstrofs(s, "^", 0);
1325 if (start == -1) // no caret found
1327 int len = strlen(s)-1;
1329 for(i = len; i >= start; --i)
1331 if(substring(s, i, 1) != "^")
1335 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1338 // check if carets aren't all escaped
1342 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1343 return substring(s, i, 2);
1346 if(substring(s, i+1, 1) == "x")
1347 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1348 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1349 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1350 return substring(s, i, 5);
1352 i -= carets; // this also skips one char before the carets
1358 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1364 s = getWrappedLine_remaining;
1368 getWrappedLine_remaining = string_null;
1369 return s; // the line has no size ANYWAY, nothing would be displayed.
1372 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1373 if(cantake > 0 && cantake < strlen(s))
1376 while(take > 0 && substring(s, take, 1) != " ")
1380 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1381 if(getWrappedLine_remaining == "")
1382 getWrappedLine_remaining = string_null;
1383 else if (tw("^7", theFontSize) == 0)
1384 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1385 return substring(s, 0, cantake);
1389 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1390 if(getWrappedLine_remaining == "")
1391 getWrappedLine_remaining = string_null;
1392 else if (tw("^7", theFontSize) == 0)
1393 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1394 return substring(s, 0, take);
1399 getWrappedLine_remaining = string_null;
1404 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1410 s = getWrappedLine_remaining;
1414 getWrappedLine_remaining = string_null;
1415 return s; // the line has no size ANYWAY, nothing would be displayed.
1418 cantake = textLengthUpToLength(s, w, tw);
1419 if(cantake > 0 && cantake < strlen(s))
1422 while(take > 0 && substring(s, take, 1) != " ")
1426 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1427 if(getWrappedLine_remaining == "")
1428 getWrappedLine_remaining = string_null;
1429 else if (tw("^7") == 0)
1430 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1431 return substring(s, 0, cantake);
1435 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1436 if(getWrappedLine_remaining == "")
1437 getWrappedLine_remaining = string_null;
1438 else if (tw("^7") == 0)
1439 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1440 return substring(s, 0, take);
1445 getWrappedLine_remaining = string_null;
1450 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1452 if(tw(theText, theFontSize) <= maxWidth)
1455 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1458 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1460 if(tw(theText) <= maxWidth)
1463 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1466 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1468 string subpattern, subpattern2, subpattern3, subpattern4;
1469 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1471 subpattern2 = ",teams,";
1473 subpattern2 = ",noteams,";
1475 subpattern3 = ",teamspawns,";
1477 subpattern3 = ",noteamspawns,";
1478 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1479 subpattern4 = ",race,";
1481 subpattern4 = string_null;
1483 if(substring(pattern, 0, 1) == "-")
1485 pattern = substring(pattern, 1, strlen(pattern) - 1);
1486 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1488 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1490 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1492 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1497 if(substring(pattern, 0, 1) == "+")
1498 pattern = substring(pattern, 1, strlen(pattern) - 1);
1499 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1500 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1501 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1505 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1512 void shuffle(float n, swapfunc_t swap, entity pass)
1515 for(i = 1; i < n; ++i)
1517 // swap i-th item at a random position from 0 to i
1518 // proof for even distribution:
1521 // item n+1 gets at any position with chance 1/(n+1)
1522 // all others will get their 1/n chance reduced by factor n/(n+1)
1523 // to be on place n+1, their chance will be 1/(n+1)
1524 // 1/n * n/(n+1) = 1/(n+1)
1526 j = floor(random() * (i + 1));
1532 string substring_range(string s, float b, float e)
1534 return substring(s, b, e - b);
1537 string swapwords(string str, float i, float j)
1540 string s1, s2, s3, s4, s5;
1541 float si, ei, sj, ej, s0, en;
1542 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1543 si = argv_start_index(i);
1544 sj = argv_start_index(j);
1545 ei = argv_end_index(i);
1546 ej = argv_end_index(j);
1547 s0 = argv_start_index(0);
1548 en = argv_end_index(n-1);
1549 s1 = substring_range(str, s0, si);
1550 s2 = substring_range(str, si, ei);
1551 s3 = substring_range(str, ei, sj);
1552 s4 = substring_range(str, sj, ej);
1553 s5 = substring_range(str, ej, en);
1554 return strcat(s1, s4, s3, s2, s5);
1557 string _shufflewords_str;
1558 void _shufflewords_swapfunc(float i, float j, entity pass)
1560 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1562 string shufflewords(string str)
1565 _shufflewords_str = str;
1566 n = tokenizebyseparator(str, " ");
1567 shuffle(n, _shufflewords_swapfunc, world);
1568 str = _shufflewords_str;
1569 _shufflewords_str = string_null;
1573 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1589 // actually, every number solves the equation!
1600 if(a > 0) // put the smaller solution first
1602 v.x = ((-b)-D) / (2*a);
1603 v.y = ((-b)+D) / (2*a);
1607 v.x = (-b+D) / (2*a);
1608 v.y = (-b-D) / (2*a);
1614 // complex solutions!
1627 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1631 // make origin and speed relative
1636 // now solve for ret, ret normalized:
1637 // eorg + t * evel == t * ret * spd
1638 // or, rather, solve for t:
1639 // |eorg + t * evel| == t * spd
1640 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1641 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1642 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1643 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1644 // q = (eorg * eorg) / (evel * evel - spd * spd)
1645 if(!solution.z) // no real solution
1648 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1649 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1650 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1651 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1652 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1653 // spd < |evel| * sin angle(evel, eorg)
1656 else if(solution.x > 0)
1658 // both solutions > 0: take the smaller one
1659 // happens if p < 0 and q > 0
1660 ret = normalize(eorg + solution.x * evel);
1662 else if(solution.y > 0)
1664 // one solution > 0: take the larger one
1665 // happens if q < 0 or q == 0 and p < 0
1666 ret = normalize(eorg + solution.y * evel);
1670 // no solution > 0: reject
1671 // happens if p > 0 and q >= 0
1672 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1673 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1678 // "Enemy is moving away from me at more than spd"
1682 // NOTE: we always got a solution if spd > |evel|
1684 if(newton_style == 2)
1685 ret = normalize(ret * spd + myvel);
1690 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1695 if(newton_style == 2)
1697 // true Newtonian projectiles with automatic aim adjustment
1699 // solve: |outspeed * mydir - myvel| = spd
1700 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1701 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1705 // myvel^2 - (mydir * myvel)^2 > spd^2
1706 // velocity without mydir component > spd
1707 // fire at smallest possible spd that works?
1708 // |(mydir * myvel) * myvel - myvel| = spd
1710 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1714 outspeed = solution.y; // the larger one
1717 //outspeed = 0; // slowest possible shot
1718 outspeed = solution.x; // the real part (that is, the average!)
1719 //dprint("impossible shot, adjusting\n");
1722 outspeed = bound(spd * mi, outspeed, spd * ma);
1723 return mydir * outspeed;
1727 return myvel + spd * mydir;
1730 float compressShotOrigin(vector v)
1734 y = rint(v.y * 4) + 128;
1735 z = rint(v.z * 4) + 128;
1736 if(x > 255 || x < 0)
1738 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1739 x = bound(0, x, 255);
1741 if(y > 255 || y < 0)
1743 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1744 y = bound(0, y, 255);
1746 if(z > 255 || z < 0)
1748 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1749 z = bound(0, z, 255);
1751 return x * 0x10000 + y * 0x100 + z;
1753 vector decompressShotOrigin(int f)
1756 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1757 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1758 v.z = ((f & 0xFF) - 128) / 4;
1762 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1764 float start, end, root, child;
1767 start = floor((n - 2) / 2);
1770 // siftdown(start, count-1);
1772 while(root * 2 + 1 <= n-1)
1774 child = root * 2 + 1;
1776 if(cmp(child, child+1, pass) < 0)
1778 if(cmp(root, child, pass) < 0)
1780 swap(root, child, pass);
1796 // siftdown(0, end);
1798 while(root * 2 + 1 <= end)
1800 child = root * 2 + 1;
1801 if(child < end && cmp(child, child+1, pass) < 0)
1803 if(cmp(root, child, pass) < 0)
1805 swap(root, child, pass);
1815 void RandomSelection_Init()
1817 RandomSelection_totalweight = 0;
1818 RandomSelection_chosen_ent = world;
1819 RandomSelection_chosen_float = 0;
1820 RandomSelection_chosen_string = string_null;
1821 RandomSelection_best_priority = -1;
1823 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1825 if(priority > RandomSelection_best_priority)
1827 RandomSelection_best_priority = priority;
1828 RandomSelection_chosen_ent = e;
1829 RandomSelection_chosen_float = f;
1830 RandomSelection_chosen_string = s;
1831 RandomSelection_totalweight = weight;
1833 else if(priority == RandomSelection_best_priority)
1835 RandomSelection_totalweight += weight;
1836 if(random() * RandomSelection_totalweight <= weight)
1838 RandomSelection_chosen_ent = e;
1839 RandomSelection_chosen_float = f;
1840 RandomSelection_chosen_string = s;
1846 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1848 // NOTE: we'll always choose the SMALLER value...
1849 float healthdamage, armordamage, armorideal;
1850 if (deathtype == DEATH_DROWN) // Why should armor help here...
1853 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1854 armordamage = a + (h - 1); // damage we can take if we could use more armor
1855 armorideal = healthdamage * armorblock;
1857 if(armordamage < healthdamage)
1870 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1873 if (deathtype == DEATH_DROWN) // Why should armor help here...
1875 v.y = bound(0, damage * armorblock, a); // save
1876 v.x = bound(0, damage - v.y, damage); // take
1882 string getcurrentmod()
1886 m = cvar_string("fs_gamedir");
1887 n = tokenize_console(m);
1898 int v = ReadShort() * 256; // note: this is signed
1899 v += ReadByte(); // note: this is unsigned
1902 vector ReadInt48_t()
1905 v.x = ReadInt24_t();
1906 v.y = ReadInt24_t();
1910 vector ReadInt72_t()
1913 v.x = ReadInt24_t();
1914 v.y = ReadInt24_t();
1915 v.z = ReadInt24_t();
1919 void WriteInt24_t(float dst, float val)
1922 WriteShort(dst, (v = floor(val / 256)));
1923 WriteByte(dst, val - v * 256); // 0..255
1925 void WriteInt48_t(float dst, vector val)
1927 WriteInt24_t(dst, val.x);
1928 WriteInt24_t(dst, val.y);
1930 void WriteInt72_t(float dst, vector val)
1932 WriteInt24_t(dst, val.x);
1933 WriteInt24_t(dst, val.y);
1934 WriteInt24_t(dst, val.z);
1939 float float2range11(float f)
1941 // continuous function mapping all reals into -1..1
1942 return f / (fabs(f) + 1);
1945 float float2range01(float f)
1947 // continuous function mapping all reals into 0..1
1948 return 0.5 + 0.5 * float2range11(f);
1951 // from the GNU Scientific Library
1952 float gsl_ran_gaussian_lastvalue;
1953 float gsl_ran_gaussian_lastvalue_set;
1954 float gsl_ran_gaussian(float sigma)
1957 if(gsl_ran_gaussian_lastvalue_set)
1959 gsl_ran_gaussian_lastvalue_set = 0;
1960 return sigma * gsl_ran_gaussian_lastvalue;
1964 a = random() * 2 * M_PI;
1965 b = sqrt(-2 * log(random()));
1966 gsl_ran_gaussian_lastvalue = cos(a) * b;
1967 gsl_ran_gaussian_lastvalue_set = 1;
1968 return sigma * sin(a) * b;
1972 string car(string s)
1975 o = strstrofs(s, " ", 0);
1978 return substring(s, 0, o);
1980 string cdr(string s)
1983 o = strstrofs(s, " ", 0);
1986 return substring(s, o + 1, strlen(s) - (o + 1));
1988 float matchacl(string acl, string str)
1995 t = car(acl); acl = cdr(acl);
1998 if(substring(t, 0, 1) == "-")
2001 t = substring(t, 1, strlen(t) - 1);
2003 else if(substring(t, 0, 1) == "+")
2004 t = substring(t, 1, strlen(t) - 1);
2006 if(substring(t, -1, 1) == "*")
2008 t = substring(t, 0, strlen(t) - 1);
2009 s = substring(str, 0, strlen(t));
2021 float startsWith(string haystack, string needle)
2023 return substring(haystack, 0, strlen(needle)) == needle;
2025 float startsWithNocase(string haystack, string needle)
2027 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2030 string get_model_datafilename(string m, float sk, string fil)
2035 m = "models/player/*_";
2037 m = strcat(m, ftos(sk));
2040 return strcat(m, ".", fil);
2043 float get_model_parameters(string m, float sk)
2045 get_model_parameters_modelname = string_null;
2046 get_model_parameters_modelskin = -1;
2047 get_model_parameters_name = string_null;
2048 get_model_parameters_species = -1;
2049 get_model_parameters_sex = string_null;
2050 get_model_parameters_weight = -1;
2051 get_model_parameters_age = -1;
2052 get_model_parameters_desc = string_null;
2053 get_model_parameters_bone_upperbody = string_null;
2054 get_model_parameters_bone_weapon = string_null;
2055 for(int i = 0; i < MAX_AIM_BONES; ++i)
2057 get_model_parameters_bone_aim[i] = string_null;
2058 get_model_parameters_bone_aimweight[i] = 0;
2060 get_model_parameters_fixbone = 0;
2063 MUTATOR_CALLHOOK(ClearModelParams);
2069 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2070 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2074 if(substring(m, -4, -1) != ".txt")
2076 if(substring(m, -6, 1) != "_")
2078 sk = stof(substring(m, -5, 1));
2079 m = substring(m, 0, -7);
2082 string fn = get_model_datafilename(m, sk, "txt");
2083 int fh = fopen(fn, FILE_READ);
2087 fn = get_model_datafilename(m, sk, "txt");
2088 fh = fopen(fn, FILE_READ);
2093 get_model_parameters_modelname = m;
2094 get_model_parameters_modelskin = sk;
2096 while((s = fgets(fh)))
2099 break; // next lines will be description
2103 get_model_parameters_name = s;
2107 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2108 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2109 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2110 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2111 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2112 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2113 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2116 get_model_parameters_sex = s;
2118 get_model_parameters_weight = stof(s);
2120 get_model_parameters_age = stof(s);
2121 if(c == "description")
2122 get_model_parameters_description = s;
2123 if(c == "bone_upperbody")
2124 get_model_parameters_bone_upperbody = s;
2125 if(c == "bone_weapon")
2126 get_model_parameters_bone_weapon = s;
2128 MUTATOR_CALLHOOK(GetModelParams, c, s);
2130 for(int i = 0; i < MAX_AIM_BONES; ++i)
2131 if(c == strcat("bone_aim", ftos(i)))
2133 get_model_parameters_bone_aimweight[i] = stof(car(s));
2134 get_model_parameters_bone_aim[i] = cdr(s);
2137 get_model_parameters_fixbone = stof(s);
2140 while((s = fgets(fh)))
2142 if(get_model_parameters_desc)
2143 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2145 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2153 vector vec2(vector v)
2160 vector NearestPointOnBox(entity box, vector org)
2162 vector m1, m2, nearest;
2164 m1 = box.mins + box.origin;
2165 m2 = box.maxs + box.origin;
2167 nearest.x = bound(m1_x, org.x, m2_x);
2168 nearest.y = bound(m1_y, org.y, m2_y);
2169 nearest.z = bound(m1_z, org.z, m2_z);
2175 float vercmp_recursive(string v1, string v2)
2181 dot1 = strstrofs(v1, ".", 0);
2182 dot2 = strstrofs(v2, ".", 0);
2186 s1 = substring(v1, 0, dot1);
2190 s2 = substring(v2, 0, dot2);
2192 r = stof(s1) - stof(s2);
2196 r = strcasecmp(s1, s2);
2209 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2212 float vercmp(string v1, string v2)
2214 if(strcasecmp(v1, v2) == 0) // early out check
2223 return vercmp_recursive(v1, v2);
2226 float u8_strsize(string s)
2246 // x-encoding (encoding as zero length invisible string)
2247 const string XENCODE_2 = "xX";
2248 const string XENCODE_22 = "0123456789abcdefABCDEF";
2249 string xencode(int f)
2252 d = f % 22; f = floor(f / 22);
2253 c = f % 22; f = floor(f / 22);
2254 b = f % 22; f = floor(f / 22);
2255 a = f % 2; // f = floor(f / 2);
2258 substring(XENCODE_2, a, 1),
2259 substring(XENCODE_22, b, 1),
2260 substring(XENCODE_22, c, 1),
2261 substring(XENCODE_22, d, 1)
2264 float xdecode(string s)
2267 if(substring(s, 0, 1) != "^")
2271 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2272 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2273 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2274 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2275 if(a < 0 || b < 0 || c < 0 || d < 0)
2277 return ((a * 22 + b) * 22 + c) * 22 + d;
2280 int lowestbit(int f)
2291 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2293 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2296 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2299 // escape the string to make it safe for consoles
2300 string MakeConsoleSafe(string input)
2302 input = strreplace("\n", "", input);
2303 input = strreplace("\\", "\\\\", input);
2304 input = strreplace("$", "$$", input);
2305 input = strreplace("\"", "\\\"", input);
2310 entity ReadCSQCEntity()
2312 int f = ReadShort();
2315 return findfloat(world, entnum, f);
2319 float shutdown_running;
2324 void CSQC_Shutdown()
2330 if(shutdown_running)
2332 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
2336 shutdown_running = 1;
2339 cvar_settemp_restore(); // this must be done LAST, but in any case
2342 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2343 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2344 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2345 // this will use the value:
2347 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2348 // accuracy at x is 1/derivative, i.e.
2349 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2351 void WriteApproxPastTime(float dst, float t)
2353 float dt = time - t;
2355 // warning: this is approximate; do not resend when you don't have to!
2356 // be careful with sendflags here!
2357 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2360 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2363 dt = rint(bound(0, dt, 255));
2369 float ReadApproxPastTime()
2371 float dt = ReadByte();
2373 // map from range...PPROXPASTTIME_MAX / 256
2374 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2376 return servertime - dt;
2381 .float skeleton_bones_index;
2382 void Skeleton_SetBones(entity e)
2384 // set skeleton_bones to the total number of bones on the model
2385 if(e.skeleton_bones_index == e.modelindex)
2386 return; // same model, nothing to update
2389 skelindex = skel_create(e.modelindex);
2390 e.skeleton_bones = skel_get_numbones(skelindex);
2391 skel_delete(skelindex);
2392 e.skeleton_bones_index = e.modelindex;
2396 string to_execute_next_frame;
2397 void execute_next_frame()
2399 if(to_execute_next_frame)
2401 localcmd("\n", to_execute_next_frame, "\n");
2402 strunzone(to_execute_next_frame);
2403 to_execute_next_frame = string_null;
2406 void queue_to_execute_next_frame(string s)
2408 if(to_execute_next_frame)
2410 s = strcat(s, "\n", to_execute_next_frame);
2411 strunzone(to_execute_next_frame);
2413 to_execute_next_frame = strzone(s);
2416 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2419 ((( startspeedfactor + endspeedfactor - 2
2420 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2421 ) * x + startspeedfactor
2425 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2427 if(startspeedfactor < 0 || endspeedfactor < 0)
2431 // if this is the case, the possible zeros of the first derivative are outside
2433 We can calculate this condition as condition
2438 // better, see below:
2439 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2442 // if this is the case, the first derivative has no zeros at all
2443 float se = startspeedfactor + endspeedfactor;
2444 float s_e = startspeedfactor - endspeedfactor;
2445 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2448 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2449 // we also get s_e <= 6 - se
2450 // 3 * (se - 4)^2 + (6 - se)^2
2451 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2452 // Therefore, above "better" check works!
2456 // known good cases:
2464 // (3.5, [0.2..2.3])
2469 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2471 s + e - 2 == 0: no inflection
2474 0 < inflection < 1 if:
2475 0 < 2s + e - 3 < 3s + 3e - 6
2476 2s + e > 3 and 2e + s > 3
2479 0 < inflection < 1 if:
2480 0 > 2s + e - 3 > 3s + 3e - 6
2481 2s + e < 3 and 2e + s < 3
2483 Therefore: there is an inflection point iff:
2484 e outside (3 - s)/2 .. 3 - s*2
2486 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)
2490 .float FindConnectedComponent_processing;
2491 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2493 entity queue_start, queue_end;
2495 // we build a queue of to-be-processed entities.
2496 // queue_start is the next entity to be checked for neighbors
2497 // queue_end is the last entity added
2499 if(e.FindConnectedComponent_processing)
2500 error("recursion or broken cleanup");
2502 // start with a 1-element queue
2503 queue_start = queue_end = e;
2504 queue_end.(fld) = world;
2505 queue_end.FindConnectedComponent_processing = 1;
2507 // for each queued item:
2508 for (; queue_start; queue_start = queue_start.(fld))
2510 // find all neighbors of queue_start
2512 for(t = world; (t = nxt(t, queue_start, pass)); )
2514 if(t.FindConnectedComponent_processing)
2516 if(iscon(t, queue_start, pass))
2518 // it is connected? ADD IT. It will look for neighbors soon too.
2519 queue_end.(fld) = t;
2521 queue_end.(fld) = world;
2522 queue_end.FindConnectedComponent_processing = 1;
2528 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
2529 queue_start.FindConnectedComponent_processing = 0;
2533 vector combine_to_vector(float x, float y, float z)
2535 vector result; result_x = x; result_y = y; result_z = z;
2539 vector get_corner_position(entity box, float corner)
2543 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2544 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2545 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2546 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2547 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2548 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2549 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2550 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2551 default: return '0 0 0';
2556 // color code replace, place inside of sprintf and parse the string
2557 string CCR(string input)
2559 // See the autocvar declarations in util.qh for default values
2561 // foreground/normal colors
2562 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2563 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2564 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2565 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2568 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2569 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2570 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2572 // background colors
2573 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2574 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2578 vector vec3(float x, float y, float z)
2588 vector animfixfps(entity e, vector a, vector b)
2590 // multi-frame anim: keep as-is
2594 dur = frameduration(e.modelindex, a.x);
2598 dur = frameduration(e.modelindex, a.x);
2608 void dedicated_print(string input) // print(), but only print if the server is not local
2610 if(server_is_dedicated) { LOG_INFO(input); }
2615 float Announcer_PickNumber(float type, float num)
2623 case 10: return ANNCE_NUM_GAMESTART_10;
2624 case 9: return ANNCE_NUM_GAMESTART_9;
2625 case 8: return ANNCE_NUM_GAMESTART_8;
2626 case 7: return ANNCE_NUM_GAMESTART_7;
2627 case 6: return ANNCE_NUM_GAMESTART_6;
2628 case 5: return ANNCE_NUM_GAMESTART_5;
2629 case 4: return ANNCE_NUM_GAMESTART_4;
2630 case 3: return ANNCE_NUM_GAMESTART_3;
2631 case 2: return ANNCE_NUM_GAMESTART_2;
2632 case 1: return ANNCE_NUM_GAMESTART_1;
2640 case 10: return ANNCE_NUM_IDLE_10;
2641 case 9: return ANNCE_NUM_IDLE_9;
2642 case 8: return ANNCE_NUM_IDLE_8;
2643 case 7: return ANNCE_NUM_IDLE_7;
2644 case 6: return ANNCE_NUM_IDLE_6;
2645 case 5: return ANNCE_NUM_IDLE_5;
2646 case 4: return ANNCE_NUM_IDLE_4;
2647 case 3: return ANNCE_NUM_IDLE_3;
2648 case 2: return ANNCE_NUM_IDLE_2;
2649 case 1: return ANNCE_NUM_IDLE_1;
2657 case 10: return ANNCE_NUM_KILL_10;
2658 case 9: return ANNCE_NUM_KILL_9;
2659 case 8: return ANNCE_NUM_KILL_8;
2660 case 7: return ANNCE_NUM_KILL_7;
2661 case 6: return ANNCE_NUM_KILL_6;
2662 case 5: return ANNCE_NUM_KILL_5;
2663 case 4: return ANNCE_NUM_KILL_4;
2664 case 3: return ANNCE_NUM_KILL_3;
2665 case 2: return ANNCE_NUM_KILL_2;
2666 case 1: return ANNCE_NUM_KILL_1;
2674 case 10: return ANNCE_NUM_RESPAWN_10;
2675 case 9: return ANNCE_NUM_RESPAWN_9;
2676 case 8: return ANNCE_NUM_RESPAWN_8;
2677 case 7: return ANNCE_NUM_RESPAWN_7;
2678 case 6: return ANNCE_NUM_RESPAWN_6;
2679 case 5: return ANNCE_NUM_RESPAWN_5;
2680 case 4: return ANNCE_NUM_RESPAWN_4;
2681 case 3: return ANNCE_NUM_RESPAWN_3;
2682 case 2: return ANNCE_NUM_RESPAWN_2;
2683 case 1: return ANNCE_NUM_RESPAWN_1;
2687 case CNT_ROUNDSTART:
2691 case 10: return ANNCE_NUM_ROUNDSTART_10;
2692 case 9: return ANNCE_NUM_ROUNDSTART_9;
2693 case 8: return ANNCE_NUM_ROUNDSTART_8;
2694 case 7: return ANNCE_NUM_ROUNDSTART_7;
2695 case 6: return ANNCE_NUM_ROUNDSTART_6;
2696 case 5: return ANNCE_NUM_ROUNDSTART_5;
2697 case 4: return ANNCE_NUM_ROUNDSTART_4;
2698 case 3: return ANNCE_NUM_ROUNDSTART_3;
2699 case 2: return ANNCE_NUM_ROUNDSTART_2;
2700 case 1: return ANNCE_NUM_ROUNDSTART_1;
2708 case 10: return ANNCE_NUM_10;
2709 case 9: return ANNCE_NUM_9;
2710 case 8: return ANNCE_NUM_8;
2711 case 7: return ANNCE_NUM_7;
2712 case 6: return ANNCE_NUM_6;
2713 case 5: return ANNCE_NUM_5;
2714 case 4: return ANNCE_NUM_4;
2715 case 3: return ANNCE_NUM_3;
2716 case 2: return ANNCE_NUM_2;
2717 case 1: return ANNCE_NUM_1;
2722 return NOTIF_ABORT; // abort sending if none of these numbers were right
2727 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2729 switch(nativecontents)
2734 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2736 return DPCONTENTS_WATER;
2738 return DPCONTENTS_SLIME;
2740 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2742 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2747 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2749 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2750 return CONTENT_SOLID;
2751 if(supercontents & DPCONTENTS_SKY)
2753 if(supercontents & DPCONTENTS_LAVA)
2754 return CONTENT_LAVA;
2755 if(supercontents & DPCONTENTS_SLIME)
2756 return CONTENT_SLIME;
2757 if(supercontents & DPCONTENTS_WATER)
2758 return CONTENT_WATER;
2759 return CONTENT_EMPTY;
2763 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2766 (c - 2 * b + a) * (t * t) +
2771 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2774 (c - 2 * b + a) * (2 * t) +