4 #include "constants.qh"
5 #include "../client/mutators/events.qh"
7 #include "notifications/all.qh"
9 #include <common/deathtypes/all.qh>
12 #include "constants.qh"
13 #include "../server/mutators/events.qh"
14 #include "notifications/all.qh"
15 #include <common/deathtypes/all.qh>
22 * Get "real" origin, in worldspace, even if ent is attached to something else.
24 vector real_origin(entity ent)
26 vector v = ((ent.absmin + ent.absmax) * 0.5);
27 entity e = ent.tag_entity;
31 v = v + ((e.absmin + e.absmax) * 0.5);
39 string wordwrap_buffer;
41 void wordwrap_buffer_put(string s)
43 wordwrap_buffer = strcat(wordwrap_buffer, s);
46 string wordwrap(string s, float l)
50 wordwrap_cb(s, l, wordwrap_buffer_put);
57 entity _wordwrap_buffer_sprint_ent;
58 void wordwrap_buffer_sprint(string s)
60 wordwrap_buffer = strcat(wordwrap_buffer, s);
63 sprint(_wordwrap_buffer_sprint_ent, wordwrap_buffer);
68 void wordwrap_sprint(entity to, string s, float l)
71 _wordwrap_buffer_sprint_ent = to;
72 wordwrap_cb(s, l, wordwrap_buffer_sprint);
73 _wordwrap_buffer_sprint_ent = NULL;
74 if(wordwrap_buffer != "")
75 sprint(to, strcat(wordwrap_buffer, "\n"));
82 string draw_UseSkinFor(string pic)
84 if(substring(pic, 0, 1) == "/")
85 return substring(pic, 1, strlen(pic)-1);
87 return strcat(draw_currentSkin, "/", pic);
91 void wordwrap_cb(string s, float l, void(string) callback)
94 float lleft, i, j, wlen;
98 for (i = 0;i < strlen(s);++i)
100 if (substring(s, i, 2) == "\\n")
106 else if (substring(s, i, 1) == "\n")
111 else if (substring(s, i, 1) == " ")
121 for (j = i+1;j < strlen(s);++j)
122 // ^^ this skips over the first character of a word, which
123 // is ALWAYS part of the word
124 // this is safe since if i+1 == strlen(s), i will become
125 // strlen(s)-1 at the end of this block and the function
126 // will terminate. A space can't be the first character we
127 // read here, and neither can a \n be the start, since these
128 // two cases have been handled above.
130 c = substring(s, j, 1);
137 // we need to keep this tempstring alive even if substring is
138 // called repeatedly, so call strcat even though we're not
148 callback(substring(s, i, wlen));
149 lleft = lleft - wlen;
156 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
186 string ScoreString(int pFlags, float pValue)
191 pValue = floor(pValue + 0.5); // round
193 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
195 else if(pFlags & SFL_RANK)
197 valstr = ftos(pValue);
199 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
200 valstr = strcat(valstr, "th");
201 else if(substring(valstr, l - 1, 1) == "1")
202 valstr = strcat(valstr, "st");
203 else if(substring(valstr, l - 1, 1) == "2")
204 valstr = strcat(valstr, "nd");
205 else if(substring(valstr, l - 1, 1) == "3")
206 valstr = strcat(valstr, "rd");
208 valstr = strcat(valstr, "th");
210 else if(pFlags & SFL_TIME)
211 valstr = TIME_ENCODED_TOSTRING(pValue);
213 valstr = ftos(pValue);
219 // compressed vector format:
220 // like MD3, just even shorter
221 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
222 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
223 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
224 // length = 2^(length_encoded/8) / 8
225 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
226 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
227 // the special value 0 indicates the zero vector
229 float lengthLogTable[128];
231 float invertLengthLog(float dist)
235 if(dist >= lengthLogTable[127])
237 if(dist <= lengthLogTable[0])
245 m = floor((l + r) / 2);
246 if(lengthLogTable[m] < dist)
252 // now: r is >=, l is <
253 float lerr = (dist - lengthLogTable[l]);
254 float rerr = (lengthLogTable[r] - dist);
260 vector decompressShortVector(int data)
265 float p = (data & 0xF000) / 0x1000;
266 float q = (data & 0x0F80) / 0x80;
267 int len = (data & 0x007F);
269 //print("\ndecompress: p ", ftos(p)); print("q ", ftos(q)); print("len ", ftos(len), "\n");
282 q = .19634954084936207740 * q;
283 p = .19634954084936207740 * p - 1.57079632679489661922;
284 out.x = cos(q) * cos(p);
285 out.y = sin(q) * cos(p);
289 //print("decompressed: ", vtos(out), "\n");
291 return out * lengthLogTable[len];
294 float compressShortVector(vector vec)
300 //print("compress: ", vtos(vec), "\n");
301 ang = vectoangles(vec);
305 if(ang.x < -90 && ang.x > +90)
306 error("BOGUS vectoangles");
307 //print("angles: ", vtos(ang), "\n");
309 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
318 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
319 len = invertLengthLog(vlen(vec));
321 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
323 return (p * 0x1000) + (y * 0x80) + len;
326 STATIC_INIT(compressShortVector)
329 float f = (2 ** (1/8));
331 for(i = 0; i < 128; ++i)
333 lengthLogTable[i] = l;
337 if(cvar("developer"))
339 LOG_INFO("Verifying vector compression table...");
340 for(i = 0x0F00; i < 0xFFFF; ++i)
341 if(i != compressShortVector(decompressShortVector(i)))
344 "BROKEN vector compression: %s -> %s -> %s",
346 vtos(decompressShortVector(i)),
347 ftos(compressShortVector(decompressShortVector(i)))
356 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
358 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
359 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
360 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
361 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
362 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
363 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
364 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
365 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
366 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
367 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
368 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
369 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
374 string fixPriorityList(string order, float from, float to, float subtract, float complete)
379 n = tokenize_console(order);
381 for(i = 0; i < n; ++i)
386 if(w >= from && w <= to)
387 neworder = strcat(neworder, ftos(w), " ");
391 if(w >= from && w <= to)
392 neworder = strcat(neworder, ftos(w), " ");
399 n = tokenize_console(neworder);
400 for(w = to; w >= from; --w)
402 int wflags = Weapons_from(w).spawnflags;
403 if((wflags & WEP_FLAG_HIDDEN) && (wflags & WEP_FLAG_MUTATORBLOCKED) && !(wflags & WEP_FLAG_NORMAL))
405 for(i = 0; i < n; ++i)
406 if(stof(argv(i)) == w)
408 if(i == n) // not found
409 neworder = strcat(neworder, ftos(w), " ");
413 return substring(neworder, 0, strlen(neworder) - 1);
416 string mapPriorityList(string order, string(string) mapfunc)
421 n = tokenize_console(order);
423 for(float i = 0; i < n; ++i)
424 neworder = strcat(neworder, mapfunc(argv(i)), " ");
426 return substring(neworder, 0, strlen(neworder) - 1);
429 string swapInPriorityList(string order, float i, float j)
431 float n = tokenize_console(order);
433 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
436 for(float w = 0; w < n; ++w)
439 s = strcat(s, argv(j), " ");
441 s = strcat(s, argv(i), " ");
443 s = strcat(s, argv(w), " ");
445 return substring(s, 0, strlen(s) - 1);
452 void get_mi_min_max(float mode)
457 if(!strcasecmp(substring(s, 0, 5), "maps/"))
458 s = substring(s, 5, strlen(s) - 5);
459 if(!strcasecmp(substring(s, strlen(s) - 4, 4), ".bsp"))
460 s = substring(s, 0, strlen(s) - 4);
461 strcpy(mi_shortname, s);
473 MapInfo_Get_ByName(mi_shortname, 0, NULL);
474 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
476 mi_min = MapInfo_Map_mins;
477 mi_max = MapInfo_Map_maxs;
485 tracebox('1 0 0' * mi.x,
486 '0 1 0' * mi.y + '0 0 1' * mi.z,
487 '0 1 0' * ma.y + '0 0 1' * ma.z,
491 if(!trace_startsolid)
492 mi_min.x = trace_endpos.x;
494 tracebox('0 1 0' * mi.y,
495 '1 0 0' * mi.x + '0 0 1' * mi.z,
496 '1 0 0' * ma.x + '0 0 1' * ma.z,
500 if(!trace_startsolid)
501 mi_min.y = trace_endpos.y;
503 tracebox('0 0 1' * mi.z,
504 '1 0 0' * mi.x + '0 1 0' * mi.y,
505 '1 0 0' * ma.x + '0 1 0' * ma.y,
509 if(!trace_startsolid)
510 mi_min.z = trace_endpos.z;
512 tracebox('1 0 0' * ma.x,
513 '0 1 0' * mi.y + '0 0 1' * mi.z,
514 '0 1 0' * ma.y + '0 0 1' * ma.z,
518 if(!trace_startsolid)
519 mi_max.x = trace_endpos.x;
521 tracebox('0 1 0' * ma.y,
522 '1 0 0' * mi.x + '0 0 1' * mi.z,
523 '1 0 0' * ma.x + '0 0 1' * ma.z,
527 if(!trace_startsolid)
528 mi_max.y = trace_endpos.y;
530 tracebox('0 0 1' * ma.z,
531 '1 0 0' * mi.x + '0 1 0' * mi.y,
532 '1 0 0' * ma.x + '0 1 0' * ma.y,
536 if(!trace_startsolid)
537 mi_max.z = trace_endpos.z;
542 void get_mi_min_max_texcoords(float mode)
546 get_mi_min_max(mode);
551 // extend mi_picmax to get a square aspect ratio
552 // center the map in that area
553 extend = mi_picmax - mi_picmin;
554 if(extend.y > extend.x)
556 mi_picmin.x -= (extend.y - extend.x) * 0.5;
557 mi_picmax.x += (extend.y - extend.x) * 0.5;
561 mi_picmin.y -= (extend.x - extend.y) * 0.5;
562 mi_picmax.y += (extend.x - extend.y) * 0.5;
565 // add another some percent
566 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
570 // calculate the texcoords
571 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
572 // first the two corners of the origin
573 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
574 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
575 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
576 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
577 // then the other corners
578 mi_pictexcoord1_x = mi_pictexcoord0_x;
579 mi_pictexcoord1_y = mi_pictexcoord2_y;
580 mi_pictexcoord3_x = mi_pictexcoord2_x;
581 mi_pictexcoord3_y = mi_pictexcoord0_y;
585 float cvar_settemp(string tmp_cvar, string tmp_value)
587 float created_saved_value;
589 created_saved_value = 0;
591 if (!(tmp_cvar || tmp_value))
593 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
597 if(!cvar_type(tmp_cvar))
599 LOG_INFOF("Error: cvar %s doesn't exist!", tmp_cvar);
603 IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
605 created_saved_value = -1; // skip creation
606 break; // no need to continue
609 if(created_saved_value != -1)
611 // creating a new entity to keep track of this cvar
612 entity e = new_pure(saved_cvar_value);
613 IL_PUSH(g_saved_cvars, e);
614 e.netname = strzone(tmp_cvar);
615 e.message = strzone(cvar_string(tmp_cvar));
616 created_saved_value = 1;
619 // update the cvar to the value given
620 cvar_set(tmp_cvar, tmp_value);
622 return created_saved_value;
625 int cvar_settemp_restore()
628 // FIXME this new-style loop fails!
630 FOREACH_ENTITY_CLASS("saved_cvar_value", true,
632 if(cvar_type(it.netname))
634 cvar_set(it.netname, it.message);
635 strunzone(it.netname);
636 strunzone(it.message);
641 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", it.netname);
646 while((e = find(e, classname, "saved_cvar_value")))
648 if(cvar_type(e.netname))
650 cvar_set(e.netname, e.message);
655 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", e.netname));
662 bool isCaretEscaped(string theText, float pos)
665 while(pos - i >= 1 && substring(theText, pos - i - 1, 1) == "^")
670 int skipIncompleteTag(string theText, float pos, int len)
674 if(substring(theText, pos - 1, 1) == "^")
676 if(isCaretEscaped(theText, pos - 1) || pos >= len)
679 int ch = str2chr(theText, pos);
680 if(ch >= '0' && ch <= '9')
681 return 1; // ^[0-9] color code found
683 tag_start = pos - 1; // ^x tag found
689 for(int i = 2; pos - i >= 0 && i <= 4; ++i)
691 if(substring(theText, pos - i, 2) == "^x")
693 tag_start = pos - i; // ^x tag found
701 if(tag_start + 5 < len)
702 if(IS_HEXDIGIT(substring(theText, tag_start + 2, 1)))
703 if(IS_HEXDIGIT(substring(theText, tag_start + 3, 1)))
704 if(IS_HEXDIGIT(substring(theText, tag_start + 4, 1)))
706 if(!isCaretEscaped(theText, tag_start))
707 return 5 - (pos - tag_start); // ^xRGB color code found
713 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
716 // The following function is SLOW.
717 // For your safety and for the protection of those around you...
718 // DO NOT CALL THIS AT HOME.
720 if(w(theText, theSize) <= maxWidth)
721 return strlen(theText); // yeah!
723 bool colors = (w("^7", theSize) == 0);
725 // binary search for right place to cut string
726 int len, left, right, middle;
728 right = len = strlen(theText);
732 middle = floor((left + right) / 2);
734 ofs = skipIncompleteTag(theText, middle, len);
735 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
740 while(left < right - 1);
745 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
748 // The following function is SLOW.
749 // For your safety and for the protection of those around you...
750 // DO NOT CALL THIS AT HOME.
752 if(w(theText) <= maxWidth)
753 return strlen(theText); // yeah!
755 bool colors = (w("^7") == 0);
757 // binary search for right place to cut string
758 int len, left, right, middle;
760 right = len = strlen(theText);
764 middle = floor((left + right) / 2);
766 ofs = skipIncompleteTag(theText, middle, len);
767 if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
772 while(left < right - 1);
777 string find_last_color_code(string s)
779 int start = strstrofs(s, "^", 0);
780 if (start == -1) // no caret found
782 int len = strlen(s)-1;
783 for(int i = len; i >= start; --i)
785 if(substring(s, i, 1) != "^")
789 while (i-carets >= start && substring(s, i-carets, 1) == "^")
792 // check if carets aren't all escaped
796 if(IS_DIGIT(substring(s, i+1, 1)))
797 return substring(s, i, 2);
800 if(substring(s, i+1, 1) == "x")
801 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
802 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
803 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
804 return substring(s, i, 5);
806 i -= carets; // this also skips one char before the carets
812 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
818 s = getWrappedLine_remaining;
822 getWrappedLine_remaining = string_null;
823 return s; // the line has no size ANYWAY, nothing would be displayed.
826 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
827 if(cantake > 0 && cantake < strlen(s))
830 while(take > 0 && substring(s, take, 1) != " ")
834 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
835 if(getWrappedLine_remaining == "")
836 getWrappedLine_remaining = string_null;
837 else if (tw("^7", theFontSize) == 0)
838 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
839 return substring(s, 0, cantake);
843 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
844 if(getWrappedLine_remaining == "")
845 getWrappedLine_remaining = string_null;
846 else if (tw("^7", theFontSize) == 0)
847 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
848 return substring(s, 0, take);
853 getWrappedLine_remaining = string_null;
858 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
864 s = getWrappedLine_remaining;
868 getWrappedLine_remaining = string_null;
869 return s; // the line has no size ANYWAY, nothing would be displayed.
872 cantake = textLengthUpToLength(s, w, tw);
873 if(cantake > 0 && cantake < strlen(s))
876 while(take > 0 && substring(s, take, 1) != " ")
880 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
881 if(getWrappedLine_remaining == "")
882 getWrappedLine_remaining = string_null;
883 else if (tw("^7") == 0)
884 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
885 return substring(s, 0, cantake);
889 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
890 if(getWrappedLine_remaining == "")
891 getWrappedLine_remaining = string_null;
892 else if (tw("^7") == 0)
893 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
894 return substring(s, 0, take);
899 getWrappedLine_remaining = string_null;
904 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
906 if(tw(theText, theFontSize) <= maxWidth)
909 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
912 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
914 if(tw(theText) <= maxWidth)
917 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
920 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
922 string subpattern, subpattern2, subpattern3, subpattern4;
923 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
925 subpattern2 = ",teams,";
927 subpattern2 = ",noteams,";
929 subpattern3 = ",teamspawns,";
931 subpattern3 = ",noteamspawns,";
932 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
933 subpattern4 = ",race,";
935 subpattern4 = string_null;
937 if(substring(pattern, 0, 1) == "-")
939 pattern = substring(pattern, 1, strlen(pattern) - 1);
940 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
942 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
944 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
946 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
951 if(substring(pattern, 0, 1) == "+")
952 pattern = substring(pattern, 1, strlen(pattern) - 1);
953 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
954 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
955 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
959 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
966 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
970 // make origin and speed relative
975 // now solve for ret, ret normalized:
976 // eorg + t * evel == t * ret * spd
977 // or, rather, solve for t:
978 // |eorg + t * evel| == t * spd
979 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
980 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
981 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
982 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
983 // q = (eorg * eorg) / (evel * evel - spd * spd)
984 if(!solution.z) // no real solution
987 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
988 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
989 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
990 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
991 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
992 // spd < |evel| * sin angle(evel, eorg)
995 else if(solution.x > 0)
997 // both solutions > 0: take the smaller one
998 // happens if p < 0 and q > 0
999 ret = normalize(eorg + solution.x * evel);
1001 else if(solution.y > 0)
1003 // one solution > 0: take the larger one
1004 // happens if q < 0 or q == 0 and p < 0
1005 ret = normalize(eorg + solution.y * evel);
1009 // no solution > 0: reject
1010 // happens if p > 0 and q >= 0
1011 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1012 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1017 // "Enemy is moving away from me at more than spd"
1021 // NOTE: we always got a solution if spd > |evel|
1023 if(newton_style == 2)
1024 ret = normalize(ret * spd + myvel);
1029 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1034 if(newton_style == 2)
1036 // true Newtonian projectiles with automatic aim adjustment
1038 // solve: |outspeed * mydir - myvel| = spd
1039 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1040 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1044 // myvel^2 - (mydir * myvel)^2 > spd^2
1045 // velocity without mydir component > spd
1046 // fire at smallest possible spd that works?
1047 // |(mydir * myvel) * myvel - myvel| = spd
1049 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1053 outspeed = solution.y; // the larger one
1056 //outspeed = 0; // slowest possible shot
1057 outspeed = solution.x; // the real part (that is, the average!)
1058 //dprint("impossible shot, adjusting\n");
1061 outspeed = bound(spd * mi, outspeed, spd * ma);
1062 return mydir * outspeed;
1066 return myvel + spd * mydir;
1069 float compressShotOrigin(vector v)
1071 float rx = rint(v.x * 2);
1072 float ry = rint(v.y * 4) + 128;
1073 float rz = rint(v.z * 4) + 128;
1074 if(rx > 255 || rx < 0)
1076 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1077 rx = bound(0, rx, 255);
1079 if(ry > 255 || ry < 0)
1081 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1082 ry = bound(0, ry, 255);
1084 if(rz > 255 || rz < 0)
1086 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1087 rz = bound(0, rz, 255);
1089 return rx * 0x10000 + ry * 0x100 + rz;
1091 vector decompressShotOrigin(int f)
1094 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1095 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1096 v.z = ((f & 0xFF) - 128) / 4;
1101 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1103 // NOTE: we'll always choose the SMALLER value...
1104 float healthdamage, armordamage, armorideal;
1105 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1108 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1109 armordamage = a + (h - 1); // damage we can take if we could use more armor
1110 armorideal = healthdamage * armorblock;
1112 if(armordamage < healthdamage)
1125 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1128 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1130 v.y = bound(0, damage * armorblock, a); // save
1131 v.x = bound(0, damage - v.y, damage); // take
1137 string getcurrentmod()
1141 m = cvar_string("fs_gamedir");
1142 n = tokenize_console(m);
1149 float matchacl(string acl, string str)
1156 t = car(acl); acl = cdr(acl);
1159 if(substring(t, 0, 1) == "-")
1162 t = substring(t, 1, strlen(t) - 1);
1164 else if(substring(t, 0, 1) == "+")
1165 t = substring(t, 1, strlen(t) - 1);
1167 if(substring(t, -1, 1) == "*")
1169 t = substring(t, 0, strlen(t) - 1);
1170 s = substring(str, 0, strlen(t));
1183 string get_model_datafilename(string m, float sk, string fil)
1188 m = "models/player/*_";
1190 m = strcat(m, ftos(sk));
1193 return strcat(m, ".", fil);
1196 float get_model_parameters(string m, float sk)
1198 get_model_parameters_modelname = string_null;
1199 get_model_parameters_modelskin = -1;
1200 get_model_parameters_name = string_null;
1201 get_model_parameters_species = -1;
1202 get_model_parameters_sex = string_null;
1203 get_model_parameters_weight = -1;
1204 get_model_parameters_age = -1;
1205 get_model_parameters_desc = string_null;
1206 get_model_parameters_bone_upperbody = string_null;
1207 get_model_parameters_bone_weapon = string_null;
1208 for(int i = 0; i < MAX_AIM_BONES; ++i)
1210 get_model_parameters_bone_aim[i] = string_null;
1211 get_model_parameters_bone_aimweight[i] = 0;
1213 get_model_parameters_fixbone = 0;
1214 get_model_parameters_hidden = false;
1217 MUTATOR_CALLHOOK(ClearModelParams);
1223 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1224 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1228 if(substring(m, -4, -1) != ".txt")
1230 if(substring(m, -6, 1) != "_")
1232 sk = stof(substring(m, -5, 1));
1233 m = substring(m, 0, -7);
1236 string fn = get_model_datafilename(m, sk, "txt");
1237 int fh = fopen(fn, FILE_READ);
1241 fn = get_model_datafilename(m, sk, "txt");
1242 fh = fopen(fn, FILE_READ);
1247 get_model_parameters_modelname = m;
1248 get_model_parameters_modelskin = sk;
1250 while((s = fgets(fh)))
1253 break; // next lines will be description
1257 get_model_parameters_name = s;
1261 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1262 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1263 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1264 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1265 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1266 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1267 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1270 get_model_parameters_sex = s;
1272 get_model_parameters_weight = stof(s);
1274 get_model_parameters_age = stof(s);
1275 if(c == "description")
1276 get_model_parameters_description = s;
1277 if(c == "bone_upperbody")
1278 get_model_parameters_bone_upperbody = s;
1279 if(c == "bone_weapon")
1280 get_model_parameters_bone_weapon = s;
1282 MUTATOR_CALLHOOK(GetModelParams, c, s);
1284 for(int i = 0; i < MAX_AIM_BONES; ++i)
1285 if(c == strcat("bone_aim", ftos(i)))
1287 get_model_parameters_bone_aimweight[i] = stof(car(s));
1288 get_model_parameters_bone_aim[i] = cdr(s);
1291 get_model_parameters_fixbone = stof(s);
1293 get_model_parameters_hidden = stob(s);
1296 while((s = fgets(fh)))
1298 if(get_model_parameters_desc)
1299 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1301 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1309 string translate_key(string key)
1311 if (prvm_language == "en") return key;
1315 case "TAB": return _("TAB");
1316 case "ENTER": return _("ENTER");
1317 case "ESCAPE": return _("ESCAPE");
1318 case "SPACE": return _("SPACE");
1320 case "BACKSPACE": return _("BACKSPACE");
1321 case "UPARROW": return _("UPARROW");
1322 case "DOWNARROW": return _("DOWNARROW");
1323 case "LEFTARROW": return _("LEFTARROW");
1324 case "RIGHTARROW": return _("RIGHTARROW");
1326 case "ALT": return _("ALT");
1327 case "CTRL": return _("CTRL");
1328 case "SHIFT": return _("SHIFT");
1330 case "F1": return _("F1");
1331 case "F2": return _("F2");
1332 case "F3": return _("F3");
1333 case "F4": return _("F4");
1334 case "F5": return _("F5");
1335 case "F6": return _("F6");
1336 case "F7": return _("F7");
1337 case "F8": return _("F8");
1338 case "F9": return _("F9");
1339 case "F10": return _("F10");
1340 case "F11": return _("F11");
1341 case "F12": return _("F12");
1343 case "INS": return _("INS");
1344 case "DEL": return _("DEL");
1345 case "PGDN": return _("PGDN");
1346 case "PGUP": return _("PGUP");
1347 case "HOME": return _("HOME");
1348 case "END": return _("END");
1350 case "PAUSE": return _("PAUSE");
1352 case "NUMLOCK": return _("NUMLOCK");
1353 case "CAPSLOCK": return _("CAPSLOCK");
1354 case "SCROLLOCK": return _("SCROLLOCK");
1357 if (substring(key, 0, 3) == "KP_")
1359 string subkey = substring(key, 3, -1);
1360 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1362 return sprintf(_("KP_%d"), stof(subkey));
1367 case "INS": return sprintf(_("KP_%s"), _("INS"));
1368 case "END": return sprintf(_("KP_%s"), _("END"));
1369 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1370 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1371 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1372 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1373 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1374 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1375 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1376 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1377 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1378 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1379 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1380 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1381 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1382 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1383 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1384 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1385 default: return key;
1389 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1391 if (substring(key, 0, 5) == "MOUSE")
1392 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1394 if (key == "MWHEELUP") return _("MWHEELUP");
1395 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1397 if (substring(key, 0,3) == "JOY")
1398 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1400 if (substring(key, 0,3) == "AUX")
1401 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1403 if (substring(key, 0, 4) == "X360_")
1405 string subkey = substring(key, 4, -1);
1408 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1409 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1410 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1411 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1412 case "START": return sprintf(_("X360_%s"), _("START"));
1413 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1414 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1415 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1416 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1417 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1418 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1419 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1420 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1421 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1422 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1423 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1424 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1425 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1426 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1427 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1428 default: return key;
1432 if (substring(key, 0, 4) == "JOY_")
1434 string subkey = substring(key, 4, -1);
1437 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1438 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1439 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1440 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1441 default: return key;
1445 if (substring(key, 0, 8) == "MIDINOTE")
1446 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1451 // x-encoding (encoding as zero length invisible string)
1452 const string XENCODE_2 = "xX";
1453 const string XENCODE_22 = "0123456789abcdefABCDEF";
1454 string xencode(int f)
1457 d = f % 22; f = floor(f / 22);
1458 c = f % 22; f = floor(f / 22);
1459 b = f % 22; f = floor(f / 22);
1460 a = f % 2; // f = floor(f / 2);
1463 substring(XENCODE_2, a, 1),
1464 substring(XENCODE_22, b, 1),
1465 substring(XENCODE_22, c, 1),
1466 substring(XENCODE_22, d, 1)
1469 float xdecode(string s)
1472 if(substring(s, 0, 1) != "^")
1476 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1477 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1478 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1479 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1480 if(a < 0 || b < 0 || c < 0 || d < 0)
1482 return ((a * 22 + b) * 22 + c) * 22 + d;
1486 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1488 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1491 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1494 float shutdown_running;
1499 void CSQC_Shutdown()
1505 if(shutdown_running)
1507 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1511 shutdown_running = 1;
1515 cvar_settemp_restore(); // this must be done LAST, but in any case
1519 .float skeleton_bones_index;
1520 void Skeleton_SetBones(entity e)
1522 // set skeleton_bones to the total number of bones on the model
1523 if(e.skeleton_bones_index == e.modelindex)
1524 return; // same model, nothing to update
1527 skelindex = skel_create(e.modelindex);
1528 e.skeleton_bones = skel_get_numbones(skelindex);
1529 skel_delete(skelindex);
1530 e.skeleton_bones_index = e.modelindex;
1534 string to_execute_next_frame;
1535 void execute_next_frame()
1537 if(to_execute_next_frame)
1539 localcmd("\n", to_execute_next_frame, "\n");
1540 strfree(to_execute_next_frame);
1543 void queue_to_execute_next_frame(string s)
1545 if(to_execute_next_frame)
1547 s = strcat(s, "\n", to_execute_next_frame);
1549 strcpy(to_execute_next_frame, s);
1552 .float FindConnectedComponent_processing;
1553 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1555 entity queue_start, queue_end;
1557 // we build a queue of to-be-processed entities.
1558 // queue_start is the next entity to be checked for neighbors
1559 // queue_end is the last entity added
1561 if(e.FindConnectedComponent_processing)
1562 error("recursion or broken cleanup");
1564 // start with a 1-element queue
1565 queue_start = queue_end = e;
1566 queue_end.(fld) = NULL;
1567 queue_end.FindConnectedComponent_processing = 1;
1569 // for each queued item:
1570 for (; queue_start; queue_start = queue_start.(fld))
1572 // find all neighbors of queue_start
1574 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1576 if(t.FindConnectedComponent_processing)
1578 if(iscon(t, queue_start, pass))
1580 // it is connected? ADD IT. It will look for neighbors soon too.
1581 queue_end.(fld) = t;
1583 queue_end.(fld) = NULL;
1584 queue_end.FindConnectedComponent_processing = 1;
1590 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1591 queue_start.FindConnectedComponent_processing = 0;
1595 vector animfixfps(entity e, vector a, vector b)
1597 // multi-frame anim: keep as-is
1600 float dur = frameduration(e.modelindex, a.x);
1601 if (dur <= 0 && b.y)
1604 dur = frameduration(e.modelindex, a.x);
1614 Notification Announcer_PickNumber(int type, int num)
1623 case 10: return ANNCE_NUM_GAMESTART_10;
1624 case 9: return ANNCE_NUM_GAMESTART_9;
1625 case 8: return ANNCE_NUM_GAMESTART_8;
1626 case 7: return ANNCE_NUM_GAMESTART_7;
1627 case 6: return ANNCE_NUM_GAMESTART_6;
1628 case 5: return ANNCE_NUM_GAMESTART_5;
1629 case 4: return ANNCE_NUM_GAMESTART_4;
1630 case 3: return ANNCE_NUM_GAMESTART_3;
1631 case 2: return ANNCE_NUM_GAMESTART_2;
1632 case 1: return ANNCE_NUM_GAMESTART_1;
1640 case 10: return ANNCE_NUM_IDLE_10;
1641 case 9: return ANNCE_NUM_IDLE_9;
1642 case 8: return ANNCE_NUM_IDLE_8;
1643 case 7: return ANNCE_NUM_IDLE_7;
1644 case 6: return ANNCE_NUM_IDLE_6;
1645 case 5: return ANNCE_NUM_IDLE_5;
1646 case 4: return ANNCE_NUM_IDLE_4;
1647 case 3: return ANNCE_NUM_IDLE_3;
1648 case 2: return ANNCE_NUM_IDLE_2;
1649 case 1: return ANNCE_NUM_IDLE_1;
1657 case 10: return ANNCE_NUM_KILL_10;
1658 case 9: return ANNCE_NUM_KILL_9;
1659 case 8: return ANNCE_NUM_KILL_8;
1660 case 7: return ANNCE_NUM_KILL_7;
1661 case 6: return ANNCE_NUM_KILL_6;
1662 case 5: return ANNCE_NUM_KILL_5;
1663 case 4: return ANNCE_NUM_KILL_4;
1664 case 3: return ANNCE_NUM_KILL_3;
1665 case 2: return ANNCE_NUM_KILL_2;
1666 case 1: return ANNCE_NUM_KILL_1;
1674 case 10: return ANNCE_NUM_RESPAWN_10;
1675 case 9: return ANNCE_NUM_RESPAWN_9;
1676 case 8: return ANNCE_NUM_RESPAWN_8;
1677 case 7: return ANNCE_NUM_RESPAWN_7;
1678 case 6: return ANNCE_NUM_RESPAWN_6;
1679 case 5: return ANNCE_NUM_RESPAWN_5;
1680 case 4: return ANNCE_NUM_RESPAWN_4;
1681 case 3: return ANNCE_NUM_RESPAWN_3;
1682 case 2: return ANNCE_NUM_RESPAWN_2;
1683 case 1: return ANNCE_NUM_RESPAWN_1;
1687 case CNT_ROUNDSTART:
1691 case 10: return ANNCE_NUM_ROUNDSTART_10;
1692 case 9: return ANNCE_NUM_ROUNDSTART_9;
1693 case 8: return ANNCE_NUM_ROUNDSTART_8;
1694 case 7: return ANNCE_NUM_ROUNDSTART_7;
1695 case 6: return ANNCE_NUM_ROUNDSTART_6;
1696 case 5: return ANNCE_NUM_ROUNDSTART_5;
1697 case 4: return ANNCE_NUM_ROUNDSTART_4;
1698 case 3: return ANNCE_NUM_ROUNDSTART_3;
1699 case 2: return ANNCE_NUM_ROUNDSTART_2;
1700 case 1: return ANNCE_NUM_ROUNDSTART_1;
1708 case 10: return ANNCE_NUM_10;
1709 case 9: return ANNCE_NUM_9;
1710 case 8: return ANNCE_NUM_8;
1711 case 7: return ANNCE_NUM_7;
1712 case 6: return ANNCE_NUM_6;
1713 case 5: return ANNCE_NUM_5;
1714 case 4: return ANNCE_NUM_4;
1715 case 3: return ANNCE_NUM_3;
1716 case 2: return ANNCE_NUM_2;
1717 case 1: return ANNCE_NUM_1;
1726 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1728 switch(nativecontents)
1733 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1735 return DPCONTENTS_WATER;
1737 return DPCONTENTS_SLIME;
1739 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1741 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1746 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1748 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1749 return CONTENT_SOLID;
1750 if(supercontents & DPCONTENTS_SKY)
1752 if(supercontents & DPCONTENTS_LAVA)
1753 return CONTENT_LAVA;
1754 if(supercontents & DPCONTENTS_SLIME)
1755 return CONTENT_SLIME;
1756 if(supercontents & DPCONTENTS_WATER)
1757 return CONTENT_WATER;
1758 return CONTENT_EMPTY;