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 void shuffle(float n, swapfunc_t swap, entity pass)
1506 for(i = 1; i < n; ++i)
1508 // swap i-th item at a random position from 0 to i
1509 // proof for even distribution:
1512 // item n+1 gets at any position with chance 1/(n+1)
1513 // all others will get their 1/n chance reduced by factor n/(n+1)
1514 // to be on place n+1, their chance will be 1/(n+1)
1515 // 1/n * n/(n+1) = 1/(n+1)
1517 j = floor(random() * (i + 1));
1523 string substring_range(string s, float b, float e)
1525 return substring(s, b, e - b);
1528 string swapwords(string str, float i, float j)
1531 string s1, s2, s3, s4, s5;
1532 float si, ei, sj, ej, s0, en;
1533 n = tokenizebyseparator(str, " "); // must match g_maplist processing in ShuffleMaplist and "shuffle"
1534 si = argv_start_index(i);
1535 sj = argv_start_index(j);
1536 ei = argv_end_index(i);
1537 ej = argv_end_index(j);
1538 s0 = argv_start_index(0);
1539 en = argv_end_index(n-1);
1540 s1 = substring_range(str, s0, si);
1541 s2 = substring_range(str, si, ei);
1542 s3 = substring_range(str, ei, sj);
1543 s4 = substring_range(str, sj, ej);
1544 s5 = substring_range(str, ej, en);
1545 return strcat(s1, s4, s3, s2, s5);
1548 string _shufflewords_str;
1549 void _shufflewords_swapfunc(float i, float j, entity pass)
1551 _shufflewords_str = swapwords(_shufflewords_str, i, j);
1553 string shufflewords(string str)
1556 _shufflewords_str = str;
1557 n = tokenizebyseparator(str, " ");
1558 shuffle(n, _shufflewords_swapfunc, world);
1559 str = _shufflewords_str;
1560 _shufflewords_str = string_null;
1564 vector solve_quadratic(float a, float b, float c) // ax^2 + bx + c = 0
1580 // actually, every number solves the equation!
1591 if(a > 0) // put the smaller solution first
1593 v.x = ((-b)-D) / (2*a);
1594 v.y = ((-b)+D) / (2*a);
1598 v.x = (-b+D) / (2*a);
1599 v.y = (-b-D) / (2*a);
1605 // complex solutions!
1618 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1622 // make origin and speed relative
1627 // now solve for ret, ret normalized:
1628 // eorg + t * evel == t * ret * spd
1629 // or, rather, solve for t:
1630 // |eorg + t * evel| == t * spd
1631 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1632 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1633 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1634 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1635 // q = (eorg * eorg) / (evel * evel - spd * spd)
1636 if(!solution.z) // no real solution
1639 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1640 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1641 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1642 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1643 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1644 // spd < |evel| * sin angle(evel, eorg)
1647 else if(solution.x > 0)
1649 // both solutions > 0: take the smaller one
1650 // happens if p < 0 and q > 0
1651 ret = normalize(eorg + solution.x * evel);
1653 else if(solution.y > 0)
1655 // one solution > 0: take the larger one
1656 // happens if q < 0 or q == 0 and p < 0
1657 ret = normalize(eorg + solution.y * evel);
1661 // no solution > 0: reject
1662 // happens if p > 0 and q >= 0
1663 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1664 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1669 // "Enemy is moving away from me at more than spd"
1673 // NOTE: we always got a solution if spd > |evel|
1675 if(newton_style == 2)
1676 ret = normalize(ret * spd + myvel);
1681 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1686 if(newton_style == 2)
1688 // true Newtonian projectiles with automatic aim adjustment
1690 // solve: |outspeed * mydir - myvel| = spd
1691 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1692 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1696 // myvel^2 - (mydir * myvel)^2 > spd^2
1697 // velocity without mydir component > spd
1698 // fire at smallest possible spd that works?
1699 // |(mydir * myvel) * myvel - myvel| = spd
1701 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1705 outspeed = solution.y; // the larger one
1708 //outspeed = 0; // slowest possible shot
1709 outspeed = solution.x; // the real part (that is, the average!)
1710 //dprint("impossible shot, adjusting\n");
1713 outspeed = bound(spd * mi, outspeed, spd * ma);
1714 return mydir * outspeed;
1718 return myvel + spd * mydir;
1721 float compressShotOrigin(vector v)
1725 y = rint(v.y * 4) + 128;
1726 z = rint(v.z * 4) + 128;
1727 if(x > 255 || x < 0)
1729 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1730 x = bound(0, x, 255);
1732 if(y > 255 || y < 0)
1734 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1735 y = bound(0, y, 255);
1737 if(z > 255 || z < 0)
1739 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1740 z = bound(0, z, 255);
1742 return x * 0x10000 + y * 0x100 + z;
1744 vector decompressShotOrigin(int f)
1747 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1748 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1749 v.z = ((f & 0xFF) - 128) / 4;
1753 void heapsort(float n, swapfunc_t swap, comparefunc_t cmp, entity pass)
1755 float start, end, root, child;
1758 start = floor((n - 2) / 2);
1761 // siftdown(start, count-1);
1763 while(root * 2 + 1 <= n-1)
1765 child = root * 2 + 1;
1767 if(cmp(child, child+1, pass) < 0)
1769 if(cmp(root, child, pass) < 0)
1771 swap(root, child, pass);
1787 // siftdown(0, end);
1789 while(root * 2 + 1 <= end)
1791 child = root * 2 + 1;
1792 if(child < end && cmp(child, child+1, pass) < 0)
1794 if(cmp(root, child, pass) < 0)
1796 swap(root, child, pass);
1806 void RandomSelection_Init()
1808 RandomSelection_totalweight = 0;
1809 RandomSelection_chosen_ent = world;
1810 RandomSelection_chosen_float = 0;
1811 RandomSelection_chosen_string = string_null;
1812 RandomSelection_best_priority = -1;
1814 void RandomSelection_Add(entity e, float f, string s, float weight, float priority)
1816 if(priority > RandomSelection_best_priority)
1818 RandomSelection_best_priority = priority;
1819 RandomSelection_chosen_ent = e;
1820 RandomSelection_chosen_float = f;
1821 RandomSelection_chosen_string = s;
1822 RandomSelection_totalweight = weight;
1824 else if(priority == RandomSelection_best_priority)
1826 RandomSelection_totalweight += weight;
1827 if(random() * RandomSelection_totalweight <= weight)
1829 RandomSelection_chosen_ent = e;
1830 RandomSelection_chosen_float = f;
1831 RandomSelection_chosen_string = s;
1837 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1839 // NOTE: we'll always choose the SMALLER value...
1840 float healthdamage, armordamage, armorideal;
1841 if (deathtype == DEATH_DROWN) // Why should armor help here...
1844 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1845 armordamage = a + (h - 1); // damage we can take if we could use more armor
1846 armorideal = healthdamage * armorblock;
1848 if(armordamage < healthdamage)
1861 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1864 if (deathtype == DEATH_DROWN) // Why should armor help here...
1866 v.y = bound(0, damage * armorblock, a); // save
1867 v.x = bound(0, damage - v.y, damage); // take
1873 string getcurrentmod()
1877 m = cvar_string("fs_gamedir");
1878 n = tokenize_console(m);
1889 int v = ReadShort() * 256; // note: this is signed
1890 v += ReadByte(); // note: this is unsigned
1893 vector ReadInt48_t()
1896 v.x = ReadInt24_t();
1897 v.y = ReadInt24_t();
1901 vector ReadInt72_t()
1904 v.x = ReadInt24_t();
1905 v.y = ReadInt24_t();
1906 v.z = ReadInt24_t();
1910 void WriteInt24_t(float dst, float val)
1913 WriteShort(dst, (v = floor(val / 256)));
1914 WriteByte(dst, val - v * 256); // 0..255
1916 void WriteInt48_t(float dst, vector val)
1918 WriteInt24_t(dst, val.x);
1919 WriteInt24_t(dst, val.y);
1921 void WriteInt72_t(float dst, vector val)
1923 WriteInt24_t(dst, val.x);
1924 WriteInt24_t(dst, val.y);
1925 WriteInt24_t(dst, val.z);
1930 float float2range11(float f)
1932 // continuous function mapping all reals into -1..1
1933 return f / (fabs(f) + 1);
1936 float float2range01(float f)
1938 // continuous function mapping all reals into 0..1
1939 return 0.5 + 0.5 * float2range11(f);
1942 // from the GNU Scientific Library
1943 float gsl_ran_gaussian_lastvalue;
1944 float gsl_ran_gaussian_lastvalue_set;
1945 float gsl_ran_gaussian(float sigma)
1948 if(gsl_ran_gaussian_lastvalue_set)
1950 gsl_ran_gaussian_lastvalue_set = 0;
1951 return sigma * gsl_ran_gaussian_lastvalue;
1955 a = random() * 2 * M_PI;
1956 b = sqrt(-2 * log(random()));
1957 gsl_ran_gaussian_lastvalue = cos(a) * b;
1958 gsl_ran_gaussian_lastvalue_set = 1;
1959 return sigma * sin(a) * b;
1963 string car(string s)
1966 o = strstrofs(s, " ", 0);
1969 return substring(s, 0, o);
1971 string cdr(string s)
1974 o = strstrofs(s, " ", 0);
1977 return substring(s, o + 1, strlen(s) - (o + 1));
1979 float matchacl(string acl, string str)
1986 t = car(acl); acl = cdr(acl);
1989 if(substring(t, 0, 1) == "-")
1992 t = substring(t, 1, strlen(t) - 1);
1994 else if(substring(t, 0, 1) == "+")
1995 t = substring(t, 1, strlen(t) - 1);
1997 if(substring(t, -1, 1) == "*")
1999 t = substring(t, 0, strlen(t) - 1);
2000 s = substring(str, 0, strlen(t));
2012 float startsWith(string haystack, string needle)
2014 return substring(haystack, 0, strlen(needle)) == needle;
2016 float startsWithNocase(string haystack, string needle)
2018 return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0;
2021 string get_model_datafilename(string m, float sk, string fil)
2026 m = "models/player/*_";
2028 m = strcat(m, ftos(sk));
2031 return strcat(m, ".", fil);
2034 float get_model_parameters(string m, float sk)
2036 get_model_parameters_modelname = string_null;
2037 get_model_parameters_modelskin = -1;
2038 get_model_parameters_name = string_null;
2039 get_model_parameters_species = -1;
2040 get_model_parameters_sex = string_null;
2041 get_model_parameters_weight = -1;
2042 get_model_parameters_age = -1;
2043 get_model_parameters_desc = string_null;
2044 get_model_parameters_bone_upperbody = string_null;
2045 get_model_parameters_bone_weapon = string_null;
2046 for(int i = 0; i < MAX_AIM_BONES; ++i)
2048 get_model_parameters_bone_aim[i] = string_null;
2049 get_model_parameters_bone_aimweight[i] = 0;
2051 get_model_parameters_fixbone = 0;
2054 MUTATOR_CALLHOOK(ClearModelParams);
2060 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
2061 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
2065 if(substring(m, -4, -1) != ".txt")
2067 if(substring(m, -6, 1) != "_")
2069 sk = stof(substring(m, -5, 1));
2070 m = substring(m, 0, -7);
2073 string fn = get_model_datafilename(m, sk, "txt");
2074 int fh = fopen(fn, FILE_READ);
2078 fn = get_model_datafilename(m, sk, "txt");
2079 fh = fopen(fn, FILE_READ);
2084 get_model_parameters_modelname = m;
2085 get_model_parameters_modelskin = sk;
2087 while((s = fgets(fh)))
2090 break; // next lines will be description
2094 get_model_parameters_name = s;
2098 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
2099 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
2100 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
2101 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
2102 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
2103 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
2104 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
2107 get_model_parameters_sex = s;
2109 get_model_parameters_weight = stof(s);
2111 get_model_parameters_age = stof(s);
2112 if(c == "description")
2113 get_model_parameters_description = s;
2114 if(c == "bone_upperbody")
2115 get_model_parameters_bone_upperbody = s;
2116 if(c == "bone_weapon")
2117 get_model_parameters_bone_weapon = s;
2119 MUTATOR_CALLHOOK(GetModelParams, c, s);
2121 for(int i = 0; i < MAX_AIM_BONES; ++i)
2122 if(c == strcat("bone_aim", ftos(i)))
2124 get_model_parameters_bone_aimweight[i] = stof(car(s));
2125 get_model_parameters_bone_aim[i] = cdr(s);
2128 get_model_parameters_fixbone = stof(s);
2131 while((s = fgets(fh)))
2133 if(get_model_parameters_desc)
2134 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
2136 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
2144 vector vec2(vector v)
2151 vector NearestPointOnBox(entity box, vector org)
2153 vector m1, m2, nearest;
2155 m1 = box.mins + box.origin;
2156 m2 = box.maxs + box.origin;
2158 nearest.x = bound(m1_x, org.x, m2_x);
2159 nearest.y = bound(m1_y, org.y, m2_y);
2160 nearest.z = bound(m1_z, org.z, m2_z);
2166 float vercmp_recursive(string v1, string v2)
2172 dot1 = strstrofs(v1, ".", 0);
2173 dot2 = strstrofs(v2, ".", 0);
2177 s1 = substring(v1, 0, dot1);
2181 s2 = substring(v2, 0, dot2);
2183 r = stof(s1) - stof(s2);
2187 r = strcasecmp(s1, s2);
2200 return vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999));
2203 float vercmp(string v1, string v2)
2205 if(strcasecmp(v1, v2) == 0) // early out check
2214 return vercmp_recursive(v1, v2);
2217 float u8_strsize(string s)
2237 // x-encoding (encoding as zero length invisible string)
2238 const string XENCODE_2 = "xX";
2239 const string XENCODE_22 = "0123456789abcdefABCDEF";
2240 string xencode(int f)
2243 d = f % 22; f = floor(f / 22);
2244 c = f % 22; f = floor(f / 22);
2245 b = f % 22; f = floor(f / 22);
2246 a = f % 2; // f = floor(f / 2);
2249 substring(XENCODE_2, a, 1),
2250 substring(XENCODE_22, b, 1),
2251 substring(XENCODE_22, c, 1),
2252 substring(XENCODE_22, d, 1)
2255 float xdecode(string s)
2258 if(substring(s, 0, 1) != "^")
2262 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
2263 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
2264 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
2265 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
2266 if(a < 0 || b < 0 || c < 0 || d < 0)
2268 return ((a * 22 + b) * 22 + c) * 22 + d;
2271 int lowestbit(int f)
2282 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
2284 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
2287 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
2290 // escape the string to make it safe for consoles
2291 string MakeConsoleSafe(string input)
2293 input = strreplace("\n", "", input);
2294 input = strreplace("\\", "\\\\", input);
2295 input = strreplace("$", "$$", input);
2296 input = strreplace("\"", "\\\"", input);
2301 entity ReadCSQCEntity()
2303 int f = ReadShort();
2306 return findfloat(world, entnum, f);
2310 float shutdown_running;
2315 void CSQC_Shutdown()
2321 if(shutdown_running)
2323 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
2327 shutdown_running = 1;
2330 cvar_settemp_restore(); // this must be done LAST, but in any case
2333 const float APPROXPASTTIME_ACCURACY_REQUIREMENT = 0.05;
2334 #define APPROXPASTTIME_MAX (16384 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2335 #define APPROXPASTTIME_RANGE (64 * APPROXPASTTIME_ACCURACY_REQUIREMENT)
2336 // this will use the value:
2338 // accuracy near zero is APPROXPASTTIME_MAX/(256*255)
2339 // accuracy at x is 1/derivative, i.e.
2340 // APPROXPASTTIME_MAX * (1 + 256 * (dt / APPROXPASTTIME_MAX))^2 / 65536
2342 void WriteApproxPastTime(float dst, float t)
2344 float dt = time - t;
2346 // warning: this is approximate; do not resend when you don't have to!
2347 // be careful with sendflags here!
2348 // we want: 0 -> 0.05, 1 -> 0.1, ..., 255 -> 12.75
2351 dt = 256 * (dt / ((APPROXPASTTIME_MAX / 256) + dt));
2354 dt = rint(bound(0, dt, 255));
2360 float ReadApproxPastTime()
2362 float dt = ReadByte();
2364 // map from range...PPROXPASTTIME_MAX / 256
2365 dt = (APPROXPASTTIME_MAX / 256) * (dt / (256 - dt));
2367 return servertime - dt;
2372 .float skeleton_bones_index;
2373 void Skeleton_SetBones(entity e)
2375 // set skeleton_bones to the total number of bones on the model
2376 if(e.skeleton_bones_index == e.modelindex)
2377 return; // same model, nothing to update
2380 skelindex = skel_create(e.modelindex);
2381 e.skeleton_bones = skel_get_numbones(skelindex);
2382 skel_delete(skelindex);
2383 e.skeleton_bones_index = e.modelindex;
2387 string to_execute_next_frame;
2388 void execute_next_frame()
2390 if(to_execute_next_frame)
2392 localcmd("\n", to_execute_next_frame, "\n");
2393 strunzone(to_execute_next_frame);
2394 to_execute_next_frame = string_null;
2397 void queue_to_execute_next_frame(string s)
2399 if(to_execute_next_frame)
2401 s = strcat(s, "\n", to_execute_next_frame);
2402 strunzone(to_execute_next_frame);
2404 to_execute_next_frame = strzone(s);
2407 float cubic_speedfunc(float startspeedfactor, float endspeedfactor, float x)
2410 ((( startspeedfactor + endspeedfactor - 2
2411 ) * x - 2 * startspeedfactor - endspeedfactor + 3
2412 ) * x + startspeedfactor
2416 float cubic_speedfunc_is_sane(float startspeedfactor, float endspeedfactor)
2418 if(startspeedfactor < 0 || endspeedfactor < 0)
2422 // if this is the case, the possible zeros of the first derivative are outside
2424 We can calculate this condition as condition
2429 // better, see below:
2430 if(startspeedfactor <= 3 && endspeedfactor <= 3)
2433 // if this is the case, the first derivative has no zeros at all
2434 float se = startspeedfactor + endspeedfactor;
2435 float s_e = startspeedfactor - endspeedfactor;
2436 if(3 * (se - 4) * (se - 4) + s_e * s_e <= 12) // an ellipse
2439 // Now let s <= 3, s <= 3, s+e >= 3 (triangle) then we get se <= 6 (top right corner).
2440 // we also get s_e <= 6 - se
2441 // 3 * (se - 4)^2 + (6 - se)^2
2442 // is quadratic, has value 12 at 3 and 6, and value < 12 in between.
2443 // Therefore, above "better" check works!
2447 // known good cases:
2455 // (3.5, [0.2..2.3])
2460 inflection point is always at (2s + e - 3) / (3s + 3e - 6).
2462 s + e - 2 == 0: no inflection
2465 0 < inflection < 1 if:
2466 0 < 2s + e - 3 < 3s + 3e - 6
2467 2s + e > 3 and 2e + s > 3
2470 0 < inflection < 1 if:
2471 0 > 2s + e - 3 > 3s + 3e - 6
2472 2s + e < 3 and 2e + s < 3
2474 Therefore: there is an inflection point iff:
2475 e outside (3 - s)/2 .. 3 - s*2
2477 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)
2481 .float FindConnectedComponent_processing;
2482 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
2484 entity queue_start, queue_end;
2486 // we build a queue of to-be-processed entities.
2487 // queue_start is the next entity to be checked for neighbors
2488 // queue_end is the last entity added
2490 if(e.FindConnectedComponent_processing)
2491 error("recursion or broken cleanup");
2493 // start with a 1-element queue
2494 queue_start = queue_end = e;
2495 queue_end.(fld) = world;
2496 queue_end.FindConnectedComponent_processing = 1;
2498 // for each queued item:
2499 for (; queue_start; queue_start = queue_start.(fld))
2501 // find all neighbors of queue_start
2503 for(t = world; (t = nxt(t, queue_start, pass)); )
2505 if(t.FindConnectedComponent_processing)
2507 if(iscon(t, queue_start, pass))
2509 // it is connected? ADD IT. It will look for neighbors soon too.
2510 queue_end.(fld) = t;
2512 queue_end.(fld) = world;
2513 queue_end.FindConnectedComponent_processing = 1;
2519 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
2520 queue_start.FindConnectedComponent_processing = 0;
2524 vector combine_to_vector(float x, float y, float z)
2526 vector result; result_x = x; result_y = y; result_z = z;
2530 vector get_corner_position(entity box, float corner)
2534 case 1: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmin.z);
2535 case 2: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmin.z);
2536 case 3: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmin.z);
2537 case 4: return combine_to_vector(box.absmin.x, box.absmin.y, box.absmax.z);
2538 case 5: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmin.z);
2539 case 6: return combine_to_vector(box.absmin.x, box.absmax.y, box.absmax.z);
2540 case 7: return combine_to_vector(box.absmax.x, box.absmin.y, box.absmax.z);
2541 case 8: return combine_to_vector(box.absmax.x, box.absmax.y, box.absmax.z);
2542 default: return '0 0 0';
2547 // color code replace, place inside of sprintf and parse the string
2548 string CCR(string input)
2550 // See the autocvar declarations in util.qh for default values
2552 // foreground/normal colors
2553 input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input);
2554 input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input);
2555 input = strreplace("^F3", strcat("^", autocvar_hud_colorset_foreground_3), input);
2556 input = strreplace("^F4", strcat("^", autocvar_hud_colorset_foreground_4), input);
2559 input = strreplace("^K1", strcat("^", autocvar_hud_colorset_kill_1), input);
2560 input = strreplace("^K2", strcat("^", autocvar_hud_colorset_kill_2), input);
2561 input = strreplace("^K3", strcat("^", autocvar_hud_colorset_kill_3), input);
2563 // background colors
2564 input = strreplace("^BG", strcat("^", autocvar_hud_colorset_background), input);
2565 input = strreplace("^N", "^7", input); // "none"-- reset to white...
2569 vector vec3(float x, float y, float z)
2579 vector animfixfps(entity e, vector a, vector b)
2581 // multi-frame anim: keep as-is
2585 dur = frameduration(e.modelindex, a.x);
2589 dur = frameduration(e.modelindex, a.x);
2599 void dedicated_print(string input) // print(), but only print if the server is not local
2601 if(server_is_dedicated) { LOG_INFO(input); }
2606 float Announcer_PickNumber(float type, float num)
2614 case 10: return ANNCE_NUM_GAMESTART_10;
2615 case 9: return ANNCE_NUM_GAMESTART_9;
2616 case 8: return ANNCE_NUM_GAMESTART_8;
2617 case 7: return ANNCE_NUM_GAMESTART_7;
2618 case 6: return ANNCE_NUM_GAMESTART_6;
2619 case 5: return ANNCE_NUM_GAMESTART_5;
2620 case 4: return ANNCE_NUM_GAMESTART_4;
2621 case 3: return ANNCE_NUM_GAMESTART_3;
2622 case 2: return ANNCE_NUM_GAMESTART_2;
2623 case 1: return ANNCE_NUM_GAMESTART_1;
2631 case 10: return ANNCE_NUM_IDLE_10;
2632 case 9: return ANNCE_NUM_IDLE_9;
2633 case 8: return ANNCE_NUM_IDLE_8;
2634 case 7: return ANNCE_NUM_IDLE_7;
2635 case 6: return ANNCE_NUM_IDLE_6;
2636 case 5: return ANNCE_NUM_IDLE_5;
2637 case 4: return ANNCE_NUM_IDLE_4;
2638 case 3: return ANNCE_NUM_IDLE_3;
2639 case 2: return ANNCE_NUM_IDLE_2;
2640 case 1: return ANNCE_NUM_IDLE_1;
2648 case 10: return ANNCE_NUM_KILL_10;
2649 case 9: return ANNCE_NUM_KILL_9;
2650 case 8: return ANNCE_NUM_KILL_8;
2651 case 7: return ANNCE_NUM_KILL_7;
2652 case 6: return ANNCE_NUM_KILL_6;
2653 case 5: return ANNCE_NUM_KILL_5;
2654 case 4: return ANNCE_NUM_KILL_4;
2655 case 3: return ANNCE_NUM_KILL_3;
2656 case 2: return ANNCE_NUM_KILL_2;
2657 case 1: return ANNCE_NUM_KILL_1;
2665 case 10: return ANNCE_NUM_RESPAWN_10;
2666 case 9: return ANNCE_NUM_RESPAWN_9;
2667 case 8: return ANNCE_NUM_RESPAWN_8;
2668 case 7: return ANNCE_NUM_RESPAWN_7;
2669 case 6: return ANNCE_NUM_RESPAWN_6;
2670 case 5: return ANNCE_NUM_RESPAWN_5;
2671 case 4: return ANNCE_NUM_RESPAWN_4;
2672 case 3: return ANNCE_NUM_RESPAWN_3;
2673 case 2: return ANNCE_NUM_RESPAWN_2;
2674 case 1: return ANNCE_NUM_RESPAWN_1;
2678 case CNT_ROUNDSTART:
2682 case 10: return ANNCE_NUM_ROUNDSTART_10;
2683 case 9: return ANNCE_NUM_ROUNDSTART_9;
2684 case 8: return ANNCE_NUM_ROUNDSTART_8;
2685 case 7: return ANNCE_NUM_ROUNDSTART_7;
2686 case 6: return ANNCE_NUM_ROUNDSTART_6;
2687 case 5: return ANNCE_NUM_ROUNDSTART_5;
2688 case 4: return ANNCE_NUM_ROUNDSTART_4;
2689 case 3: return ANNCE_NUM_ROUNDSTART_3;
2690 case 2: return ANNCE_NUM_ROUNDSTART_2;
2691 case 1: return ANNCE_NUM_ROUNDSTART_1;
2699 case 10: return ANNCE_NUM_10;
2700 case 9: return ANNCE_NUM_9;
2701 case 8: return ANNCE_NUM_8;
2702 case 7: return ANNCE_NUM_7;
2703 case 6: return ANNCE_NUM_6;
2704 case 5: return ANNCE_NUM_5;
2705 case 4: return ANNCE_NUM_4;
2706 case 3: return ANNCE_NUM_3;
2707 case 2: return ANNCE_NUM_2;
2708 case 1: return ANNCE_NUM_1;
2713 return NOTIF_ABORT; // abort sending if none of these numbers were right
2718 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
2720 switch(nativecontents)
2725 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
2727 return DPCONTENTS_WATER;
2729 return DPCONTENTS_SLIME;
2731 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
2733 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
2738 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
2740 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
2741 return CONTENT_SOLID;
2742 if(supercontents & DPCONTENTS_SKY)
2744 if(supercontents & DPCONTENTS_LAVA)
2745 return CONTENT_LAVA;
2746 if(supercontents & DPCONTENTS_SLIME)
2747 return CONTENT_SLIME;
2748 if(supercontents & DPCONTENTS_WATER)
2749 return CONTENT_WATER;
2750 return CONTENT_EMPTY;
2754 vector bezier_quadratic_getpoint(vector a, vector b, vector c, float t)
2757 (c - 2 * b + a) * (t * t) +
2762 vector bezier_quadratic_getderivative(vector a, vector b, vector c, float t)
2765 (c - 2 * b + a) * (2 * t) +