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 // Databases (hash tables)
275 const float DB_BUCKETS = 8192;
276 void db_save(float db, string pFilename)
279 fh = fopen(pFilename, FILE_WRITE);
282 LOG_INFO(strcat("^1Can't write DB to ", pFilename));
286 fputs(fh, strcat(ftos(DB_BUCKETS), "\n"));
287 for(i = 0; i < n; ++i)
288 fputs(fh, strcat(bufstr_get(db, i), "\n"));
297 int db_load(string pFilename)
299 float db, fh, i, j, n;
304 fh = fopen(pFilename, FILE_READ);
308 if(stof(l) == DB_BUCKETS)
311 while((l = fgets(fh)))
314 bufstr_set(db, i, l);
320 // different count of buckets, or a dump?
321 // need to reorganize the database then (SLOW)
323 // note: we also parse the first line (l) in case the DB file is
324 // missing the bucket count
327 n = tokenizebyseparator(l, "\\");
328 for(j = 2; j < n; j += 2)
329 db_put(db, argv(j-1), uri_unescape(argv(j)));
331 while((l = fgets(fh)));
337 void db_dump(float db, string pFilename)
339 float fh, i, j, n, m;
340 fh = fopen(pFilename, FILE_WRITE);
342 error(strcat("Can't dump DB to ", pFilename));
345 for(i = 0; i < n; ++i)
347 m = tokenizebyseparator(bufstr_get(db, i), "\\");
348 for(j = 2; j < m; j += 2)
349 fputs(fh, strcat("\\", argv(j-1), "\\", argv(j), "\n"));
354 void db_close(float db)
359 string db_get(float db, string pKey)
362 h = crc16(false, pKey) % DB_BUCKETS;
363 return uri_unescape(infoget(bufstr_get(db, h), pKey));
366 void db_put(float db, string pKey, string pValue)
369 h = crc16(false, pKey) % DB_BUCKETS;
370 bufstr_set(db, h, infoadd(bufstr_get(db, h), pKey, uri_escape(pValue)));
376 LOG_INFO("LOAD...\n");
377 db = db_load("foo.db");
378 LOG_INFO("LOADED. FILL...\n");
379 for(i = 0; i < DB_BUCKETS; ++i)
380 db_put(db, ftos(random()), "X");
381 LOG_INFO("FILLED. SAVE...\n");
382 db_save(db, "foo.db");
383 LOG_INFO("SAVED. CLOSE...\n");
385 LOG_INFO("CLOSED.\n");
388 // Multiline text file buffers
389 int buf_load(string pFilename)
396 fh = fopen(pFilename, FILE_READ);
403 while((l = fgets(fh)))
405 bufstr_set(buf, i, l);
412 void buf_save(float buf, string pFilename)
415 fh = fopen(pFilename, FILE_WRITE);
417 error(strcat("Can't write buf to ", pFilename));
418 n = buf_getsize(buf);
419 for(i = 0; i < n; ++i)
420 fputs(fh, strcat(bufstr_get(buf, i), "\n"));
424 string format_time(float seconds)
426 float days, hours, minutes;
427 seconds = floor(seconds + 0.5);
428 days = floor(seconds / 864000);
429 seconds -= days * 864000;
430 hours = floor(seconds / 36000);
431 seconds -= hours * 36000;
432 minutes = floor(seconds / 600);
433 seconds -= minutes * 600;
435 return sprintf(_("%d days, %02d:%02d:%02d"), days, hours, minutes, seconds);
437 return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds);
440 string mmsss(float tenths)
444 tenths = floor(tenths + 0.5);
445 minutes = floor(tenths / 600);
446 tenths -= minutes * 600;
447 s = ftos(1000 + tenths);
448 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1));
451 string mmssss(float hundredths)
455 hundredths = floor(hundredths + 0.5);
456 minutes = floor(hundredths / 6000);
457 hundredths -= minutes * 6000;
458 s = ftos(10000 + hundredths);
459 return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 2));
462 string ScoreString(int pFlags, float pValue)
467 pValue = floor(pValue + 0.5); // round
469 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
471 else if(pFlags & SFL_RANK)
473 valstr = ftos(pValue);
475 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
476 valstr = strcat(valstr, "th");
477 else if(substring(valstr, l - 1, 1) == "1")
478 valstr = strcat(valstr, "st");
479 else if(substring(valstr, l - 1, 1) == "2")
480 valstr = strcat(valstr, "nd");
481 else if(substring(valstr, l - 1, 1) == "3")
482 valstr = strcat(valstr, "rd");
484 valstr = strcat(valstr, "th");
486 else if(pFlags & SFL_TIME)
487 valstr = TIME_ENCODED_TOSTRING(pValue);
489 valstr = ftos(pValue);
494 // compressed vector format:
495 // like MD3, just even shorter
496 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
497 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
498 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
499 // length = 2^(length_encoded/8) / 8
500 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
501 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
502 // the special value 0 indicates the zero vector
504 float lengthLogTable[128];
506 float invertLengthLog(float x)
510 if(x >= lengthLogTable[127])
512 if(x <= lengthLogTable[0])
520 m = floor((l + r) / 2);
521 if(lengthLogTable[m] < x)
527 // now: r is >=, l is <
528 float lerr = (x - lengthLogTable[l]);
529 float rerr = (lengthLogTable[r] - x);
535 vector decompressShortVector(int data)
540 float p = (data & 0xF000) / 0x1000;
541 float y = (data & 0x0F80) / 0x80;
542 int len = (data & 0x007F);
544 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
557 y = .19634954084936207740 * y;
558 p = .19634954084936207740 * p - 1.57079632679489661922;
559 out.x = cos(y) * cos(p);
560 out.y = sin(y) * cos(p);
564 //print("decompressed: ", vtos(out), "\n");
566 return out * lengthLogTable[len];
569 float compressShortVector(vector vec)
575 //print("compress: ", vtos(vec), "\n");
576 ang = vectoangles(vec);
580 if(ang.x < -90 && ang.x > +90)
581 error("BOGUS vectoangles");
582 //print("angles: ", vtos(ang), "\n");
584 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
593 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
594 len = invertLengthLog(vlen(vec));
596 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
598 return (p * 0x1000) + (y * 0x80) + len;
601 void compressShortVector_init()
604 float f = pow(2, 1/8);
606 for(i = 0; i < 128; ++i)
608 lengthLogTable[i] = l;
612 if(cvar("developer"))
614 LOG_INFO("Verifying vector compression table...\n");
615 for(i = 0x0F00; i < 0xFFFF; ++i)
616 if(i != compressShortVector(decompressShortVector(i)))
618 LOG_INFO("BROKEN vector compression: ", ftos(i));
619 LOG_INFO(" -> ", vtos(decompressShortVector(i)));
620 LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
629 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
631 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
632 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
633 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
634 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
635 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
636 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
637 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
638 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
639 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
640 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
641 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
642 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
647 string fixPriorityList(string order, float from, float to, float subtract, float complete)
652 n = tokenize_console(order);
654 for(i = 0; i < n; ++i)
659 if(w >= from && w <= to)
660 neworder = strcat(neworder, ftos(w), " ");
664 if(w >= from && w <= to)
665 neworder = strcat(neworder, ftos(w), " ");
672 n = tokenize_console(neworder);
673 for(w = to; w >= from; --w)
675 for(i = 0; i < n; ++i)
676 if(stof(argv(i)) == w)
678 if(i == n) // not found
679 neworder = strcat(neworder, ftos(w), " ");
683 return substring(neworder, 0, strlen(neworder) - 1);
686 string mapPriorityList(string order, string(string) mapfunc)
691 n = tokenize_console(order);
693 for(i = 0; i < n; ++i)
694 neworder = strcat(neworder, mapfunc(argv(i)), " ");
696 return substring(neworder, 0, strlen(neworder) - 1);
699 string swapInPriorityList(string order, float i, float j)
704 n = tokenize_console(order);
706 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
709 for(w = 0; w < n; ++w)
712 s = strcat(s, argv(j), " ");
714 s = strcat(s, argv(i), " ");
716 s = strcat(s, argv(w), " ");
718 return substring(s, 0, strlen(s) - 1);
724 float cvar_value_issafe(string s)
726 if(strstrofs(s, "\"", 0) >= 0)
728 if(strstrofs(s, "\\", 0) >= 0)
730 if(strstrofs(s, ";", 0) >= 0)
732 if(strstrofs(s, "$", 0) >= 0)
734 if(strstrofs(s, "\r", 0) >= 0)
736 if(strstrofs(s, "\n", 0) >= 0)
742 void get_mi_min_max(float mode)
747 strunzone(mi_shortname);
748 mi_shortname = mapname;
749 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
750 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
751 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
752 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
753 mi_shortname = strzone(mi_shortname);
765 MapInfo_Get_ByName(mi_shortname, 0, 0);
766 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
768 mi_min = MapInfo_Map_mins;
769 mi_max = MapInfo_Map_maxs;
777 tracebox('1 0 0' * mi.x,
778 '0 1 0' * mi.y + '0 0 1' * mi.z,
779 '0 1 0' * ma.y + '0 0 1' * ma.z,
783 if(!trace_startsolid)
784 mi_min.x = trace_endpos.x;
786 tracebox('0 1 0' * mi.y,
787 '1 0 0' * mi.x + '0 0 1' * mi.z,
788 '1 0 0' * ma.x + '0 0 1' * ma.z,
792 if(!trace_startsolid)
793 mi_min.y = trace_endpos.y;
795 tracebox('0 0 1' * mi.z,
796 '1 0 0' * mi.x + '0 1 0' * mi.y,
797 '1 0 0' * ma.x + '0 1 0' * ma.y,
801 if(!trace_startsolid)
802 mi_min.z = trace_endpos.z;
804 tracebox('1 0 0' * ma.x,
805 '0 1 0' * mi.y + '0 0 1' * mi.z,
806 '0 1 0' * ma.y + '0 0 1' * ma.z,
810 if(!trace_startsolid)
811 mi_max.x = trace_endpos.x;
813 tracebox('0 1 0' * ma.y,
814 '1 0 0' * mi.x + '0 0 1' * mi.z,
815 '1 0 0' * ma.x + '0 0 1' * ma.z,
819 if(!trace_startsolid)
820 mi_max.y = trace_endpos.y;
822 tracebox('0 0 1' * ma.z,
823 '1 0 0' * mi.x + '0 1 0' * mi.y,
824 '1 0 0' * ma.x + '0 1 0' * ma.y,
828 if(!trace_startsolid)
829 mi_max.z = trace_endpos.z;
834 void get_mi_min_max_texcoords(float mode)
838 get_mi_min_max(mode);
843 // extend mi_picmax to get a square aspect ratio
844 // center the map in that area
845 extend = mi_picmax - mi_picmin;
846 if(extend.y > extend.x)
848 mi_picmin.x -= (extend.y - extend.x) * 0.5;
849 mi_picmax.x += (extend.y - extend.x) * 0.5;
853 mi_picmin.y -= (extend.x - extend.y) * 0.5;
854 mi_picmax.y += (extend.x - extend.y) * 0.5;
857 // add another some percent
858 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
862 // calculate the texcoords
863 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
864 // first the two corners of the origin
865 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
866 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
867 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
868 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
869 // then the other corners
870 mi_pictexcoord1_x = mi_pictexcoord0_x;
871 mi_pictexcoord1_y = mi_pictexcoord2_y;
872 mi_pictexcoord3_x = mi_pictexcoord2_x;
873 mi_pictexcoord3_y = mi_pictexcoord0_y;
877 float cvar_settemp(string tmp_cvar, string tmp_value)
879 float created_saved_value;
882 created_saved_value = 0;
884 if (!(tmp_cvar || tmp_value))
886 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !\n");
890 if(!cvar_type(tmp_cvar))
892 LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
896 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
897 if(e.netname == tmp_cvar)
898 created_saved_value = -1; // skip creation
900 if(created_saved_value != -1)
902 // creating a new entity to keep track of this cvar
904 e.classname = "saved_cvar_value";
905 e.netname = strzone(tmp_cvar);
906 e.message = strzone(cvar_string(tmp_cvar));
907 created_saved_value = 1;
910 // update the cvar to the value given
911 cvar_set(tmp_cvar, tmp_value);
913 return created_saved_value;
916 float cvar_settemp_restore()
920 while((e = find(e, classname, "saved_cvar_value")))
922 if(cvar_type(e.netname))
924 cvar_set(e.netname, e.message);
929 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
935 float almost_equals(float a, float b)
938 eps = (max(a, -a) + max(b, -b)) * 0.001;
939 if(a - b < eps && b - a < eps)
944 float almost_in_bounds(float a, float b, float c)
947 eps = (max(a, -a) + max(c, -c)) * 0.001;
950 return b == median(a - eps, b, c + eps);
953 float power2of(float e)
957 float log2of(float x)
959 // NOTE: generated code
1032 float rgb_mi_ma_to_hue(vector rgb, float mi, float ma)
1036 else if(ma == rgb.x)
1039 return (rgb.y - rgb.z) / (ma - mi);
1041 return (rgb.y - rgb.z) / (ma - mi) + 6;
1043 else if(ma == rgb.y)
1044 return (rgb.z - rgb.x) / (ma - mi) + 2;
1045 else // if(ma == rgb_z)
1046 return (rgb.x - rgb.y) / (ma - mi) + 4;
1049 vector hue_mi_ma_to_rgb(float hue, float mi, float ma)
1053 hue -= 6 * floor(hue / 6);
1055 //else if(ma == rgb_x)
1056 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1060 rgb.y = hue * (ma - mi) + mi;
1063 //else if(ma == rgb_y)
1064 // hue = 60 * (rgb_z - rgb_x) / (ma - mi) + 120;
1067 rgb.x = (2 - hue) * (ma - mi) + mi;
1075 rgb.z = (hue - 2) * (ma - mi) + mi;
1077 //else // if(ma == rgb_z)
1078 // hue = 60 * (rgb_x - rgb_y) / (ma - mi) + 240;
1082 rgb.y = (4 - hue) * (ma - mi) + mi;
1087 rgb.x = (hue - 4) * (ma - mi) + mi;
1091 //else if(ma == rgb_x)
1092 // hue = 60 * (rgb_y - rgb_z) / (ma - mi);
1093 else // if(hue <= 6)
1097 rgb.z = (6 - hue) * (ma - mi) + mi;
1103 vector rgb_to_hsv(vector rgb)
1108 mi = min(rgb.x, rgb.y, rgb.z);
1109 ma = max(rgb.x, rgb.y, rgb.z);
1111 hsv.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1122 vector hsv_to_rgb(vector hsv)
1124 return hue_mi_ma_to_rgb(hsv.x, hsv.z * (1 - hsv.y), hsv.z);
1127 vector rgb_to_hsl(vector rgb)
1132 mi = min(rgb.x, rgb.y, rgb.z);
1133 ma = max(rgb.x, rgb.y, rgb.z);
1135 hsl.x = rgb_mi_ma_to_hue(rgb, mi, ma);
1137 hsl.z = 0.5 * (mi + ma);
1140 else if(hsl.z <= 0.5)
1141 hsl.y = (ma - mi) / (2*hsl.z);
1142 else // if(hsl_z > 0.5)
1143 hsl.y = (ma - mi) / (2 - 2*hsl.z);
1148 vector hsl_to_rgb(vector hsl)
1150 float mi, ma, maminusmi;
1153 maminusmi = hsl.y * 2 * hsl.z;
1155 maminusmi = hsl.y * (2 - 2 * hsl.z);
1157 // hsl_z = 0.5 * mi + 0.5 * ma
1158 // maminusmi = - mi + ma
1159 mi = hsl.z - 0.5 * maminusmi;
1160 ma = hsl.z + 0.5 * maminusmi;
1162 return hue_mi_ma_to_rgb(hsl.x, mi, ma);
1165 string rgb_to_hexcolor(vector rgb)
1170 DEC_TO_HEXDIGIT(floor(rgb.x * 15 + 0.5)),
1171 DEC_TO_HEXDIGIT(floor(rgb.y * 15 + 0.5)),
1172 DEC_TO_HEXDIGIT(floor(rgb.z * 15 + 0.5))
1176 // requires that m2>m1 in all coordinates, and that m4>m3
1177 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;}
1179 // requires the same, but is a stronger condition
1180 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;}
1185 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
1188 // The following function is SLOW.
1189 // For your safety and for the protection of those around you...
1190 // DO NOT CALL THIS AT HOME.
1191 // No really, don't.
1192 if(w(theText, theSize) <= maxWidth)
1193 return strlen(theText); // yeah!
1195 // binary search for right place to cut string
1197 float left, right, middle; // this always works
1199 right = strlen(theText); // this always fails
1202 middle = floor((left + right) / 2);
1203 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
1208 while(left < right - 1);
1210 if(w("^7", theSize) == 0) // detect color codes support in the width function
1212 // NOTE: when color codes are involved, this binary search is,
1213 // mathematically, BROKEN. However, it is obviously guaranteed to
1214 // terminate, as the range still halves each time - but nevertheless, it is
1215 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1216 // range, and "right" is outside).
1218 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1219 // and decrease left on the basis of the chars detected of the truncated tag
1220 // Even if the ^xrgb tag is not complete/correct, left is decreased
1221 // (sometimes too much but with a correct result)
1222 // it fixes also ^[0-9]
1223 while(left >= 1 && substring(theText, left-1, 1) == "^")
1226 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1228 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1230 ch = str2chr(theText, left-1);
1231 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1234 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1236 ch = str2chr(theText, left-2);
1237 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1239 ch = str2chr(theText, left-1);
1240 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1249 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
1252 // The following function is SLOW.
1253 // For your safety and for the protection of those around you...
1254 // DO NOT CALL THIS AT HOME.
1255 // No really, don't.
1256 if(w(theText) <= maxWidth)
1257 return strlen(theText); // yeah!
1259 // binary search for right place to cut string
1261 float left, right, middle; // this always works
1263 right = strlen(theText); // this always fails
1266 middle = floor((left + right) / 2);
1267 if(w(substring(theText, 0, middle)) <= maxWidth)
1272 while(left < right - 1);
1274 if(w("^7") == 0) // detect color codes support in the width function
1276 // NOTE: when color codes are involved, this binary search is,
1277 // mathematically, BROKEN. However, it is obviously guaranteed to
1278 // terminate, as the range still halves each time - but nevertheless, it is
1279 // guaranteed that it finds ONE valid cutoff place (where "left" is in
1280 // range, and "right" is outside).
1282 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
1283 // and decrease left on the basis of the chars detected of the truncated tag
1284 // Even if the ^xrgb tag is not complete/correct, left is decreased
1285 // (sometimes too much but with a correct result)
1286 // it fixes also ^[0-9]
1287 while(left >= 1 && substring(theText, left-1, 1) == "^")
1290 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
1292 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
1294 ch = str2chr(theText, left-1);
1295 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
1298 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
1300 ch = str2chr(theText, left-2);
1301 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
1303 ch = str2chr(theText, left-1);
1304 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
1313 string find_last_color_code(string s)
1315 int start = strstrofs(s, "^", 0);
1316 if (start == -1) // no caret found
1318 int len = strlen(s)-1;
1320 for(i = len; i >= start; --i)
1322 if(substring(s, i, 1) != "^")
1326 while (i-carets >= start && substring(s, i-carets, 1) == "^")
1329 // check if carets aren't all escaped
1333 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
1334 return substring(s, i, 2);
1337 if(substring(s, i+1, 1) == "x")
1338 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
1339 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
1340 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
1341 return substring(s, i, 5);
1343 i -= carets; // this also skips one char before the carets
1349 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1355 s = getWrappedLine_remaining;
1359 getWrappedLine_remaining = string_null;
1360 return s; // the line has no size ANYWAY, nothing would be displayed.
1363 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
1364 if(cantake > 0 && cantake < strlen(s))
1367 while(take > 0 && substring(s, take, 1) != " ")
1371 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1372 if(getWrappedLine_remaining == "")
1373 getWrappedLine_remaining = string_null;
1374 else if (tw("^7", theFontSize) == 0)
1375 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1376 return substring(s, 0, cantake);
1380 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
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, take)), getWrappedLine_remaining);
1385 return substring(s, 0, take);
1390 getWrappedLine_remaining = string_null;
1395 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
1401 s = getWrappedLine_remaining;
1405 getWrappedLine_remaining = string_null;
1406 return s; // the line has no size ANYWAY, nothing would be displayed.
1409 cantake = textLengthUpToLength(s, w, tw);
1410 if(cantake > 0 && cantake < strlen(s))
1413 while(take > 0 && substring(s, take, 1) != " ")
1417 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1418 if(getWrappedLine_remaining == "")
1419 getWrappedLine_remaining = string_null;
1420 else if (tw("^7") == 0)
1421 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1422 return substring(s, 0, cantake);
1426 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
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, take)), getWrappedLine_remaining);
1431 return substring(s, 0, take);
1436 getWrappedLine_remaining = string_null;
1441 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1443 if(tw(theText, theFontSize) <= maxWidth)
1446 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1449 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1451 if(tw(theText) <= maxWidth)
1454 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1457 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
1459 string subpattern, subpattern2, subpattern3, subpattern4;
1460 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1462 subpattern2 = ",teams,";
1464 subpattern2 = ",noteams,";
1466 subpattern3 = ",teamspawns,";
1468 subpattern3 = ",noteamspawns,";
1469 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1470 subpattern4 = ",race,";
1472 subpattern4 = string_null;
1474 if(substring(pattern, 0, 1) == "-")
1476 pattern = substring(pattern, 1, strlen(pattern) - 1);
1477 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1479 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1481 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1483 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1488 if(substring(pattern, 0, 1) == "+")
1489 pattern = substring(pattern, 1, strlen(pattern) - 1);
1490 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1491 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1492 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1496 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1503 string substring_range(string s, float b, float e)
1505 return substring(s, b, e - b);
1508 string swapwords(string str, float i, float j)
1511 string s1, s2, s3, s4, s5;
1512 float si, ei, sj, ej, s0, en;
1513 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1514 si = argv_start_index(i);
1515 sj = argv_start_index(j);
1516 ei = argv_end_index(i);
1517 ej = argv_end_index(j);
1518 s0 = argv_start_index(0);
1519 en = argv_end_index(n-1);
1520 s1 = substring_range(str, s0, si);
1521 s2 = substring_range(str, si, ei);
1522 s3 = substring_range(str, ei, sj);
1523 s4 = substring_range(str, sj, ej);
1524 s5 = substring_range(str, ej, en);
1525 return strcat(s1, s4, s3, s2, s5);
1528 string _shufflewords_str;
1529 void _shufflewords_swapfunc(float i, float j, entity pass)
1531 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1533 string shufflewords(string str)
1536 _shufflewords_str = str;
1537 n = tokenizebyseparator(str, " ");
1538 shuffle(n, _shufflewords_swapfunc, world);
1539 str = _shufflewords_str;
1540 _shufflewords_str = string_null;
1544 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1560 // actually, every number solves the equation!
1571 if(a > 0) // put the smaller solution first
1573 v.x = ((-b)-D) / (2*a);
1574 v.y = ((-b)+D) / (2*a);
1578 v.x = (-b+D) / (2*a);
1579 v.y = (-b-D) / (2*a);
1585 // complex solutions!
1598 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1602 // make origin and speed relative
1607 // now solve for ret, ret normalized:
1608 // eorg + t * evel == t * ret * spd
1609 // or, rather, solve for t:
1610 // |eorg + t * evel| == t * spd
1611 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1612 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1613 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1614 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1615 // q = (eorg * eorg) / (evel * evel - spd * spd)
1616 if(!solution.z) // no real solution
1619 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1620 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1621 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1622 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1623 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1624 // spd < |evel| * sin angle(evel, eorg)
1627 else if(solution.x > 0)
1629 // both solutions > 0: take the smaller one
1630 // happens if p < 0 and q > 0
1631 ret = normalize(eorg + solution.x * evel);
1633 else if(solution.y > 0)
1635 // one solution > 0: take the larger one
1636 // happens if q < 0 or q == 0 and p < 0
1637 ret = normalize(eorg + solution.y * evel);
1641 // no solution > 0: reject
1642 // happens if p > 0 and q >= 0
1643 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1644 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1649 // "Enemy is moving away from me at more than spd"
1653 // NOTE: we always got a solution if spd > |evel|
1655 if(newton_style == 2)
1656 ret = normalize(ret * spd + myvel);
1661 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1666 if(newton_style == 2)
1668 // true Newtonian projectiles with automatic aim adjustment
1670 // solve: |outspeed * mydir - myvel| = spd
1671 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1672 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1676 // myvel^2 - (mydir * myvel)^2 > spd^2
1677 // velocity without mydir component > spd
1678 // fire at smallest possible spd that works?
1679 // |(mydir * myvel) * myvel - myvel| = spd
1681 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1685 outspeed = solution.y; // the larger one
1688 //outspeed = 0; // slowest possible shot
1689 outspeed = solution.x; // the real part (that is, the average!)
1690 //dprint("impossible shot, adjusting\n");
1693 outspeed = bound(spd * mi, outspeed, spd * ma);
1694 return mydir * outspeed;
1698 return myvel + spd * mydir;
1701 float compressShotOrigin(vector v)
1705 y = rint(v.y * 4) + 128;
1706 z = rint(v.z * 4) + 128;
1707 if(x > 255 || x < 0)
1709 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1710 x = bound(0, x, 255);
1712 if(y > 255 || y < 0)
1714 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1715 y = bound(0, y, 255);
1717 if(z > 255 || z < 0)
1719 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1720 z = bound(0, z, 255);
1722 return x * 0x10000 + y * 0x100 + z;
1724 vector decompressShotOrigin(int f)
1727 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1728 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1729 v.z = ((f & 0xFF) - 128) / 4;
1734 void RandomSelection_Init()
1736 RandomSelection_totalweight = 0;
1737 RandomSelection_chosen_ent = world;
1738 RandomSelection_chosen_float = 0;
1739 RandomSelection_chosen_string = string_null;
1740 RandomSelection_best_priority = -1;
1742 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1744 if(priority > RandomSelection_best_priority)
1746 RandomSelection_best_priority = priority;
1747 RandomSelection_chosen_ent = e;
1748 RandomSelection_chosen_float = f;
1749 RandomSelection_chosen_string = s;
1750 RandomSelection_totalweight = weight;
1752 else if(priority == RandomSelection_best_priority)
1754 RandomSelection_totalweight += weight;
1755 if(random() * RandomSelection_totalweight <= weight)
1757 RandomSelection_chosen_ent = e;
1758 RandomSelection_chosen_float = f;
1759 RandomSelection_chosen_string = s;
1765 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1767 // NOTE: we'll always choose the SMALLER value...
1768 float healthdamage, armordamage, armorideal;
1769 if (deathtype == DEATH_DROWN) // Why should armor help here...
1772 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1773 armordamage = a + (h - 1); // damage we can take if we could use more armor
1774 armorideal = healthdamage * armorblock;
1776 if(armordamage < healthdamage)
1789 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1792 if (deathtype == DEATH_DROWN) // Why should armor help here...
1794 v.y = bound(0, damage * armorblock, a); // save
1795 v.x = bound(0, damage - v.y, damage); // take
1801 string getcurrentmod()
1805 m = cvar_string("fs_gamedir");
1806 n = tokenize_console(m);
1817 int v = ReadShort() * 256; // note: this is signed
1818 v += ReadByte(); // note: this is unsigned
1821 vector ReadInt48_t()
1824 v.x = ReadInt24_t();
1825 v.y = ReadInt24_t();
1829 vector ReadInt72_t()
1832 v.x = ReadInt24_t();
1833 v.y = ReadInt24_t();
1834 v.z = ReadInt24_t();
1838 void WriteInt24_t(float dst, float val)
1841 WriteShort(dst, (v = floor(val / 256)));
1842 WriteByte(dst, val - v * 256); // 0..255
1844 void WriteInt48_t(float dst, vector val)
1846 WriteInt24_t(dst, val.x);
1847 WriteInt24_t(dst, val.y);
1849 void WriteInt72_t(float dst, vector val)
1851 WriteInt24_t(dst, val.x);
1852 WriteInt24_t(dst, val.y);
1853 WriteInt24_t(dst, val.z);
1858 float float2range11(float f)
1860 // continuous function mapping all reals into -1..1
1861 return f / (fabs(f) + 1);
1864 float float2range01(float f)
1866 // continuous function mapping all reals into 0..1
1867 return 0.5 + 0.5 * float2range11(f);
1870 // from the GNU Scientific Library
1871 float gsl_ran_gaussian_lastvalue;
1872 float gsl_ran_gaussian_lastvalue_set;
1873 float gsl_ran_gaussian(float sigma)
1876 if(gsl_ran_gaussian_lastvalue_set)
1878 gsl_ran_gaussian_lastvalue_set = 0;
1879 return sigma * gsl_ran_gaussian_lastvalue;
1883 a = random() * 2 * M_PI;
1884 b = sqrt(-2 * log(random()));
1885 gsl_ran_gaussian_lastvalue = cos(a) * b;
1886 gsl_ran_gaussian_lastvalue_set = 1;
1887 return sigma * sin(a) * b;
1891 string car(string s)
1894 o = strstrofs(s, " ", 0);
1897 return substring(s, 0, o);
1899 string cdr(string s)
1902 o = strstrofs(s, " ", 0);
1905 return substring(s, o + 1, strlen(s) - (o + 1));
1907 float matchacl(string acl, string str)
1914 t = car(acl); acl = cdr(acl);
1917 if(substring(t, 0, 1) == "-")
1920 t = substring(t, 1, strlen(t) - 1);
1922 else if(substring(t, 0, 1) == "+")
1923 t = substring(t, 1, strlen(t) - 1);
1925 if(substring(t, -1, 1) == "*")
1927 t = substring(t, 0, strlen(t) - 1);
1928 s = substring(str, 0, strlen(t));
1940 float startsWith(string haystack, string needle)
1942 return substring(haystack, 0, strlen(needle)) == needle;
1944 float startsWithNocase(string haystack, string needle)
1946 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
1949 string get_model_datafilename(string m, float sk, string fil)
1954 m = "models/player/*_";
1956 m = strcat(m, ftos(sk));
1959 return strcat(m, ".", fil);
1962 float get_model_parameters(string m, float sk)
1964 get_model_parameters_modelname = string_null;
1965 get_model_parameters_modelskin = -1;
1966 get_model_parameters_name = string_null;
1967 get_model_parameters_species = -1;
1968 get_model_parameters_sex = string_null;
1969 get_model_parameters_weight = -1;
1970 get_model_parameters_age = -1;
1971 get_model_parameters_desc = string_null;
1972 get_model_parameters_bone_upperbody = string_null;
1973 get_model_parameters_bone_weapon = string_null;
1974 for(int i = 0; i < MAX_AIM_BONES; ++i)
1976 get_model_parameters_bone_aim[i] = string_null;
1977 get_model_parameters_bone_aimweight[i] = 0;
1979 get_model_parameters_fixbone = 0;
1982 MUTATOR_CALLHOOK(ClearModelParams);
1988 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1989 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1993 if(substring(m, -4, -1) != ".txt")
1995 if(substring(m, -6, 1) != "_")
1997 sk = stof(substring(m, -5, 1));
1998 m = substring(m, 0, -7);
2001 string fn = get_model_datafilename(m, sk, "txt");
2002 int fh = fopen(fn, FILE_READ);
2006 fn = get_model_datafilename(m, sk, "txt");
2007 fh = fopen(fn, FILE_READ);
2012 get_model_parameters_modelname = m;
2013 get_model_parameters_modelskin = sk;
2015 while((s = fgets(fh)))
2018 break; // next lines will be description
2022 get_model_parameters_name = s;
2026 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2027 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2028 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2029 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2030 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2031 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2032 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2035 get_model_parameters_sex = s;
2037 get_model_parameters_weight = stof(s);
2039 get_model_parameters_age = stof(s);
2040 if(c == "description")
2041 get_model_parameters_description = s;
2042 if(c == "bone_upperbody")
2043 get_model_parameters_bone_upperbody = s;
2044 if(c == "bone_weapon")
2045 get_model_parameters_bone_weapon = s;
2047 MUTATOR_CALLHOOK(GetModelParams, c, s);
2049 for(int i = 0; i < MAX_AIM_BONES; ++i)
2050 if(c == strcat("bone_aim", ftos(i)))
2052 get_model_parameters_bone_aimweight[i] = stof(car(s));
2053 get_model_parameters_bone_aim[i] = cdr(s);
2056 get_model_parameters_fixbone = stof(s);
2059 while((s = fgets(fh)))
2061 if(get_model_parameters_desc)
2062 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2064 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2072 vector vec2(vector v)
2079 vector NearestPointOnBox(entity box, vector org)
2081 vector m1, m2, nearest;
2083 m1 = box.mins + box.origin;
2084 m2 = box.maxs + box.origin;
2086 nearest.x = bound(m1_x, org.x, m2_x);
2087 nearest.y = bound(m1_y, org.y, m2_y);
2088 nearest.z = bound(m1_z, org.z, m2_z);
2094 float vercmp_recursive(string v1, string v2)
2100 dot1 = strstrofs(v1, ".", 0);
2101 dot2 = strstrofs(v2, ".", 0);
2105 s1 = substring(v1, 0, dot1);
2109 s2 = substring(v2, 0, dot2);
2111 r = stof(s1) - stof(s2);
2115 r = strcasecmp(s1, s2);
2128 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2131 float vercmp(string v1, string v2)
2133 if(strcasecmp(v1, v2) == 0) // early out check
2142 return vercmp_recursive(v1, v2);
2145 float u8_strsize(string s)
2165 // x-encoding (encoding as zero length invisible string)
2166 const string XENCODE_2 = "xX";
2167 const string XENCODE_22 = "0123456789abcdefABCDEF";
2168 string xencode(int f)
2171 d = f % 22; f = floor(f / 22);
2172 c = f % 22; f = floor(f / 22);
2173 b = f % 22; f = floor(f / 22);
2174 a = f % 2; // f = floor(f / 2);
2177 substring(XENCODE_2, a, 1),
2178 substring(XENCODE_22, b, 1),
2179 substring(XENCODE_22, c, 1),
2180 substring(XENCODE_22, d, 1)
2183 float xdecode(string s)
2186 if(substring(s, 0, 1) != "^")
2190 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2191 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2192 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2193 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2194 if(a < 0 || b < 0 || c < 0 || d < 0)
2196 return ((a * 22 + b) * 22 + c) * 22 + d;
2199 int lowestbit(int f)
2210 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2212 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2215 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2218 // escape the string to make it safe for consoles
2219 string MakeConsoleSafe(string input)
2221 input = strreplace("\n", "", input);
2222 input = strreplace("\\", "\\\\", input);
2223 input = strreplace("$", "$$", input);
2224 input = strreplace("\"", "\\\"", input);
2229 entity ReadCSQCEntity()
2231 int f = ReadShort();
2234 return findfloat(world, entnum, f);
2238 float shutdown_running;
2243 void CSQC_Shutdown()
2249 if(shutdown_running)
2251 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
2255 shutdown_running = 1;
2258 cvar_settemp_restore(); // this must be done LAST, but in any case
2261 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2262 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2263 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2264 // this will use the value:
2266 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2267 // accuracy at x is 1/derivative, i.e.
2268 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2270 void WriteApproxPastTime(float dst, float t)
2272 float dt = time - t;
2274 // warning: this is approximate; do not resend when you don't have to!
2275 // be careful with sendflags here!
2276 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2279 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2282 dt = rint(bound(0, dt, 255));
2288 float ReadApproxPastTime()
2290 float dt = ReadByte();
2292 // map from range...PPROXPASTTIME_MAX / 256
2293 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2295 return servertime - dt;
2300 .float skeleton_bones_index;
2301 void Skeleton_SetBones(entity e)
2303 // set skeleton_bones to the total number of bones on the model
2304 if(e.skeleton_bones_index == e.modelindex)
2305 return; // same model, nothing to update
2308 skelindex = skel_create(e.modelindex);
2309 e.skeleton_bones = skel_get_numbones(skelindex);
2310 skel_delete(skelindex);
2311 e.skeleton_bones_index = e.modelindex;
2315 string to_execute_next_frame;
2316 void execute_next_frame()
2318 if(to_execute_next_frame)
2320 localcmd("\n", to_execute_next_frame, "\n");
2321 strunzone(to_execute_next_frame);
2322 to_execute_next_frame = string_null;
2325 void queue_to_execute_next_frame(string s)
2327 if(to_execute_next_frame)
2329 s = strcat(s, "\n", to_execute_next_frame);
2330 strunzone(to_execute_next_frame);
2332 to_execute_next_frame = strzone(s);
2335 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2338 ((( startspeedfactor + endspeedfactor - 2
2339 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2340 ) * x + startspeedfactor
2344 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2346 if(startspeedfactor < 0 || endspeedfactor < 0)
2350 // if this is the case, the possible zeros of the first derivative are outside
2352 We can calculate this condition as condition
2357 // better, see below:
2358 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2361 // if this is the case, the first derivative has no zeros at all
2362 float se = startspeedfactor + endspeedfactor;
2363 float s_e = startspeedfactor - endspeedfactor;
2364 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2367 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2368 // we also get s_e <= 6 - se
2369 // 3 * (se - 4)^2 + (6 - se)^2
2370 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2371 // Therefore, above "better" check works!
2375 // known good cases:
2383 // (3.5, [0.2..2.3])
2388 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2390 s + e - 2 == 0: no inflection
2393 0 < inflection < 1 if:
2394 0 < 2s + e - 3 < 3s + 3e - 6
2395 2s + e > 3 and 2e + s > 3
2398 0 < inflection < 1 if:
2399 0 > 2s + e - 3 > 3s + 3e - 6
2400 2s + e < 3 and 2e + s < 3
2402 Therefore: there is an inflection point iff:
2403 e outside (3 - s)/2 .. 3 - s*2
2405 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)
2409 .float FindConnectedComponent_processing;
2410 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2412 entity queue_start, queue_end;
2414 // we build a queue of to-be-processed entities.
2415 // queue_start is the next entity to be checked for neighbors
2416 // queue_end is the last entity added
2418 if(e.FindConnectedComponent_processing)
2419 error("recursion or broken cleanup");
2421 // start with a 1-element queue
2422 queue_start = queue_end = e;
2423 queue_end.(fld) = world;
2424 queue_end.FindConnectedComponent_processing = 1;
2426 // for each queued item:
2427 for (; queue_start; queue_start = queue_start.(fld))
2429 // find all neighbors of queue_start
2431 for(t = world; (t = nxt(t, queue_start, pass)); )
2433 if(t.FindConnectedComponent_processing)
2435 if(iscon(t, queue_start, pass))
2437 // it is connected? ADD IT. It will look for neighbors soon too.
2438 queue_end.(fld) = t;
2440 queue_end.(fld) = world;
2441 queue_end.FindConnectedComponent_processing = 1;
2447 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
2448 queue_start.FindConnectedComponent_processing = 0;
2452 vector combine_to_vector(float x, float y, float z)
2454 vector result; result_x = x; result_y = y; result_z = z;
2458 vector get_corner_position(entity box, float corner)
2462 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2463 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2464 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2465 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2466 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2467 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2468 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2469 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2470 default: return '0 0 0';
2475 // color code replace, place inside of sprintf and parse the string
2476 string CCR(string input)
2478 // See the autocvar declarations in util.qh for default values
2480 // foreground/normal colors
2481 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2482 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2483 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2484 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2487 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2488 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2489 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2491 // background colors
2492 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2493 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2497 vector vec3(float x, float y, float z)
2507 vector animfixfps(entity e, vector a, vector b)
2509 // multi-frame anim: keep as-is
2513 dur = frameduration(e.modelindex, a.x);
2517 dur = frameduration(e.modelindex, a.x);
2527 void dedicated_print(string input) // print(), but only print if the server is not local
2529 if(server_is_dedicated) { LOG_INFO(input); }
2534 float Announcer_PickNumber(float type, float num)
2542 case 10: return ANNCE_NUM_GAMESTART_10;
2543 case 9: return ANNCE_NUM_GAMESTART_9;
2544 case 8: return ANNCE_NUM_GAMESTART_8;
2545 case 7: return ANNCE_NUM_GAMESTART_7;
2546 case 6: return ANNCE_NUM_GAMESTART_6;
2547 case 5: return ANNCE_NUM_GAMESTART_5;
2548 case 4: return ANNCE_NUM_GAMESTART_4;
2549 case 3: return ANNCE_NUM_GAMESTART_3;
2550 case 2: return ANNCE_NUM_GAMESTART_2;
2551 case 1: return ANNCE_NUM_GAMESTART_1;
2559 case 10: return ANNCE_NUM_IDLE_10;
2560 case 9: return ANNCE_NUM_IDLE_9;
2561 case 8: return ANNCE_NUM_IDLE_8;
2562 case 7: return ANNCE_NUM_IDLE_7;
2563 case 6: return ANNCE_NUM_IDLE_6;
2564 case 5: return ANNCE_NUM_IDLE_5;
2565 case 4: return ANNCE_NUM_IDLE_4;
2566 case 3: return ANNCE_NUM_IDLE_3;
2567 case 2: return ANNCE_NUM_IDLE_2;
2568 case 1: return ANNCE_NUM_IDLE_1;
2576 case 10: return ANNCE_NUM_KILL_10;
2577 case 9: return ANNCE_NUM_KILL_9;
2578 case 8: return ANNCE_NUM_KILL_8;
2579 case 7: return ANNCE_NUM_KILL_7;
2580 case 6: return ANNCE_NUM_KILL_6;
2581 case 5: return ANNCE_NUM_KILL_5;
2582 case 4: return ANNCE_NUM_KILL_4;
2583 case 3: return ANNCE_NUM_KILL_3;
2584 case 2: return ANNCE_NUM_KILL_2;
2585 case 1: return ANNCE_NUM_KILL_1;
2593 case 10: return ANNCE_NUM_RESPAWN_10;
2594 case 9: return ANNCE_NUM_RESPAWN_9;
2595 case 8: return ANNCE_NUM_RESPAWN_8;
2596 case 7: return ANNCE_NUM_RESPAWN_7;
2597 case 6: return ANNCE_NUM_RESPAWN_6;
2598 case 5: return ANNCE_NUM_RESPAWN_5;
2599 case 4: return ANNCE_NUM_RESPAWN_4;
2600 case 3: return ANNCE_NUM_RESPAWN_3;
2601 case 2: return ANNCE_NUM_RESPAWN_2;
2602 case 1: return ANNCE_NUM_RESPAWN_1;
2606 case CNT_ROUNDSTART:
2610 case 10: return ANNCE_NUM_ROUNDSTART_10;
2611 case 9: return ANNCE_NUM_ROUNDSTART_9;
2612 case 8: return ANNCE_NUM_ROUNDSTART_8;
2613 case 7: return ANNCE_NUM_ROUNDSTART_7;
2614 case 6: return ANNCE_NUM_ROUNDSTART_6;
2615 case 5: return ANNCE_NUM_ROUNDSTART_5;
2616 case 4: return ANNCE_NUM_ROUNDSTART_4;
2617 case 3: return ANNCE_NUM_ROUNDSTART_3;
2618 case 2: return ANNCE_NUM_ROUNDSTART_2;
2619 case 1: return ANNCE_NUM_ROUNDSTART_1;
2627 case 10: return ANNCE_NUM_10;
2628 case 9: return ANNCE_NUM_9;
2629 case 8: return ANNCE_NUM_8;
2630 case 7: return ANNCE_NUM_7;
2631 case 6: return ANNCE_NUM_6;
2632 case 5: return ANNCE_NUM_5;
2633 case 4: return ANNCE_NUM_4;
2634 case 3: return ANNCE_NUM_3;
2635 case 2: return ANNCE_NUM_2;
2636 case 1: return ANNCE_NUM_1;
2641 return NOTIF_ABORT; // abort sending if none of these numbers were right
2646 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2648 switch(nativecontents)
2653 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2655 return DPCONTENTS_WATER;
2657 return DPCONTENTS_SLIME;
2659 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2661 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2666 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2668 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2669 return CONTENT_SOLID;
2670 if(supercontents & DPCONTENTS_SKY)
2672 if(supercontents & DPCONTENTS_LAVA)
2673 return CONTENT_LAVA;
2674 if(supercontents & DPCONTENTS_SLIME)
2675 return CONTENT_SLIME;
2676 if(supercontents & DPCONTENTS_WATER)
2677 return CONTENT_WATER;
2678 return CONTENT_EMPTY;
2682 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2685 (c - 2 * b + a) * (t * t) +
2690 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2693 (c - 2 * b + a) * (2 * t) +