4 #include <client/mutators/_mod.qh>
5 #include <common/constants.qh>
6 #include <common/deathtypes/all.qh>
7 #include <common/gamemodes/_mod.qh>
8 #include <common/mapinfo.qh>
9 #include <common/notifications/all.qh>
10 #include <common/scores.qh>
13 #include <common/constants.qh>
14 #include <common/deathtypes/all.qh>
15 #include <common/gamemodes/_mod.qh>
16 #include <common/mapinfo.qh>
17 #include <common/notifications/all.qh>
18 #include <common/scores.qh>
19 #include <server/mutators/_mod.qh>
23 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
29 //nudge = 2 * cvar("collision_impactnudge"); // why not?
32 dir = normalize(v2 - v1);
34 pos = v1 + dir * nudge;
41 if(pos * dir >= v2 * dir)
49 tracebox(pos, mi, ma, v2, nomonsters, forent);
54 LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2));
55 LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos));
56 LOG_TRACE(" trace_endpos is ", vtos(trace_endpos));
57 LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos)));
60 stopentity = trace_ent;
64 // we started inside solid.
65 // then trace from endpos to pos
67 tracebox(t, mi, ma, pos, nomonsters, forent);
71 // t is still inside solid? bad
72 // force advance, then, and retry
73 pos = t + dir * nudge;
75 // but if we hit an entity, stop RIGHT before it
76 if(stopatentity && stopentity && stopentity != ignorestopatentity)
78 trace_ent = stopentity;
80 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
86 // we actually LEFT solid!
87 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
93 // pos is outside solid?!? but why?!? never mind, just return it.
95 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
101 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
103 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
112 Returns a point at least 12 units away from walls
113 (useful for explosion animations, although the blast is performed where it really happened)
117 vector findbetterlocation (vector org, float mindist)
119 vector vec = mindist * '1 0 0';
123 traceline (org, org + vec, true, NULL);
125 if (trace_fraction < 1)
127 vector loc = trace_endpos;
128 traceline (loc, loc + vec, true, NULL);
129 if (trace_fraction >= 1)
146 * Get "real" origin, in worldspace, even if ent is attached to something else.
148 vector real_origin(entity ent)
150 vector v = ((ent.absmin + ent.absmax) * 0.5);
151 entity e = ent.tag_entity;
155 v = v + ((e.absmin + e.absmax) * 0.5);
163 string wordwrap_buffer;
165 void wordwrap_buffer_put(string s)
167 wordwrap_buffer = strcat(wordwrap_buffer, s);
170 string wordwrap(string s, float l)
173 wordwrap_buffer = "";
174 wordwrap_cb(s, l, wordwrap_buffer_put);
176 wordwrap_buffer = "";
181 entity _wordwrap_buffer_sprint_ent;
182 void wordwrap_buffer_sprint(string s)
184 wordwrap_buffer = strcat(wordwrap_buffer, s);
187 sprint(_wordwrap_buffer_sprint_ent, wordwrap_buffer);
188 wordwrap_buffer = "";
192 void wordwrap_sprint(entity to, string s, float l)
194 wordwrap_buffer = "";
195 _wordwrap_buffer_sprint_ent = to;
196 wordwrap_cb(s, l, wordwrap_buffer_sprint);
197 _wordwrap_buffer_sprint_ent = NULL;
198 if(wordwrap_buffer != "")
199 sprint(to, strcat(wordwrap_buffer, "\n"));
200 wordwrap_buffer = "";
206 string draw_UseSkinFor(string pic)
208 if(substring(pic, 0, 1) == "/")
209 return substring(pic, 1, strlen(pic)-1);
211 return strcat(draw_currentSkin, "/", pic);
215 void wordwrap_cb(string s, float l, void(string) callback)
218 float lleft, i, j, wlen;
223 for (i = 0; i < len; ++i)
225 if (substring(s, i, 2) == "\\n")
231 else if (substring(s, i, 1) == "\n")
236 else if (substring(s, i, 1) == " ")
246 for (j = i+1; j < len; ++j)
247 // ^^ this skips over the first character of a word, which
248 // is ALWAYS part of the word
249 // this is safe since if i+1 == strlen(s), i will become
250 // strlen(s)-1 at the end of this block and the function
251 // will terminate. A space can't be the first character we
252 // read here, and neither can a \n be the start, since these
253 // two cases have been handled above.
255 c = substring(s, j, 1);
262 // we need to keep this tempstring alive even if substring is
263 // called repeatedly, so call strcat even though we're not
273 callback(substring(s, i, wlen));
281 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
311 string ScoreString(int pFlags, float pValue)
316 pValue = floor(pValue + 0.5); // round
318 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
320 else if(pFlags & SFL_RANK)
321 valstr = count_ordinal(pValue);
322 else if(pFlags & SFL_TIME)
323 valstr = TIME_ENCODED_TOSTRING(pValue);
325 valstr = ftos(pValue);
331 // compressed vector format:
332 // like MD3, just even shorter
333 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
334 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
335 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
336 // length = 2^(length_encoded/8) / 8
337 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
338 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
339 // the special value 0 indicates the zero vector
341 float lengthLogTable[128];
343 float invertLengthLog(float dist)
347 if(dist >= lengthLogTable[127])
349 if(dist <= lengthLogTable[0])
357 m = floor((l + r) / 2);
358 if(lengthLogTable[m] < dist)
364 // now: r is >=, l is <
365 float lerr = (dist - lengthLogTable[l]);
366 float rerr = (lengthLogTable[r] - dist);
372 vector decompressShortVector(int data)
377 float p = (data & 0xF000) / 0x1000;
378 float q = (data & 0x0F80) / 0x80;
379 int len = (data & 0x007F);
381 //print("\ndecompress: p ", ftos(p)); print("q ", ftos(q)); print("len ", ftos(len), "\n");
394 q = .19634954084936207740 * q;
395 p = .19634954084936207740 * p - 1.57079632679489661922;
396 out.x = cos(q) * cos(p);
397 out.y = sin(q) * cos(p);
401 //print("decompressed: ", vtos(out), "\n");
403 return out * lengthLogTable[len];
406 float compressShortVector(vector vec)
412 //print("compress: ", vtos(vec), "\n");
413 ang = vectoangles(vec);
417 if(ang.x < -90 && ang.x > +90)
418 error("BOGUS vectoangles");
419 //print("angles: ", vtos(ang), "\n");
421 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
430 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
431 len = invertLengthLog(vlen(vec));
433 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
435 return (p * 0x1000) + (y * 0x80) + len;
438 STATIC_INIT(compressShortVector)
441 float f = (2 ** (1/8));
443 for(i = 0; i < 128; ++i)
445 lengthLogTable[i] = l;
449 if(cvar("developer") > 0)
451 LOG_TRACE("Verifying vector compression table...");
452 for(i = 0x0F00; i < 0xFFFF; ++i)
453 if(i != compressShortVector(decompressShortVector(i)))
456 "BROKEN vector compression: %s -> %s -> %s",
458 vtos(decompressShortVector(i)),
459 ftos(compressShortVector(decompressShortVector(i)))
467 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
469 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
470 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
471 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
472 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
473 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
474 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
475 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
476 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
477 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
478 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
479 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
480 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
485 string fixPriorityList(string order, float from, float to, float subtract, float complete)
490 n = tokenize_console(order);
492 for(i = 0; i < n; ++i)
497 if(w >= from && w <= to)
498 neworder = strcat(neworder, ftos(w), " ");
502 if(w >= from && w <= to)
503 neworder = strcat(neworder, ftos(w), " ");
510 n = tokenize_console(neworder);
511 for(w = to; w >= from; --w)
513 int wflags = REGISTRY_GET(Weapons, w).spawnflags;
514 if(wflags & WEP_FLAG_SPECIALATTACK)
516 for(i = 0; i < n; ++i)
517 if(stof(argv(i)) == w)
519 if(i == n) // not found
520 neworder = strcat(neworder, ftos(w), " ");
524 return substring(neworder, 0, strlen(neworder) - 1);
527 string mapPriorityList(string order, string(string) mapfunc)
532 n = tokenize_console(order);
534 for(float i = 0; i < n; ++i)
535 neworder = strcat(neworder, mapfunc(argv(i)), " ");
537 return substring(neworder, 0, strlen(neworder) - 1);
540 string swapInPriorityList(string order, float i, float j)
542 float n = tokenize_console(order);
544 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
547 for(float w = 0; w < n; ++w)
550 s = strcat(s, argv(j), " ");
552 s = strcat(s, argv(i), " ");
554 s = strcat(s, argv(w), " ");
556 return substring(s, 0, strlen(s) - 1);
563 void get_mi_min_max(float mode)
568 if(!strcasecmp(substring(s, 0, 5), "maps/"))
569 s = substring(s, 5, strlen(s) - 5);
570 if(!strcasecmp(substring(s, strlen(s) - 4, 4), ".bsp"))
571 s = substring(s, 0, strlen(s) - 4);
572 strcpy(mi_shortname, s);
584 MapInfo_Get_ByName(mi_shortname, 0, NULL);
585 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
587 mi_min = MapInfo_Map_mins;
588 mi_max = MapInfo_Map_maxs;
596 tracebox('1 0 0' * mi.x,
597 '0 1 0' * mi.y + '0 0 1' * mi.z,
598 '0 1 0' * ma.y + '0 0 1' * ma.z,
602 if(!trace_startsolid)
603 mi_min.x = trace_endpos.x;
605 tracebox('0 1 0' * mi.y,
606 '1 0 0' * mi.x + '0 0 1' * mi.z,
607 '1 0 0' * ma.x + '0 0 1' * ma.z,
611 if(!trace_startsolid)
612 mi_min.y = trace_endpos.y;
614 tracebox('0 0 1' * mi.z,
615 '1 0 0' * mi.x + '0 1 0' * mi.y,
616 '1 0 0' * ma.x + '0 1 0' * ma.y,
620 if(!trace_startsolid)
621 mi_min.z = trace_endpos.z;
623 tracebox('1 0 0' * ma.x,
624 '0 1 0' * mi.y + '0 0 1' * mi.z,
625 '0 1 0' * ma.y + '0 0 1' * ma.z,
629 if(!trace_startsolid)
630 mi_max.x = trace_endpos.x;
632 tracebox('0 1 0' * ma.y,
633 '1 0 0' * mi.x + '0 0 1' * mi.z,
634 '1 0 0' * ma.x + '0 0 1' * ma.z,
638 if(!trace_startsolid)
639 mi_max.y = trace_endpos.y;
641 tracebox('0 0 1' * ma.z,
642 '1 0 0' * mi.x + '0 1 0' * mi.y,
643 '1 0 0' * ma.x + '0 1 0' * ma.y,
647 if(!trace_startsolid)
648 mi_max.z = trace_endpos.z;
653 void get_mi_min_max_texcoords(float mode)
657 get_mi_min_max(mode);
662 // extend mi_picmax to get a square aspect ratio
663 // center the map in that area
664 extend = mi_picmax - mi_picmin;
665 if(extend.y > extend.x)
667 mi_picmin.x -= (extend.y - extend.x) * 0.5;
668 mi_picmax.x += (extend.y - extend.x) * 0.5;
672 mi_picmin.y -= (extend.x - extend.y) * 0.5;
673 mi_picmax.y += (extend.x - extend.y) * 0.5;
676 // add another some percent
677 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
681 // calculate the texcoords
682 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
683 // first the two corners of the origin
684 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
685 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
686 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
687 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
688 // then the other corners
689 mi_pictexcoord1_x = mi_pictexcoord0_x;
690 mi_pictexcoord1_y = mi_pictexcoord2_y;
691 mi_pictexcoord3_x = mi_pictexcoord2_x;
692 mi_pictexcoord3_y = mi_pictexcoord0_y;
696 float cvar_settemp(string tmp_cvar, string tmp_value)
698 float created_saved_value;
700 created_saved_value = 0;
702 if (!(tmp_cvar || tmp_value))
704 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
708 if(!cvar_type(tmp_cvar))
710 LOG_INFOF("Error: cvar %s doesn't exist!", tmp_cvar);
714 IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
716 created_saved_value = -1; // skip creation
717 break; // no need to continue
720 if(created_saved_value != -1)
722 // creating a new entity to keep track of this cvar
723 entity e = new_pure(saved_cvar_value);
724 IL_PUSH(g_saved_cvars, e);
725 e.netname = strzone(tmp_cvar);
726 e.message = strzone(cvar_string(tmp_cvar));
727 created_saved_value = 1;
730 // update the cvar to the value given
731 cvar_set(tmp_cvar, tmp_value);
733 return created_saved_value;
736 int cvar_settemp_restore()
739 // FIXME this new-style loop fails!
741 FOREACH_ENTITY_CLASS("saved_cvar_value", true,
743 if(cvar_type(it.netname))
745 cvar_set(it.netname, it.message);
746 strunzone(it.netname);
747 strunzone(it.message);
752 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", it.netname);
757 while((e = find(e, classname, "saved_cvar_value")))
759 if(cvar_type(e.netname))
761 cvar_set(e.netname, e.message);
766 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", e.netname));
773 int skipIncompleteTag(string theText, float pos, int len)
777 if(substring(theText, pos - 1, 1) == "^")
779 if(isCaretEscaped(theText, pos - 1) || pos >= len)
782 int ch = str2chr(theText, pos);
783 if(ch >= '0' && ch <= '9')
784 return 1; // ^[0-9] color code found
786 tag_start = pos - 1; // ^x tag found
792 for(int i = 2; pos - i >= 0 && i <= 4; ++i)
794 if(substring(theText, pos - i, 2) == "^x")
796 tag_start = pos - i; // ^x tag found
804 if(tag_start + 5 < len)
805 if(IS_HEXDIGIT(substring(theText, tag_start + 2, 1)))
806 if(IS_HEXDIGIT(substring(theText, tag_start + 3, 1)))
807 if(IS_HEXDIGIT(substring(theText, tag_start + 4, 1)))
809 if(!isCaretEscaped(theText, tag_start))
810 return 5 - (pos - tag_start); // ^xRGB color code found
816 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
819 // The following function is SLOW.
820 // For your safety and for the protection of those around you...
821 // DO NOT CALL THIS AT HOME.
823 if(w(theText, theSize) <= maxWidth)
824 return strlen(theText); // yeah!
826 bool colors = (w("^7", theSize) == 0);
828 // binary search for right place to cut string
829 int len, left, right, middle;
831 right = len = strlen(theText);
835 middle = floor((left + right) / 2);
837 ofs = skipIncompleteTag(theText, middle, len);
838 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
843 while(left < right - 1);
848 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
851 // The following function is SLOW.
852 // For your safety and for the protection of those around you...
853 // DO NOT CALL THIS AT HOME.
855 if(w(theText) <= maxWidth)
856 return strlen(theText); // yeah!
858 bool colors = (w("^7") == 0);
860 // binary search for right place to cut string
861 int len, left, right, middle;
863 right = len = strlen(theText);
867 middle = floor((left + right) / 2);
869 ofs = skipIncompleteTag(theText, middle, len);
870 if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
875 while(left < right - 1);
880 string find_last_color_code(string s)
882 int start = strstrofs(s, "^", 0);
883 if (start == -1) // no caret found
885 int len = strlen(s)-1;
886 for(int i = len; i >= start; --i)
888 if(substring(s, i, 1) != "^")
892 while (i-carets >= start && substring(s, i-carets, 1) == "^")
895 // check if carets aren't all escaped
899 if(IS_DIGIT(substring(s, i+1, 1)))
900 return substring(s, i, 2);
903 if(substring(s, i+1, 1) == "x")
904 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
905 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
906 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
907 return substring(s, i, 5);
909 i -= carets; // this also skips one char before the carets
915 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
917 string s = getWrappedLine_remaining;
921 getWrappedLine_remaining = string_null;
922 return s; // the line has no size ANYWAY, nothing would be displayed.
925 int take_until = textLengthUpToWidth(s, w, theFontSize, tw);
926 if(take_until > 0 && take_until < strlen(s))
928 int last_word = take_until - 1;
929 while(last_word > 0 && substring(s, last_word, 1) != " ")
935 take_until = last_word;
939 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
940 if(getWrappedLine_remaining == "")
941 getWrappedLine_remaining = string_null;
942 else if (tw("^7", theFontSize) == 0)
943 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
944 return substring(s, 0, take_until);
948 getWrappedLine_remaining = string_null;
953 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
955 string s = getWrappedLine_remaining;
959 getWrappedLine_remaining = string_null;
960 return s; // the line has no size ANYWAY, nothing would be displayed.
963 int take_until = textLengthUpToLength(s, w, tw);
964 if(take_until > 0 && take_until < strlen(s))
966 int last_word = take_until - 1;
967 while(last_word > 0 && substring(s, last_word, 1) != " ")
973 take_until = last_word;
977 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
978 if(getWrappedLine_remaining == "")
979 getWrappedLine_remaining = string_null;
980 else if (tw("^7") == 0)
981 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
982 return substring(s, 0, take_until);
986 getWrappedLine_remaining = string_null;
991 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
993 if(tw(theText, theFontSize) <= maxWidth)
996 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
999 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1001 if(tw(theText) <= maxWidth)
1004 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1007 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1009 string subpattern, subpattern2, subpattern3, subpattern4;
1010 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1012 subpattern2 = ",teams,";
1014 subpattern2 = ",noteams,";
1016 subpattern3 = ",teamspawns,";
1018 subpattern3 = ",noteamspawns,";
1019 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1020 subpattern4 = ",race,";
1022 subpattern4 = string_null;
1024 if(substring(pattern, 0, 1) == "-")
1026 pattern = substring(pattern, 1, strlen(pattern) - 1);
1027 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1029 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1031 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1033 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1038 if(substring(pattern, 0, 1) == "+")
1039 pattern = substring(pattern, 1, strlen(pattern) - 1);
1040 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1041 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1042 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1046 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1053 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1057 // make origin and speed relative
1062 // now solve for ret, ret normalized:
1063 // eorg + t * evel == t * ret * spd
1064 // or, rather, solve for t:
1065 // |eorg + t * evel| == t * spd
1066 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1067 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1068 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1069 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1070 // q = (eorg * eorg) / (evel * evel - spd * spd)
1071 if(!solution.z) // no real solution
1074 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1075 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1076 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1077 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1078 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1079 // spd < |evel| * sin angle(evel, eorg)
1082 else if(solution.x > 0)
1084 // both solutions > 0: take the smaller one
1085 // happens if p < 0 and q > 0
1086 ret = normalize(eorg + solution.x * evel);
1088 else if(solution.y > 0)
1090 // one solution > 0: take the larger one
1091 // happens if q < 0 or q == 0 and p < 0
1092 ret = normalize(eorg + solution.y * evel);
1096 // no solution > 0: reject
1097 // happens if p > 0 and q >= 0
1098 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1099 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1104 // "Enemy is moving away from me at more than spd"
1108 // NOTE: we always got a solution if spd > |evel|
1110 if(newton_style == 2)
1111 ret = normalize(ret * spd + myvel);
1116 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1121 if(newton_style == 2)
1123 // true Newtonian projectiles with automatic aim adjustment
1125 // solve: |outspeed * mydir - myvel| = spd
1126 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1127 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1131 // myvel^2 - (mydir * myvel)^2 > spd^2
1132 // velocity without mydir component > spd
1133 // fire at smallest possible spd that works?
1134 // |(mydir * myvel) * myvel - myvel| = spd
1136 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1140 outspeed = solution.y; // the larger one
1143 //outspeed = 0; // slowest possible shot
1144 outspeed = solution.x; // the real part (that is, the average!)
1145 //dprint("impossible shot, adjusting\n");
1148 outspeed = bound(spd * mi, outspeed, spd * ma);
1149 return mydir * outspeed;
1153 return myvel + spd * mydir;
1156 float compressShotOrigin(vector v)
1158 float rx = rint(v.x * 2);
1159 float ry = rint(v.y * 4) + 128;
1160 float rz = rint(v.z * 4) + 128;
1161 if(rx > 255 || rx < 0)
1163 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1164 rx = bound(0, rx, 255);
1166 if(ry > 255 || ry < 0)
1168 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1169 ry = bound(0, ry, 255);
1171 if(rz > 255 || rz < 0)
1173 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1174 rz = bound(0, rz, 255);
1176 return rx * 0x10000 + ry * 0x100 + rz;
1178 vector decompressShotOrigin(int f)
1181 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1182 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1183 v.z = ((f & 0xFF) - 128) / 4;
1188 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1190 // NOTE: we'll always choose the SMALLER value...
1191 float healthdamage, armordamage, armorideal;
1192 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1195 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1196 armordamage = a + (h - 1); // damage we can take if we could use more armor
1197 armorideal = healthdamage * armorblock;
1199 if(armordamage < healthdamage)
1212 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1215 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1217 if (deathtype & HITTYPE_ARMORPIERCE)
1219 v.y = bound(0, damage * armorblock, a); // save
1220 v.x = bound(0, damage - v.y, damage); // take
1226 string getcurrentmod()
1230 m = cvar_string("fs_gamedir");
1231 n = tokenize_console(m);
1238 float matchacl(string acl, string str)
1245 t = car(acl); acl = cdr(acl);
1248 if(substring(t, 0, 1) == "-")
1251 t = substring(t, 1, strlen(t) - 1);
1253 else if(substring(t, 0, 1) == "+")
1254 t = substring(t, 1, strlen(t) - 1);
1256 if(substring(t, -1, 1) == "*")
1258 t = substring(t, 0, strlen(t) - 1);
1259 s = substring(str, 0, strlen(t));
1267 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1274 void write_String_To_File(int fh, string str, bool alsoprint)
1277 if (alsoprint) LOG_INFO(str);
1280 string get_model_datafilename(string m, float sk, string fil)
1285 m = "models/player/*_";
1287 m = strcat(m, ftos(sk));
1290 return strcat(m, ".", fil);
1293 float get_model_parameters(string m, float sk)
1295 get_model_parameters_modelname = string_null;
1296 get_model_parameters_modelskin = -1;
1297 get_model_parameters_name = string_null;
1298 get_model_parameters_species = -1;
1299 get_model_parameters_sex = string_null;
1300 get_model_parameters_weight = -1;
1301 get_model_parameters_age = -1;
1302 get_model_parameters_desc = string_null;
1303 get_model_parameters_bone_upperbody = string_null;
1304 get_model_parameters_bone_weapon = string_null;
1305 for(int i = 0; i < MAX_AIM_BONES; ++i)
1307 get_model_parameters_bone_aim[i] = string_null;
1308 get_model_parameters_bone_aimweight[i] = 0;
1310 get_model_parameters_fixbone = 0;
1311 get_model_parameters_hidden = false;
1314 MUTATOR_CALLHOOK(ClearModelParams);
1320 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1321 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1325 if(substring(m, -4, -1) != ".txt")
1327 if(substring(m, -6, 1) != "_")
1329 sk = stof(substring(m, -5, 1));
1330 m = substring(m, 0, -7);
1333 string fn = get_model_datafilename(m, sk, "txt");
1334 int fh = fopen(fn, FILE_READ);
1338 fn = get_model_datafilename(m, sk, "txt");
1339 fh = fopen(fn, FILE_READ);
1344 get_model_parameters_modelname = m;
1345 get_model_parameters_modelskin = sk;
1347 while((s = fgets(fh)))
1350 break; // next lines will be description
1354 get_model_parameters_name = s;
1358 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1359 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1360 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1361 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1362 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1363 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1364 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1368 if (s == "Male") s = _("Male");
1369 else if (s == "Female") s = _("Female");
1370 else if (s == "Undisclosed") s = _("Undisclosed");
1371 get_model_parameters_sex = s;
1374 get_model_parameters_weight = stof(s);
1376 get_model_parameters_age = stof(s);
1377 if(c == "description")
1378 get_model_parameters_description = s;
1379 if(c == "bone_upperbody")
1380 get_model_parameters_bone_upperbody = s;
1381 if(c == "bone_weapon")
1382 get_model_parameters_bone_weapon = s;
1384 MUTATOR_CALLHOOK(GetModelParams, c, s);
1386 for(int i = 0; i < MAX_AIM_BONES; ++i)
1387 if(c == strcat("bone_aim", ftos(i)))
1389 get_model_parameters_bone_aimweight[i] = stof(car(s));
1390 get_model_parameters_bone_aim[i] = cdr(s);
1393 get_model_parameters_fixbone = stof(s);
1395 get_model_parameters_hidden = stob(s);
1398 while((s = fgets(fh)))
1400 if(get_model_parameters_desc)
1401 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1403 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1411 string translate_key(string key)
1413 if (prvm_language == "en") return key;
1415 if (substring(key, 0, 1) == "<")
1417 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1418 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1423 case "TAB": return _("TAB");
1424 case "ENTER": return _("ENTER");
1425 case "ESCAPE": return _("ESCAPE");
1426 case "SPACE": return _("SPACE");
1428 case "BACKSPACE": return _("BACKSPACE");
1429 case "UPARROW": return _("UPARROW");
1430 case "DOWNARROW": return _("DOWNARROW");
1431 case "LEFTARROW": return _("LEFTARROW");
1432 case "RIGHTARROW": return _("RIGHTARROW");
1434 case "ALT": return _("ALT");
1435 case "CTRL": return _("CTRL");
1436 case "SHIFT": return _("SHIFT");
1438 case "INS": return _("INS");
1439 case "DEL": return _("DEL");
1440 case "PGDN": return _("PGDN");
1441 case "PGUP": return _("PGUP");
1442 case "HOME": return _("HOME");
1443 case "END": return _("END");
1445 case "PAUSE": return _("PAUSE");
1447 case "NUMLOCK": return _("NUMLOCK");
1448 case "CAPSLOCK": return _("CAPSLOCK");
1449 case "SCROLLOCK": return _("SCROLLOCK");
1451 case "SEMICOLON": return _("SEMICOLON");
1452 case "TILDE": return _("TILDE");
1453 case "BACKQUOTE": return _("BACKQUOTE");
1454 case "QUOTE": return _("QUOTE");
1455 case "APOSTROPHE": return _("APOSTROPHE");
1456 case "BACKSLASH": return _("BACKSLASH");
1459 if (substring(key, 0, 1) == "F")
1461 string subkey = substring(key, 1, -1);
1462 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1464 return sprintf(_("F%d"), stof(subkey));
1466 // continue in case there is another key name starting with F
1469 if (substring(key, 0, 3) == "KP_")
1471 string subkey = substring(key, 3, -1);
1472 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1474 return sprintf(_("KP_%d"), stof(subkey));
1479 case "INS": return sprintf(_("KP_%s"), _("INS"));
1480 case "END": return sprintf(_("KP_%s"), _("END"));
1481 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1482 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1483 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1484 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1485 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1486 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1487 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1488 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1489 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1490 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1491 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1492 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1493 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1494 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1495 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1496 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1497 default: return key;
1501 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1503 if (substring(key, 0, 5) == "MOUSE")
1504 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1506 if (key == "MWHEELUP") return _("MWHEELUP");
1507 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1509 if (substring(key, 0,3) == "JOY")
1510 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1512 if (substring(key, 0,3) == "AUX")
1513 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1515 if (substring(key, 0, 4) == "X360_")
1517 string subkey = substring(key, 4, -1);
1520 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1521 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1522 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1523 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1524 case "START": return sprintf(_("X360_%s"), _("START"));
1525 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1526 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1527 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1528 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1529 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1530 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1531 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1532 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1533 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1534 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1535 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1536 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1537 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1538 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1539 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1540 default: return key;
1544 if (substring(key, 0, 4) == "JOY_")
1546 string subkey = substring(key, 4, -1);
1549 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1550 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1551 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1552 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1553 default: return key;
1557 if (substring(key, 0, 8) == "MIDINOTE")
1558 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1563 // x-encoding (encoding as zero length invisible string)
1564 const string XENCODE_2 = "xX";
1565 const string XENCODE_22 = "0123456789abcdefABCDEF";
1566 string xencode(int f)
1569 d = f % 22; f = floor(f / 22);
1570 c = f % 22; f = floor(f / 22);
1571 b = f % 22; f = floor(f / 22);
1572 a = f % 2; // f = floor(f / 2);
1575 substring(XENCODE_2, a, 1),
1576 substring(XENCODE_22, b, 1),
1577 substring(XENCODE_22, c, 1),
1578 substring(XENCODE_22, d, 1)
1581 float xdecode(string s)
1584 if(substring(s, 0, 1) != "^")
1588 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1589 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1590 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1591 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1592 if(a < 0 || b < 0 || c < 0 || d < 0)
1594 return ((a * 22 + b) * 22 + c) * 22 + d;
1598 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1600 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1603 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1606 float shutdown_running;
1611 void CSQC_Shutdown()
1617 if(shutdown_running)
1619 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1623 shutdown_running = 1;
1627 cvar_settemp_restore(); // this must be done LAST, but in any case
1631 .float skeleton_bones_index;
1632 void Skeleton_SetBones(entity e)
1634 // set skeleton_bones to the total number of bones on the model
1635 if(e.skeleton_bones_index == e.modelindex)
1636 return; // same model, nothing to update
1639 skelindex = skel_create(e.modelindex);
1640 e.skeleton_bones = skel_get_numbones(skelindex);
1641 skel_delete(skelindex);
1642 e.skeleton_bones_index = e.modelindex;
1646 string to_execute_next_frame;
1647 void execute_next_frame()
1649 if(to_execute_next_frame)
1651 localcmd("\n", to_execute_next_frame, "\n");
1652 strfree(to_execute_next_frame);
1655 void queue_to_execute_next_frame(string s)
1657 if(to_execute_next_frame)
1659 s = strcat(s, "\n", to_execute_next_frame);
1661 strcpy(to_execute_next_frame, s);
1664 .float FindConnectedComponent_processing;
1665 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1667 entity queue_start, queue_end;
1669 // we build a queue of to-be-processed entities.
1670 // queue_start is the next entity to be checked for neighbors
1671 // queue_end is the last entity added
1673 if(e.FindConnectedComponent_processing)
1674 error("recursion or broken cleanup");
1676 // start with a 1-element queue
1677 queue_start = queue_end = e;
1678 queue_end.(fld) = NULL;
1679 queue_end.FindConnectedComponent_processing = 1;
1681 // for each queued item:
1682 for (; queue_start; queue_start = queue_start.(fld))
1684 // find all neighbors of queue_start
1686 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1688 if(t.FindConnectedComponent_processing)
1690 if(iscon(t, queue_start, pass))
1692 // it is connected? ADD IT. It will look for neighbors soon too.
1693 queue_end.(fld) = t;
1695 queue_end.(fld) = NULL;
1696 queue_end.FindConnectedComponent_processing = 1;
1702 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1703 queue_start.FindConnectedComponent_processing = 0;
1707 vector animfixfps(entity e, vector a, vector b)
1709 // multi-frame anim: keep as-is
1712 float dur = frameduration(e.modelindex, a.x);
1713 if (dur <= 0 && b.y)
1716 dur = frameduration(e.modelindex, a.x);
1726 Notification Announcer_PickNumber(int type, int num)
1735 case 10: return ANNCE_NUM_GAMESTART_10;
1736 case 9: return ANNCE_NUM_GAMESTART_9;
1737 case 8: return ANNCE_NUM_GAMESTART_8;
1738 case 7: return ANNCE_NUM_GAMESTART_7;
1739 case 6: return ANNCE_NUM_GAMESTART_6;
1740 case 5: return ANNCE_NUM_GAMESTART_5;
1741 case 4: return ANNCE_NUM_GAMESTART_4;
1742 case 3: return ANNCE_NUM_GAMESTART_3;
1743 case 2: return ANNCE_NUM_GAMESTART_2;
1744 case 1: return ANNCE_NUM_GAMESTART_1;
1752 case 10: return ANNCE_NUM_IDLE_10;
1753 case 9: return ANNCE_NUM_IDLE_9;
1754 case 8: return ANNCE_NUM_IDLE_8;
1755 case 7: return ANNCE_NUM_IDLE_7;
1756 case 6: return ANNCE_NUM_IDLE_6;
1757 case 5: return ANNCE_NUM_IDLE_5;
1758 case 4: return ANNCE_NUM_IDLE_4;
1759 case 3: return ANNCE_NUM_IDLE_3;
1760 case 2: return ANNCE_NUM_IDLE_2;
1761 case 1: return ANNCE_NUM_IDLE_1;
1769 case 10: return ANNCE_NUM_KILL_10;
1770 case 9: return ANNCE_NUM_KILL_9;
1771 case 8: return ANNCE_NUM_KILL_8;
1772 case 7: return ANNCE_NUM_KILL_7;
1773 case 6: return ANNCE_NUM_KILL_6;
1774 case 5: return ANNCE_NUM_KILL_5;
1775 case 4: return ANNCE_NUM_KILL_4;
1776 case 3: return ANNCE_NUM_KILL_3;
1777 case 2: return ANNCE_NUM_KILL_2;
1778 case 1: return ANNCE_NUM_KILL_1;
1786 case 10: return ANNCE_NUM_RESPAWN_10;
1787 case 9: return ANNCE_NUM_RESPAWN_9;
1788 case 8: return ANNCE_NUM_RESPAWN_8;
1789 case 7: return ANNCE_NUM_RESPAWN_7;
1790 case 6: return ANNCE_NUM_RESPAWN_6;
1791 case 5: return ANNCE_NUM_RESPAWN_5;
1792 case 4: return ANNCE_NUM_RESPAWN_4;
1793 case 3: return ANNCE_NUM_RESPAWN_3;
1794 case 2: return ANNCE_NUM_RESPAWN_2;
1795 case 1: return ANNCE_NUM_RESPAWN_1;
1799 case CNT_ROUNDSTART:
1803 case 10: return ANNCE_NUM_ROUNDSTART_10;
1804 case 9: return ANNCE_NUM_ROUNDSTART_9;
1805 case 8: return ANNCE_NUM_ROUNDSTART_8;
1806 case 7: return ANNCE_NUM_ROUNDSTART_7;
1807 case 6: return ANNCE_NUM_ROUNDSTART_6;
1808 case 5: return ANNCE_NUM_ROUNDSTART_5;
1809 case 4: return ANNCE_NUM_ROUNDSTART_4;
1810 case 3: return ANNCE_NUM_ROUNDSTART_3;
1811 case 2: return ANNCE_NUM_ROUNDSTART_2;
1812 case 1: return ANNCE_NUM_ROUNDSTART_1;
1820 case 10: return ANNCE_NUM_10;
1821 case 9: return ANNCE_NUM_9;
1822 case 8: return ANNCE_NUM_8;
1823 case 7: return ANNCE_NUM_7;
1824 case 6: return ANNCE_NUM_6;
1825 case 5: return ANNCE_NUM_5;
1826 case 4: return ANNCE_NUM_4;
1827 case 3: return ANNCE_NUM_3;
1828 case 2: return ANNCE_NUM_2;
1829 case 1: return ANNCE_NUM_1;
1838 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1840 switch(nativecontents)
1845 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1847 return DPCONTENTS_WATER;
1849 return DPCONTENTS_SLIME;
1851 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1853 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1858 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1860 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1861 return CONTENT_SOLID;
1862 if(supercontents & DPCONTENTS_SKY)
1864 if(supercontents & DPCONTENTS_LAVA)
1865 return CONTENT_LAVA;
1866 if(supercontents & DPCONTENTS_SLIME)
1867 return CONTENT_SLIME;
1868 if(supercontents & DPCONTENTS_WATER)
1869 return CONTENT_WATER;
1870 return CONTENT_EMPTY;
1875 void attach_sameorigin(entity e, entity to, string tag)
1877 vector org, t_forward, t_left, t_up, e_forward, e_up;
1880 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1881 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1882 t_forward = v_forward * tagscale;
1883 t_left = v_right * -tagscale;
1884 t_up = v_up * tagscale;
1886 e.origin_x = org * t_forward;
1887 e.origin_y = org * t_left;
1888 e.origin_z = org * t_up;
1890 // current forward and up directions
1891 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1892 e.angles = AnglesTransform_FromVAngles(e.angles);
1894 e.angles = AnglesTransform_FromAngles(e.angles);
1895 fixedmakevectors(e.angles);
1897 // untransform forward, up!
1898 e_forward.x = v_forward * t_forward;
1899 e_forward.y = v_forward * t_left;
1900 e_forward.z = v_forward * t_up;
1901 e_up.x = v_up * t_forward;
1902 e_up.y = v_up * t_left;
1903 e_up.z = v_up * t_up;
1905 e.angles = fixedvectoangles2(e_forward, e_up);
1906 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1907 e.angles = AnglesTransform_ToVAngles(e.angles);
1909 e.angles = AnglesTransform_ToAngles(e.angles);
1911 setattachment(e, to, tag);
1912 setorigin(e, e.origin);
1915 void detach_sameorigin(entity e)
1918 org = gettaginfo(e, 0);
1919 e.angles = fixedvectoangles2(v_forward, v_up);
1920 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1921 e.angles = AnglesTransform_ToVAngles(e.angles);
1923 e.angles = AnglesTransform_ToAngles(e.angles);
1925 setattachment(e, NULL, "");
1926 setorigin(e, e.origin);
1929 void follow_sameorigin(entity e, entity to)
1931 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1932 e.aiment = to; // make the hole follow bmodel
1933 e.punchangle = to.angles; // the original angles of bmodel
1934 e.view_ofs = e.origin - to.origin; // relative origin
1935 e.v_angle = e.angles - to.angles; // relative angles
1939 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1940 void unfollow_sameorigin(entity e)
1942 set_movetype(e, MOVETYPE_NONE);
1946 .string aiment_classname;
1947 .float aiment_deadflag;
1948 void SetMovetypeFollow(entity ent, entity e)
1950 // FIXME this may not be warpzone aware
1951 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1952 ent.solid = SOLID_NOT; // MOVETYPE_FOLLOW is always non-solid - this means this cannot be teleported by warpzones any more! Instead, we must notice when our owner gets teleported.
1953 ent.aiment = e; // make the hole follow bmodel
1954 ent.punchangle = e.angles; // the original angles of bmodel
1955 ent.view_ofs = ent.origin - e.origin; // relative origin
1956 ent.v_angle = ent.angles - e.angles; // relative angles
1957 ent.aiment_classname = strzone(e.classname);
1958 ent.aiment_deadflag = e.deadflag;
1960 void UnsetMovetypeFollow(entity ent)
1962 set_movetype(ent, MOVETYPE_FLY);
1963 PROJECTILE_MAKETRIGGER(ent);
1966 float LostMovetypeFollow(entity ent)
1969 if(ent.move_movetype != MOVETYPE_FOLLOW)
1975 if(ent.aiment.classname != ent.aiment_classname)
1977 if(ent.aiment.deadflag != ent.aiment_deadflag)
1985 // decolorizes and team colors the player name when needed
1986 string playername(string thename, int teamid, bool team_colorize)
1989 bool do_colorize = (teamplay && team_colorize);
1991 if(do_colorize && !intermission_running)
1996 string t = Team_ColorCode(teamid);
1997 return strcat(t, strdecolorize(thename));
2003 float trace_hits_box_a0, trace_hits_box_a1;
2005 float trace_hits_box_1d(float end, float thmi, float thma)
2009 // just check if x is in range
2017 // do the trace with respect to x
2018 // 0 -> end has to stay in thmi -> thma
2019 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2020 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2021 if (trace_hits_box_a0 > trace_hits_box_a1)
2027 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2032 // now it is a trace from 0 to end
2034 trace_hits_box_a0 = 0;
2035 trace_hits_box_a1 = 1;
2037 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2039 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2041 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2047 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2049 return trace_hits_box(start, end, thmi - ma, thma - mi);
2054 float cvar_or(string cv, float v)
2056 string s = cvar_string(cv);
2063 // NOTE base is the central value
2064 // freq: circle frequency, = 2*pi*frequency in hertz
2066 // -1 start from the lower value
2067 // 0 start from the base value
2068 // 1 start from the higher value
2070 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2073 // RMS = sqrt(base^2 + 0.5 * range^2)
2075 // base = sqrt(RMS^2 - 0.5 * range^2)
2078 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2082 float blink(float base, float range, float freq)
2084 return blink_synced(base, range, freq, 0, 0);