4 #include "../client/defs.qh"
5 #include "constants.qh"
6 #include "../client/mutators/events.qh"
8 #include "notifications.qh"
9 #include "deathtypes/all.qh"
12 #include "constants.qh"
13 #include "../server/autocvars.qh"
14 #include "../server/defs.qh"
15 #include "../server/mutators/events.qh"
16 #include "notifications.qh"
17 #include "deathtypes/all.qh"
21 string wordwrap_buffer;
23 void wordwrap_buffer_put(string s)
25 wordwrap_buffer = strcat(wordwrap_buffer, s);
28 string wordwrap(string s, float l)
32 wordwrap_cb(s, l, wordwrap_buffer_put);
40 void wordwrap_buffer_sprint(string s)
42 wordwrap_buffer = strcat(wordwrap_buffer, s);
45 sprint(self, wordwrap_buffer);
50 void wordwrap_sprint(string s, float l)
53 wordwrap_cb(s, l, wordwrap_buffer_sprint);
54 if(wordwrap_buffer != "")
55 sprint(self, strcat(wordwrap_buffer, "\n"));
63 string draw_UseSkinFor(string pic)
65 if(substring(pic, 0, 1) == "/")
66 return substring(pic, 1, strlen(pic)-1);
68 return strcat(draw_currentSkin, "/", pic);
72 void wordwrap_cb(string s, float l, void(string) callback)
75 float lleft, i, j, wlen;
79 for (i = 0;i < strlen(s);++i)
81 if (substring(s, i, 2) == "\\n")
87 else if (substring(s, i, 1) == "\n")
92 else if (substring(s, i, 1) == " ")
102 for (j = i+1;j < strlen(s);++j)
103 // ^^ this skips over the first character of a word, which
104 // is ALWAYS part of the word
105 // this is safe since if i+1 == strlen(s), i will become
106 // strlen(s)-1 at the end of this block and the function
107 // will terminate. A space can't be the first character we
108 // read here, and neither can a \n be the start, since these
109 // two cases have been handled above.
111 c = substring(s, j, 1);
118 // we need to keep this tempstring alive even if substring is
119 // called repeatedly, so call strcat even though we're not
129 callback(substring(s, i, wlen));
130 lleft = lleft - wlen;
137 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
166 string ScoreString(int pFlags, float pValue)
171 pValue = floor(pValue + 0.5); // round
173 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
175 else if(pFlags & SFL_RANK)
177 valstr = ftos(pValue);
179 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
180 valstr = strcat(valstr, "th");
181 else if(substring(valstr, l - 1, 1) == "1")
182 valstr = strcat(valstr, "st");
183 else if(substring(valstr, l - 1, 1) == "2")
184 valstr = strcat(valstr, "nd");
185 else if(substring(valstr, l - 1, 1) == "3")
186 valstr = strcat(valstr, "rd");
188 valstr = strcat(valstr, "th");
190 else if(pFlags & SFL_TIME)
191 valstr = TIME_ENCODED_TOSTRING(pValue);
193 valstr = ftos(pValue);
198 // compressed vector format:
199 // like MD3, just even shorter
200 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
201 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
202 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
203 // length = 2^(length_encoded/8) / 8
204 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
205 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
206 // the special value 0 indicates the zero vector
208 float lengthLogTable[128];
210 float invertLengthLog(float x)
214 if(x >= lengthLogTable[127])
216 if(x <= lengthLogTable[0])
224 m = floor((l + r) / 2);
225 if(lengthLogTable[m] < x)
231 // now: r is >=, l is <
232 float lerr = (x - lengthLogTable[l]);
233 float rerr = (lengthLogTable[r] - x);
239 vector decompressShortVector(int data)
244 float p = (data & 0xF000) / 0x1000;
245 float y = (data & 0x0F80) / 0x80;
246 int len = (data & 0x007F);
248 //print("\ndecompress: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
261 y = .19634954084936207740 * y;
262 p = .19634954084936207740 * p - 1.57079632679489661922;
263 out.x = cos(y) * cos(p);
264 out.y = sin(y) * cos(p);
268 //print("decompressed: ", vtos(out), "\n");
270 return out * lengthLogTable[len];
273 float compressShortVector(vector vec)
279 //print("compress: ", vtos(vec), "\n");
280 ang = vectoangles(vec);
284 if(ang.x < -90 && ang.x > +90)
285 error("BOGUS vectoangles");
286 //print("angles: ", vtos(ang), "\n");
288 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
297 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
298 len = invertLengthLog(vlen(vec));
300 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
302 return (p * 0x1000) + (y * 0x80) + len;
305 void compressShortVector_init()
308 float f = pow(2, 1/8);
310 for(i = 0; i < 128; ++i)
312 lengthLogTable[i] = l;
316 if(cvar("developer"))
318 LOG_INFO("Verifying vector compression table...\n");
319 for(i = 0x0F00; i < 0xFFFF; ++i)
320 if(i != compressShortVector(decompressShortVector(i)))
322 LOG_INFO("BROKEN vector compression: ", ftos(i));
323 LOG_INFO(" -> ", vtos(decompressShortVector(i)));
324 LOG_INFO(" -> ", ftos(compressShortVector(decompressShortVector(i))));
333 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
335 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
336 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
337 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
338 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
339 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
340 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
341 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
342 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
343 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
344 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
345 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
346 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
351 string fixPriorityList(string order, float from, float to, float subtract, float complete)
356 n = tokenize_console(order);
358 for(i = 0; i < n; ++i)
363 if(w >= from && w <= to)
364 neworder = strcat(neworder, ftos(w), " ");
368 if(w >= from && w <= to)
369 neworder = strcat(neworder, ftos(w), " ");
376 n = tokenize_console(neworder);
377 for(w = to; w >= from; --w)
379 for(i = 0; i < n; ++i)
380 if(stof(argv(i)) == w)
382 if(i == n) // not found
383 neworder = strcat(neworder, ftos(w), " ");
387 return substring(neworder, 0, strlen(neworder) - 1);
390 string mapPriorityList(string order, string(string) mapfunc)
395 n = tokenize_console(order);
397 for(i = 0; i < n; ++i)
398 neworder = strcat(neworder, mapfunc(argv(i)), " ");
400 return substring(neworder, 0, strlen(neworder) - 1);
403 string swapInPriorityList(string order, float i, float j)
408 n = tokenize_console(order);
410 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
413 for(w = 0; w < n; ++w)
416 s = strcat(s, argv(j), " ");
418 s = strcat(s, argv(i), " ");
420 s = strcat(s, argv(w), " ");
422 return substring(s, 0, strlen(s) - 1);
429 void get_mi_min_max(float mode)
434 strunzone(mi_shortname);
435 mi_shortname = mapname;
436 if(!strcasecmp(substring(mi_shortname, 0, 5), "maps/"))
437 mi_shortname = substring(mi_shortname, 5, strlen(mi_shortname) - 5);
438 if(!strcasecmp(substring(mi_shortname, strlen(mi_shortname) - 4, 4), ".bsp"))
439 mi_shortname = substring(mi_shortname, 0, strlen(mi_shortname) - 4);
440 mi_shortname = strzone(mi_shortname);
452 MapInfo_Get_ByName(mi_shortname, 0, 0);
453 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
455 mi_min = MapInfo_Map_mins;
456 mi_max = MapInfo_Map_maxs;
464 tracebox('1 0 0' * mi.x,
465 '0 1 0' * mi.y + '0 0 1' * mi.z,
466 '0 1 0' * ma.y + '0 0 1' * ma.z,
470 if(!trace_startsolid)
471 mi_min.x = trace_endpos.x;
473 tracebox('0 1 0' * mi.y,
474 '1 0 0' * mi.x + '0 0 1' * mi.z,
475 '1 0 0' * ma.x + '0 0 1' * ma.z,
479 if(!trace_startsolid)
480 mi_min.y = trace_endpos.y;
482 tracebox('0 0 1' * mi.z,
483 '1 0 0' * mi.x + '0 1 0' * mi.y,
484 '1 0 0' * ma.x + '0 1 0' * ma.y,
488 if(!trace_startsolid)
489 mi_min.z = trace_endpos.z;
491 tracebox('1 0 0' * ma.x,
492 '0 1 0' * mi.y + '0 0 1' * mi.z,
493 '0 1 0' * ma.y + '0 0 1' * ma.z,
497 if(!trace_startsolid)
498 mi_max.x = trace_endpos.x;
500 tracebox('0 1 0' * ma.y,
501 '1 0 0' * mi.x + '0 0 1' * mi.z,
502 '1 0 0' * ma.x + '0 0 1' * ma.z,
506 if(!trace_startsolid)
507 mi_max.y = trace_endpos.y;
509 tracebox('0 0 1' * ma.z,
510 '1 0 0' * mi.x + '0 1 0' * mi.y,
511 '1 0 0' * ma.x + '0 1 0' * ma.y,
515 if(!trace_startsolid)
516 mi_max.z = trace_endpos.z;
521 void get_mi_min_max_texcoords(float mode)
525 get_mi_min_max(mode);
530 // extend mi_picmax to get a square aspect ratio
531 // center the map in that area
532 extend = mi_picmax - mi_picmin;
533 if(extend.y > extend.x)
535 mi_picmin.x -= (extend.y - extend.x) * 0.5;
536 mi_picmax.x += (extend.y - extend.x) * 0.5;
540 mi_picmin.y -= (extend.x - extend.y) * 0.5;
541 mi_picmax.y += (extend.x - extend.y) * 0.5;
544 // add another some percent
545 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
549 // calculate the texcoords
550 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
551 // first the two corners of the origin
552 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
553 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
554 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
555 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
556 // then the other corners
557 mi_pictexcoord1_x = mi_pictexcoord0_x;
558 mi_pictexcoord1_y = mi_pictexcoord2_y;
559 mi_pictexcoord3_x = mi_pictexcoord2_x;
560 mi_pictexcoord3_y = mi_pictexcoord0_y;
564 float cvar_settemp(string tmp_cvar, string tmp_value)
566 float created_saved_value;
569 created_saved_value = 0;
571 if (!(tmp_cvar || tmp_value))
573 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !\n");
577 if(!cvar_type(tmp_cvar))
579 LOG_INFOF("Error: cvar %s doesn't exist!\n", tmp_cvar);
583 for(e = world; (e = find(e, classname, "saved_cvar_value")); )
584 if(e.netname == tmp_cvar)
585 created_saved_value = -1; // skip creation
587 if(created_saved_value != -1)
589 // creating a new entity to keep track of this cvar
590 e = new(saved_cvar_value);
592 e.netname = strzone(tmp_cvar);
593 e.message = strzone(cvar_string(tmp_cvar));
594 created_saved_value = 1;
597 // update the cvar to the value given
598 cvar_set(tmp_cvar, tmp_value);
600 return created_saved_value;
603 float cvar_settemp_restore()
607 while((e = find(e, classname, "saved_cvar_value")))
609 if(cvar_type(e.netname))
611 cvar_set(e.netname, e.message);
612 strunzone(e.netname);
613 strunzone(e.message);
618 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.\n", e.netname);
624 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
627 // The following function is SLOW.
628 // For your safety and for the protection of those around you...
629 // DO NOT CALL THIS AT HOME.
631 if(w(theText, theSize) <= maxWidth)
632 return strlen(theText); // yeah!
634 // binary search for right place to cut string
636 float left, right, middle; // this always works
638 right = strlen(theText); // this always fails
641 middle = floor((left + right) / 2);
642 if(w(substring(theText, 0, middle), theSize) <= maxWidth)
647 while(left < right - 1);
649 if(w("^7", theSize) == 0) // detect color codes support in the width function
651 // NOTE: when color codes are involved, this binary search is,
652 // mathematically, BROKEN. However, it is obviously guaranteed to
653 // terminate, as the range still halves each time - but nevertheless, it is
654 // guaranteed that it finds ONE valid cutoff place (where "left" is in
655 // range, and "right" is outside).
657 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
658 // and decrease left on the basis of the chars detected of the truncated tag
659 // Even if the ^xrgb tag is not complete/correct, left is decreased
660 // (sometimes too much but with a correct result)
661 // it fixes also ^[0-9]
662 while(left >= 1 && substring(theText, left-1, 1) == "^")
665 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
667 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
669 ch = str2chr(theText, left-1);
670 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
673 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
675 ch = str2chr(theText, left-2);
676 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
678 ch = str2chr(theText, left-1);
679 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
688 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
691 // The following function is SLOW.
692 // For your safety and for the protection of those around you...
693 // DO NOT CALL THIS AT HOME.
695 if(w(theText) <= maxWidth)
696 return strlen(theText); // yeah!
698 // binary search for right place to cut string
700 float left, right, middle; // this always works
702 right = strlen(theText); // this always fails
705 middle = floor((left + right) / 2);
706 if(w(substring(theText, 0, middle)) <= maxWidth)
711 while(left < right - 1);
713 if(w("^7") == 0) // detect color codes support in the width function
715 // NOTE: when color codes are involved, this binary search is,
716 // mathematically, BROKEN. However, it is obviously guaranteed to
717 // terminate, as the range still halves each time - but nevertheless, it is
718 // guaranteed that it finds ONE valid cutoff place (where "left" is in
719 // range, and "right" is outside).
721 // terencehill: the following code detects truncated ^xrgb tags (e.g. ^x or ^x4)
722 // and decrease left on the basis of the chars detected of the truncated tag
723 // Even if the ^xrgb tag is not complete/correct, left is decreased
724 // (sometimes too much but with a correct result)
725 // it fixes also ^[0-9]
726 while(left >= 1 && substring(theText, left-1, 1) == "^")
729 if (left >= 2 && substring(theText, left-2, 2) == "^x") // ^x/
731 else if (left >= 3 && substring(theText, left-3, 2) == "^x")
733 ch = str2chr(theText, left-1);
734 if( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xr/
737 else if (left >= 4 && substring(theText, left-4, 2) == "^x")
739 ch = str2chr(theText, left-2);
740 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') )
742 ch = str2chr(theText, left-1);
743 if ( (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') ) // ^xrg/
752 string find_last_color_code(string s)
754 int start = strstrofs(s, "^", 0);
755 if (start == -1) // no caret found
757 int len = strlen(s)-1;
759 for(i = len; i >= start; --i)
761 if(substring(s, i, 1) != "^")
765 while (i-carets >= start && substring(s, i-carets, 1) == "^")
768 // check if carets aren't all escaped
772 if(strstrofs("0123456789", substring(s, i+1, 1), 0) >= 0)
773 return substring(s, i, 2);
776 if(substring(s, i+1, 1) == "x")
777 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+2, 1), 0) >= 0)
778 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+3, 1), 0) >= 0)
779 if(strstrofs("0123456789abcdefABCDEF", substring(s, i+4, 1), 0) >= 0)
780 return substring(s, i, 5);
782 i -= carets; // this also skips one char before the carets
788 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
794 s = getWrappedLine_remaining;
798 getWrappedLine_remaining = string_null;
799 return s; // the line has no size ANYWAY, nothing would be displayed.
802 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
803 if(cantake > 0 && cantake < strlen(s))
806 while(take > 0 && substring(s, take, 1) != " ")
810 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
811 if(getWrappedLine_remaining == "")
812 getWrappedLine_remaining = string_null;
813 else if (tw("^7", theFontSize) == 0)
814 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
815 return substring(s, 0, cantake);
819 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
820 if(getWrappedLine_remaining == "")
821 getWrappedLine_remaining = string_null;
822 else if (tw("^7", theFontSize) == 0)
823 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
824 return substring(s, 0, take);
829 getWrappedLine_remaining = string_null;
834 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
840 s = getWrappedLine_remaining;
844 getWrappedLine_remaining = string_null;
845 return s; // the line has no size ANYWAY, nothing would be displayed.
848 cantake = textLengthUpToLength(s, w, tw);
849 if(cantake > 0 && cantake < strlen(s))
852 while(take > 0 && substring(s, take, 1) != " ")
856 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
857 if(getWrappedLine_remaining == "")
858 getWrappedLine_remaining = string_null;
859 else if (tw("^7") == 0)
860 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
861 return substring(s, 0, cantake);
865 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
866 if(getWrappedLine_remaining == "")
867 getWrappedLine_remaining = string_null;
868 else if (tw("^7") == 0)
869 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
870 return substring(s, 0, take);
875 getWrappedLine_remaining = string_null;
880 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
882 if(tw(theText, theFontSize) <= maxWidth)
885 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
888 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
890 if(tw(theText) <= maxWidth)
893 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
896 float isGametypeInFilter(float gt, float tp, float ts, string pattern)
898 string subpattern, subpattern2, subpattern3, subpattern4;
899 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
901 subpattern2 = ",teams,";
903 subpattern2 = ",noteams,";
905 subpattern3 = ",teamspawns,";
907 subpattern3 = ",noteamspawns,";
908 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
909 subpattern4 = ",race,";
911 subpattern4 = string_null;
913 if(substring(pattern, 0, 1) == "-")
915 pattern = substring(pattern, 1, strlen(pattern) - 1);
916 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
918 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
920 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
922 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
927 if(substring(pattern, 0, 1) == "+")
928 pattern = substring(pattern, 1, strlen(pattern) - 1);
929 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
930 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
931 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
935 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
942 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
946 // make origin and speed relative
951 // now solve for ret, ret normalized:
952 // eorg + t * evel == t * ret * spd
953 // or, rather, solve for t:
954 // |eorg + t * evel| == t * spd
955 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
956 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
957 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
958 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
959 // q = (eorg * eorg) / (evel * evel - spd * spd)
960 if(!solution.z) // no real solution
963 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
964 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
965 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
966 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
967 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
968 // spd < |evel| * sin angle(evel, eorg)
971 else if(solution.x > 0)
973 // both solutions > 0: take the smaller one
974 // happens if p < 0 and q > 0
975 ret = normalize(eorg + solution.x * evel);
977 else if(solution.y > 0)
979 // one solution > 0: take the larger one
980 // happens if q < 0 or q == 0 and p < 0
981 ret = normalize(eorg + solution.y * evel);
985 // no solution > 0: reject
986 // happens if p > 0 and q >= 0
987 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
988 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
993 // "Enemy is moving away from me at more than spd"
997 // NOTE: we always got a solution if spd > |evel|
999 if(newton_style == 2)
1000 ret = normalize(ret * spd + myvel);
1005 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1010 if(newton_style == 2)
1012 // true Newtonian projectiles with automatic aim adjustment
1014 // solve: |outspeed * mydir - myvel| = spd
1015 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1016 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1020 // myvel^2 - (mydir * myvel)^2 > spd^2
1021 // velocity without mydir component > spd
1022 // fire at smallest possible spd that works?
1023 // |(mydir * myvel) * myvel - myvel| = spd
1025 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1029 outspeed = solution.y; // the larger one
1032 //outspeed = 0; // slowest possible shot
1033 outspeed = solution.x; // the real part (that is, the average!)
1034 //dprint("impossible shot, adjusting\n");
1037 outspeed = bound(spd * mi, outspeed, spd * ma);
1038 return mydir * outspeed;
1042 return myvel + spd * mydir;
1045 float compressShotOrigin(vector v)
1049 y = rint(v.y * 4) + 128;
1050 z = rint(v.z * 4) + 128;
1051 if(x > 255 || x < 0)
1053 LOG_INFO("shot origin ", vtos(v), " x out of bounds\n");
1054 x = bound(0, x, 255);
1056 if(y > 255 || y < 0)
1058 LOG_INFO("shot origin ", vtos(v), " y out of bounds\n");
1059 y = bound(0, y, 255);
1061 if(z > 255 || z < 0)
1063 LOG_INFO("shot origin ", vtos(v), " z out of bounds\n");
1064 z = bound(0, z, 255);
1066 return x * 0x10000 + y * 0x100 + z;
1068 vector decompressShotOrigin(int f)
1071 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1072 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1073 v.z = ((f & 0xFF) - 128) / 4;
1078 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1080 // NOTE: we'll always choose the SMALLER value...
1081 float healthdamage, armordamage, armorideal;
1082 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1085 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1086 armordamage = a + (h - 1); // damage we can take if we could use more armor
1087 armorideal = healthdamage * armorblock;
1089 if(armordamage < healthdamage)
1102 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1105 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1107 v.y = bound(0, damage * armorblock, a); // save
1108 v.x = bound(0, damage - v.y, damage); // take
1114 string getcurrentmod()
1118 m = cvar_string("fs_gamedir");
1119 n = tokenize_console(m);
1126 float matchacl(string acl, string str)
1133 t = car(acl); acl = cdr(acl);
1136 if(substring(t, 0, 1) == "-")
1139 t = substring(t, 1, strlen(t) - 1);
1141 else if(substring(t, 0, 1) == "+")
1142 t = substring(t, 1, strlen(t) - 1);
1144 if(substring(t, -1, 1) == "*")
1146 t = substring(t, 0, strlen(t) - 1);
1147 s = substring(str, 0, strlen(t));
1160 string get_model_datafilename(string m, float sk, string fil)
1165 m = "models/player/*_";
1167 m = strcat(m, ftos(sk));
1170 return strcat(m, ".", fil);
1173 float get_model_parameters(string m, float sk)
1175 get_model_parameters_modelname = string_null;
1176 get_model_parameters_modelskin = -1;
1177 get_model_parameters_name = string_null;
1178 get_model_parameters_species = -1;
1179 get_model_parameters_sex = string_null;
1180 get_model_parameters_weight = -1;
1181 get_model_parameters_age = -1;
1182 get_model_parameters_desc = string_null;
1183 get_model_parameters_bone_upperbody = string_null;
1184 get_model_parameters_bone_weapon = string_null;
1185 for(int i = 0; i < MAX_AIM_BONES; ++i)
1187 get_model_parameters_bone_aim[i] = string_null;
1188 get_model_parameters_bone_aimweight[i] = 0;
1190 get_model_parameters_fixbone = 0;
1193 MUTATOR_CALLHOOK(ClearModelParams);
1199 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1200 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1204 if(substring(m, -4, -1) != ".txt")
1206 if(substring(m, -6, 1) != "_")
1208 sk = stof(substring(m, -5, 1));
1209 m = substring(m, 0, -7);
1212 string fn = get_model_datafilename(m, sk, "txt");
1213 int fh = fopen(fn, FILE_READ);
1217 fn = get_model_datafilename(m, sk, "txt");
1218 fh = fopen(fn, FILE_READ);
1223 get_model_parameters_modelname = m;
1224 get_model_parameters_modelskin = sk;
1226 while((s = fgets(fh)))
1229 break; // next lines will be description
1233 get_model_parameters_name = s;
1237 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1238 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1239 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1240 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1241 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1242 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1243 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1246 get_model_parameters_sex = s;
1248 get_model_parameters_weight = stof(s);
1250 get_model_parameters_age = stof(s);
1251 if(c == "description")
1252 get_model_parameters_description = s;
1253 if(c == "bone_upperbody")
1254 get_model_parameters_bone_upperbody = s;
1255 if(c == "bone_weapon")
1256 get_model_parameters_bone_weapon = s;
1258 MUTATOR_CALLHOOK(GetModelParams, c, s);
1260 for(int i = 0; i < MAX_AIM_BONES; ++i)
1261 if(c == strcat("bone_aim", ftos(i)))
1263 get_model_parameters_bone_aimweight[i] = stof(car(s));
1264 get_model_parameters_bone_aim[i] = cdr(s);
1267 get_model_parameters_fixbone = stof(s);
1270 while((s = fgets(fh)))
1272 if(get_model_parameters_desc)
1273 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1275 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1283 // x-encoding (encoding as zero length invisible string)
1284 const string XENCODE_2 = "xX";
1285 const string XENCODE_22 = "0123456789abcdefABCDEF";
1286 string xencode(int f)
1289 d = f % 22; f = floor(f / 22);
1290 c = f % 22; f = floor(f / 22);
1291 b = f % 22; f = floor(f / 22);
1292 a = f % 2; // f = floor(f / 2);
1295 substring(XENCODE_2, a, 1),
1296 substring(XENCODE_22, b, 1),
1297 substring(XENCODE_22, c, 1),
1298 substring(XENCODE_22, d, 1)
1301 float xdecode(string s)
1304 if(substring(s, 0, 1) != "^")
1308 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1309 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1310 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1311 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1312 if(a < 0 || b < 0 || c < 0 || d < 0)
1314 return ((a * 22 + b) * 22 + c) * 22 + d;
1318 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1320 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1323 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1326 float shutdown_running;
1331 void CSQC_Shutdown()
1337 if(shutdown_running)
1339 LOG_INFO("Recursive shutdown detected! Only restoring cvars...\n");
1343 shutdown_running = 1;
1347 cvar_settemp_restore(); // this must be done LAST, but in any case
1351 .float skeleton_bones_index;
1352 void Skeleton_SetBones(entity e)
1354 // set skeleton_bones to the total number of bones on the model
1355 if(e.skeleton_bones_index == e.modelindex)
1356 return; // same model, nothing to update
1359 skelindex = skel_create(e.modelindex);
1360 e.skeleton_bones = skel_get_numbones(skelindex);
1361 skel_delete(skelindex);
1362 e.skeleton_bones_index = e.modelindex;
1366 string to_execute_next_frame;
1367 void execute_next_frame()
1369 if(to_execute_next_frame)
1371 localcmd("\n", to_execute_next_frame, "\n");
1372 strunzone(to_execute_next_frame);
1373 to_execute_next_frame = string_null;
1376 void queue_to_execute_next_frame(string s)
1378 if(to_execute_next_frame)
1380 s = strcat(s, "\n", to_execute_next_frame);
1381 strunzone(to_execute_next_frame);
1383 to_execute_next_frame = strzone(s);
1386 .float FindConnectedComponent_processing;
1387 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1389 entity queue_start, queue_end;
1391 // we build a queue of to-be-processed entities.
1392 // queue_start is the next entity to be checked for neighbors
1393 // queue_end is the last entity added
1395 if(e.FindConnectedComponent_processing)
1396 error("recursion or broken cleanup");
1398 // start with a 1-element queue
1399 queue_start = queue_end = e;
1400 queue_end.(fld) = world;
1401 queue_end.FindConnectedComponent_processing = 1;
1403 // for each queued item:
1404 for (; queue_start; queue_start = queue_start.(fld))
1406 // find all neighbors of queue_start
1408 for(t = world; (t = nxt(t, queue_start, pass)); )
1410 if(t.FindConnectedComponent_processing)
1412 if(iscon(t, queue_start, pass))
1414 // it is connected? ADD IT. It will look for neighbors soon too.
1415 queue_end.(fld) = t;
1417 queue_end.(fld) = world;
1418 queue_end.FindConnectedComponent_processing = 1;
1424 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1425 queue_start.FindConnectedComponent_processing = 0;
1429 vector animfixfps(entity e, vector a, vector b)
1431 // multi-frame anim: keep as-is
1435 dur = frameduration(e.modelindex, a.x);
1439 dur = frameduration(e.modelindex, a.x);
1449 void dedicated_print(string input) // print(), but only print if the server is not local
1451 if(server_is_dedicated) { LOG_INFO(input); }
1456 float Announcer_PickNumber(float type, float num)
1464 case 10: return ANNCE_NUM_GAMESTART_10;
1465 case 9: return ANNCE_NUM_GAMESTART_9;
1466 case 8: return ANNCE_NUM_GAMESTART_8;
1467 case 7: return ANNCE_NUM_GAMESTART_7;
1468 case 6: return ANNCE_NUM_GAMESTART_6;
1469 case 5: return ANNCE_NUM_GAMESTART_5;
1470 case 4: return ANNCE_NUM_GAMESTART_4;
1471 case 3: return ANNCE_NUM_GAMESTART_3;
1472 case 2: return ANNCE_NUM_GAMESTART_2;
1473 case 1: return ANNCE_NUM_GAMESTART_1;
1481 case 10: return ANNCE_NUM_IDLE_10;
1482 case 9: return ANNCE_NUM_IDLE_9;
1483 case 8: return ANNCE_NUM_IDLE_8;
1484 case 7: return ANNCE_NUM_IDLE_7;
1485 case 6: return ANNCE_NUM_IDLE_6;
1486 case 5: return ANNCE_NUM_IDLE_5;
1487 case 4: return ANNCE_NUM_IDLE_4;
1488 case 3: return ANNCE_NUM_IDLE_3;
1489 case 2: return ANNCE_NUM_IDLE_2;
1490 case 1: return ANNCE_NUM_IDLE_1;
1498 case 10: return ANNCE_NUM_KILL_10;
1499 case 9: return ANNCE_NUM_KILL_9;
1500 case 8: return ANNCE_NUM_KILL_8;
1501 case 7: return ANNCE_NUM_KILL_7;
1502 case 6: return ANNCE_NUM_KILL_6;
1503 case 5: return ANNCE_NUM_KILL_5;
1504 case 4: return ANNCE_NUM_KILL_4;
1505 case 3: return ANNCE_NUM_KILL_3;
1506 case 2: return ANNCE_NUM_KILL_2;
1507 case 1: return ANNCE_NUM_KILL_1;
1515 case 10: return ANNCE_NUM_RESPAWN_10;
1516 case 9: return ANNCE_NUM_RESPAWN_9;
1517 case 8: return ANNCE_NUM_RESPAWN_8;
1518 case 7: return ANNCE_NUM_RESPAWN_7;
1519 case 6: return ANNCE_NUM_RESPAWN_6;
1520 case 5: return ANNCE_NUM_RESPAWN_5;
1521 case 4: return ANNCE_NUM_RESPAWN_4;
1522 case 3: return ANNCE_NUM_RESPAWN_3;
1523 case 2: return ANNCE_NUM_RESPAWN_2;
1524 case 1: return ANNCE_NUM_RESPAWN_1;
1528 case CNT_ROUNDSTART:
1532 case 10: return ANNCE_NUM_ROUNDSTART_10;
1533 case 9: return ANNCE_NUM_ROUNDSTART_9;
1534 case 8: return ANNCE_NUM_ROUNDSTART_8;
1535 case 7: return ANNCE_NUM_ROUNDSTART_7;
1536 case 6: return ANNCE_NUM_ROUNDSTART_6;
1537 case 5: return ANNCE_NUM_ROUNDSTART_5;
1538 case 4: return ANNCE_NUM_ROUNDSTART_4;
1539 case 3: return ANNCE_NUM_ROUNDSTART_3;
1540 case 2: return ANNCE_NUM_ROUNDSTART_2;
1541 case 1: return ANNCE_NUM_ROUNDSTART_1;
1549 case 10: return ANNCE_NUM_10;
1550 case 9: return ANNCE_NUM_9;
1551 case 8: return ANNCE_NUM_8;
1552 case 7: return ANNCE_NUM_7;
1553 case 6: return ANNCE_NUM_6;
1554 case 5: return ANNCE_NUM_5;
1555 case 4: return ANNCE_NUM_4;
1556 case 3: return ANNCE_NUM_3;
1557 case 2: return ANNCE_NUM_2;
1558 case 1: return ANNCE_NUM_1;
1563 return NOTIF_ABORT; // abort sending if none of these numbers were right
1568 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1570 switch(nativecontents)
1575 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1577 return DPCONTENTS_WATER;
1579 return DPCONTENTS_SLIME;
1581 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1583 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1588 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1590 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1591 return CONTENT_SOLID;
1592 if(supercontents & DPCONTENTS_SKY)
1594 if(supercontents & DPCONTENTS_LAVA)
1595 return CONTENT_LAVA;
1596 if(supercontents & DPCONTENTS_SLIME)
1597 return CONTENT_SLIME;
1598 if(supercontents & DPCONTENTS_WATER)
1599 return CONTENT_WATER;
1600 return CONTENT_EMPTY;