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 bool isCaretEscaped(string theText, float pos)
776 while(pos - i >= 1 && substring(theText, pos - i - 1, 1) == "^")
781 int skipIncompleteTag(string theText, float pos, int len)
785 if(substring(theText, pos - 1, 1) == "^")
787 if(isCaretEscaped(theText, pos - 1) || pos >= len)
790 int ch = str2chr(theText, pos);
791 if(ch >= '0' && ch <= '9')
792 return 1; // ^[0-9] color code found
794 tag_start = pos - 1; // ^x tag found
800 for(int i = 2; pos - i >= 0 && i <= 4; ++i)
802 if(substring(theText, pos - i, 2) == "^x")
804 tag_start = pos - i; // ^x tag found
812 if(tag_start + 5 < len)
813 if(IS_HEXDIGIT(substring(theText, tag_start + 2, 1)))
814 if(IS_HEXDIGIT(substring(theText, tag_start + 3, 1)))
815 if(IS_HEXDIGIT(substring(theText, tag_start + 4, 1)))
817 if(!isCaretEscaped(theText, tag_start))
818 return 5 - (pos - tag_start); // ^xRGB color code found
824 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
827 // The following function is SLOW.
828 // For your safety and for the protection of those around you...
829 // DO NOT CALL THIS AT HOME.
831 if(w(theText, theSize) <= maxWidth)
832 return strlen(theText); // yeah!
834 bool colors = (w("^7", theSize) == 0);
836 // binary search for right place to cut string
837 int len, left, right, middle;
839 right = len = strlen(theText);
843 middle = floor((left + right) / 2);
845 ofs = skipIncompleteTag(theText, middle, len);
846 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
851 while(left < right - 1);
856 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
859 // The following function is SLOW.
860 // For your safety and for the protection of those around you...
861 // DO NOT CALL THIS AT HOME.
863 if(w(theText) <= maxWidth)
864 return strlen(theText); // yeah!
866 bool colors = (w("^7") == 0);
868 // binary search for right place to cut string
869 int len, left, right, middle;
871 right = len = strlen(theText);
875 middle = floor((left + right) / 2);
877 ofs = skipIncompleteTag(theText, middle, len);
878 if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
883 while(left < right - 1);
888 string find_last_color_code(string s)
890 int start = strstrofs(s, "^", 0);
891 if (start == -1) // no caret found
893 int len = strlen(s)-1;
894 for(int i = len; i >= start; --i)
896 if(substring(s, i, 1) != "^")
900 while (i-carets >= start && substring(s, i-carets, 1) == "^")
903 // check if carets aren't all escaped
907 if(IS_DIGIT(substring(s, i+1, 1)))
908 return substring(s, i, 2);
911 if(substring(s, i+1, 1) == "x")
912 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
913 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
914 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
915 return substring(s, i, 5);
917 i -= carets; // this also skips one char before the carets
923 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
929 s = getWrappedLine_remaining;
933 getWrappedLine_remaining = string_null;
934 return s; // the line has no size ANYWAY, nothing would be displayed.
937 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
938 if(cantake > 0 && cantake < strlen(s))
941 while(take > 0 && substring(s, take, 1) != " ")
951 getWrappedLine_remaining = substring(s, cantake + skip, strlen(s) - cantake);
952 if(getWrappedLine_remaining == "")
953 getWrappedLine_remaining = string_null;
954 else if (tw("^7", theFontSize) == 0)
955 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
956 return substring(s, 0, cantake);
960 getWrappedLine_remaining = string_null;
965 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
971 s = getWrappedLine_remaining;
975 getWrappedLine_remaining = string_null;
976 return s; // the line has no size ANYWAY, nothing would be displayed.
979 cantake = textLengthUpToLength(s, w, tw);
980 if(cantake > 0 && cantake < strlen(s))
983 while(take > 0 && substring(s, take, 1) != " ")
993 getWrappedLine_remaining = substring(s, cantake + skip, strlen(s) - cantake);
994 if(getWrappedLine_remaining == "")
995 getWrappedLine_remaining = string_null;
996 else if (tw("^7") == 0)
997 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
998 return substring(s, 0, cantake);
1002 getWrappedLine_remaining = string_null;
1007 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1009 if(tw(theText, theFontSize) <= maxWidth)
1012 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1015 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1017 if(tw(theText) <= maxWidth)
1020 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1023 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1025 string subpattern, subpattern2, subpattern3, subpattern4;
1026 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1028 subpattern2 = ",teams,";
1030 subpattern2 = ",noteams,";
1032 subpattern3 = ",teamspawns,";
1034 subpattern3 = ",noteamspawns,";
1035 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1036 subpattern4 = ",race,";
1038 subpattern4 = string_null;
1040 if(substring(pattern, 0, 1) == "-")
1042 pattern = substring(pattern, 1, strlen(pattern) - 1);
1043 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1045 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1047 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1049 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1054 if(substring(pattern, 0, 1) == "+")
1055 pattern = substring(pattern, 1, strlen(pattern) - 1);
1056 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1057 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1058 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1062 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1069 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1073 // make origin and speed relative
1078 // now solve for ret, ret normalized:
1079 // eorg + t * evel == t * ret * spd
1080 // or, rather, solve for t:
1081 // |eorg + t * evel| == t * spd
1082 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1083 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1084 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1085 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1086 // q = (eorg * eorg) / (evel * evel - spd * spd)
1087 if(!solution.z) // no real solution
1090 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1091 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1092 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1093 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1094 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1095 // spd < |evel| * sin angle(evel, eorg)
1098 else if(solution.x > 0)
1100 // both solutions > 0: take the smaller one
1101 // happens if p < 0 and q > 0
1102 ret = normalize(eorg + solution.x * evel);
1104 else if(solution.y > 0)
1106 // one solution > 0: take the larger one
1107 // happens if q < 0 or q == 0 and p < 0
1108 ret = normalize(eorg + solution.y * evel);
1112 // no solution > 0: reject
1113 // happens if p > 0 and q >= 0
1114 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1115 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1120 // "Enemy is moving away from me at more than spd"
1124 // NOTE: we always got a solution if spd > |evel|
1126 if(newton_style == 2)
1127 ret = normalize(ret * spd + myvel);
1132 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1137 if(newton_style == 2)
1139 // true Newtonian projectiles with automatic aim adjustment
1141 // solve: |outspeed * mydir - myvel| = spd
1142 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1143 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1147 // myvel^2 - (mydir * myvel)^2 > spd^2
1148 // velocity without mydir component > spd
1149 // fire at smallest possible spd that works?
1150 // |(mydir * myvel) * myvel - myvel| = spd
1152 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1156 outspeed = solution.y; // the larger one
1159 //outspeed = 0; // slowest possible shot
1160 outspeed = solution.x; // the real part (that is, the average!)
1161 //dprint("impossible shot, adjusting\n");
1164 outspeed = bound(spd * mi, outspeed, spd * ma);
1165 return mydir * outspeed;
1169 return myvel + spd * mydir;
1172 float compressShotOrigin(vector v)
1174 float rx = rint(v.x * 2);
1175 float ry = rint(v.y * 4) + 128;
1176 float rz = rint(v.z * 4) + 128;
1177 if(rx > 255 || rx < 0)
1179 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1180 rx = bound(0, rx, 255);
1182 if(ry > 255 || ry < 0)
1184 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1185 ry = bound(0, ry, 255);
1187 if(rz > 255 || rz < 0)
1189 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1190 rz = bound(0, rz, 255);
1192 return rx * 0x10000 + ry * 0x100 + rz;
1194 vector decompressShotOrigin(int f)
1197 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1198 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1199 v.z = ((f & 0xFF) - 128) / 4;
1204 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1206 // NOTE: we'll always choose the SMALLER value...
1207 float healthdamage, armordamage, armorideal;
1208 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1211 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1212 armordamage = a + (h - 1); // damage we can take if we could use more armor
1213 armorideal = healthdamage * armorblock;
1215 if(armordamage < healthdamage)
1228 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1231 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1233 if (deathtype & HITTYPE_ARMORPIERCE)
1235 v.y = bound(0, damage * armorblock, a); // save
1236 v.x = bound(0, damage - v.y, damage); // take
1242 string getcurrentmod()
1246 m = cvar_string("fs_gamedir");
1247 n = tokenize_console(m);
1254 float matchacl(string acl, string str)
1261 t = car(acl); acl = cdr(acl);
1264 if(substring(t, 0, 1) == "-")
1267 t = substring(t, 1, strlen(t) - 1);
1269 else if(substring(t, 0, 1) == "+")
1270 t = substring(t, 1, strlen(t) - 1);
1272 if(substring(t, -1, 1) == "*")
1274 t = substring(t, 0, strlen(t) - 1);
1275 s = substring(str, 0, strlen(t));
1283 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1290 void write_String_To_File(int fh, string str, bool alsoprint)
1293 if (alsoprint) LOG_INFO(str);
1296 string get_model_datafilename(string m, float sk, string fil)
1301 m = "models/player/*_";
1303 m = strcat(m, ftos(sk));
1306 return strcat(m, ".", fil);
1309 float get_model_parameters(string m, float sk)
1311 get_model_parameters_modelname = string_null;
1312 get_model_parameters_modelskin = -1;
1313 get_model_parameters_name = string_null;
1314 get_model_parameters_species = -1;
1315 get_model_parameters_sex = string_null;
1316 get_model_parameters_weight = -1;
1317 get_model_parameters_age = -1;
1318 get_model_parameters_desc = string_null;
1319 get_model_parameters_bone_upperbody = string_null;
1320 get_model_parameters_bone_weapon = string_null;
1321 for(int i = 0; i < MAX_AIM_BONES; ++i)
1323 get_model_parameters_bone_aim[i] = string_null;
1324 get_model_parameters_bone_aimweight[i] = 0;
1326 get_model_parameters_fixbone = 0;
1327 get_model_parameters_hidden = false;
1330 MUTATOR_CALLHOOK(ClearModelParams);
1336 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1337 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1341 if(substring(m, -4, -1) != ".txt")
1343 if(substring(m, -6, 1) != "_")
1345 sk = stof(substring(m, -5, 1));
1346 m = substring(m, 0, -7);
1349 string fn = get_model_datafilename(m, sk, "txt");
1350 int fh = fopen(fn, FILE_READ);
1354 fn = get_model_datafilename(m, sk, "txt");
1355 fh = fopen(fn, FILE_READ);
1360 get_model_parameters_modelname = m;
1361 get_model_parameters_modelskin = sk;
1363 while((s = fgets(fh)))
1366 break; // next lines will be description
1370 get_model_parameters_name = s;
1374 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1375 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1376 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1377 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1378 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1379 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1380 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1384 if (s == "Male") s = _("Male");
1385 else if (s == "Female") s = _("Female");
1386 else if (s == "Undisclosed") s = _("Undisclosed");
1387 get_model_parameters_sex = s;
1390 get_model_parameters_weight = stof(s);
1392 get_model_parameters_age = stof(s);
1393 if(c == "description")
1394 get_model_parameters_description = s;
1395 if(c == "bone_upperbody")
1396 get_model_parameters_bone_upperbody = s;
1397 if(c == "bone_weapon")
1398 get_model_parameters_bone_weapon = s;
1400 MUTATOR_CALLHOOK(GetModelParams, c, s);
1402 for(int i = 0; i < MAX_AIM_BONES; ++i)
1403 if(c == strcat("bone_aim", ftos(i)))
1405 get_model_parameters_bone_aimweight[i] = stof(car(s));
1406 get_model_parameters_bone_aim[i] = cdr(s);
1409 get_model_parameters_fixbone = stof(s);
1411 get_model_parameters_hidden = stob(s);
1414 while((s = fgets(fh)))
1416 if(get_model_parameters_desc)
1417 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1419 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1427 string translate_key(string key)
1429 if (prvm_language == "en") return key;
1431 if (substring(key, 0, 1) == "<")
1433 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1434 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1439 case "TAB": return _("TAB");
1440 case "ENTER": return _("ENTER");
1441 case "ESCAPE": return _("ESCAPE");
1442 case "SPACE": return _("SPACE");
1444 case "BACKSPACE": return _("BACKSPACE");
1445 case "UPARROW": return _("UPARROW");
1446 case "DOWNARROW": return _("DOWNARROW");
1447 case "LEFTARROW": return _("LEFTARROW");
1448 case "RIGHTARROW": return _("RIGHTARROW");
1450 case "ALT": return _("ALT");
1451 case "CTRL": return _("CTRL");
1452 case "SHIFT": return _("SHIFT");
1454 case "INS": return _("INS");
1455 case "DEL": return _("DEL");
1456 case "PGDN": return _("PGDN");
1457 case "PGUP": return _("PGUP");
1458 case "HOME": return _("HOME");
1459 case "END": return _("END");
1461 case "PAUSE": return _("PAUSE");
1463 case "NUMLOCK": return _("NUMLOCK");
1464 case "CAPSLOCK": return _("CAPSLOCK");
1465 case "SCROLLOCK": return _("SCROLLOCK");
1467 case "SEMICOLON": return _("SEMICOLON");
1468 case "TILDE": return _("TILDE");
1469 case "BACKQUOTE": return _("BACKQUOTE");
1470 case "QUOTE": return _("QUOTE");
1471 case "APOSTROPHE": return _("APOSTROPHE");
1472 case "BACKSLASH": return _("BACKSLASH");
1475 if (substring(key, 0, 1) == "F")
1477 string subkey = substring(key, 1, -1);
1478 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1480 return sprintf(_("F%d"), stof(subkey));
1482 // continue in case there is another key name starting with F
1485 if (substring(key, 0, 3) == "KP_")
1487 string subkey = substring(key, 3, -1);
1488 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1490 return sprintf(_("KP_%d"), stof(subkey));
1495 case "INS": return sprintf(_("KP_%s"), _("INS"));
1496 case "END": return sprintf(_("KP_%s"), _("END"));
1497 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1498 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1499 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1500 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1501 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1502 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1503 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1504 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1505 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1506 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1507 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1508 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1509 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1510 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1511 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1512 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1513 default: return key;
1517 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1519 if (substring(key, 0, 5) == "MOUSE")
1520 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1522 if (key == "MWHEELUP") return _("MWHEELUP");
1523 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1525 if (substring(key, 0,3) == "JOY")
1526 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1528 if (substring(key, 0,3) == "AUX")
1529 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1531 if (substring(key, 0, 4) == "X360_")
1533 string subkey = substring(key, 4, -1);
1536 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1537 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1538 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1539 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1540 case "START": return sprintf(_("X360_%s"), _("START"));
1541 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1542 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1543 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1544 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1545 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1546 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1547 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1548 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1549 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1550 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1551 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1552 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1553 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1554 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1555 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1556 default: return key;
1560 if (substring(key, 0, 4) == "JOY_")
1562 string subkey = substring(key, 4, -1);
1565 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1566 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1567 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1568 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1569 default: return key;
1573 if (substring(key, 0, 8) == "MIDINOTE")
1574 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1579 // x-encoding (encoding as zero length invisible string)
1580 const string XENCODE_2 = "xX";
1581 const string XENCODE_22 = "0123456789abcdefABCDEF";
1582 string xencode(int f)
1585 d = f % 22; f = floor(f / 22);
1586 c = f % 22; f = floor(f / 22);
1587 b = f % 22; f = floor(f / 22);
1588 a = f % 2; // f = floor(f / 2);
1591 substring(XENCODE_2, a, 1),
1592 substring(XENCODE_22, b, 1),
1593 substring(XENCODE_22, c, 1),
1594 substring(XENCODE_22, d, 1)
1597 float xdecode(string s)
1600 if(substring(s, 0, 1) != "^")
1604 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1605 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1606 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1607 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1608 if(a < 0 || b < 0 || c < 0 || d < 0)
1610 return ((a * 22 + b) * 22 + c) * 22 + d;
1614 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1616 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1619 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1622 float shutdown_running;
1627 void CSQC_Shutdown()
1633 if(shutdown_running)
1635 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1639 shutdown_running = 1;
1643 cvar_settemp_restore(); // this must be done LAST, but in any case
1647 .float skeleton_bones_index;
1648 void Skeleton_SetBones(entity e)
1650 // set skeleton_bones to the total number of bones on the model
1651 if(e.skeleton_bones_index == e.modelindex)
1652 return; // same model, nothing to update
1655 skelindex = skel_create(e.modelindex);
1656 e.skeleton_bones = skel_get_numbones(skelindex);
1657 skel_delete(skelindex);
1658 e.skeleton_bones_index = e.modelindex;
1662 string to_execute_next_frame;
1663 void execute_next_frame()
1665 if(to_execute_next_frame)
1667 localcmd("\n", to_execute_next_frame, "\n");
1668 strfree(to_execute_next_frame);
1671 void queue_to_execute_next_frame(string s)
1673 if(to_execute_next_frame)
1675 s = strcat(s, "\n", to_execute_next_frame);
1677 strcpy(to_execute_next_frame, s);
1680 .float FindConnectedComponent_processing;
1681 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1683 entity queue_start, queue_end;
1685 // we build a queue of to-be-processed entities.
1686 // queue_start is the next entity to be checked for neighbors
1687 // queue_end is the last entity added
1689 if(e.FindConnectedComponent_processing)
1690 error("recursion or broken cleanup");
1692 // start with a 1-element queue
1693 queue_start = queue_end = e;
1694 queue_end.(fld) = NULL;
1695 queue_end.FindConnectedComponent_processing = 1;
1697 // for each queued item:
1698 for (; queue_start; queue_start = queue_start.(fld))
1700 // find all neighbors of queue_start
1702 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1704 if(t.FindConnectedComponent_processing)
1706 if(iscon(t, queue_start, pass))
1708 // it is connected? ADD IT. It will look for neighbors soon too.
1709 queue_end.(fld) = t;
1711 queue_end.(fld) = NULL;
1712 queue_end.FindConnectedComponent_processing = 1;
1718 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1719 queue_start.FindConnectedComponent_processing = 0;
1723 vector animfixfps(entity e, vector a, vector b)
1725 // multi-frame anim: keep as-is
1728 float dur = frameduration(e.modelindex, a.x);
1729 if (dur <= 0 && b.y)
1732 dur = frameduration(e.modelindex, a.x);
1742 Notification Announcer_PickNumber(int type, int num)
1751 case 10: return ANNCE_NUM_GAMESTART_10;
1752 case 9: return ANNCE_NUM_GAMESTART_9;
1753 case 8: return ANNCE_NUM_GAMESTART_8;
1754 case 7: return ANNCE_NUM_GAMESTART_7;
1755 case 6: return ANNCE_NUM_GAMESTART_6;
1756 case 5: return ANNCE_NUM_GAMESTART_5;
1757 case 4: return ANNCE_NUM_GAMESTART_4;
1758 case 3: return ANNCE_NUM_GAMESTART_3;
1759 case 2: return ANNCE_NUM_GAMESTART_2;
1760 case 1: return ANNCE_NUM_GAMESTART_1;
1768 case 10: return ANNCE_NUM_IDLE_10;
1769 case 9: return ANNCE_NUM_IDLE_9;
1770 case 8: return ANNCE_NUM_IDLE_8;
1771 case 7: return ANNCE_NUM_IDLE_7;
1772 case 6: return ANNCE_NUM_IDLE_6;
1773 case 5: return ANNCE_NUM_IDLE_5;
1774 case 4: return ANNCE_NUM_IDLE_4;
1775 case 3: return ANNCE_NUM_IDLE_3;
1776 case 2: return ANNCE_NUM_IDLE_2;
1777 case 1: return ANNCE_NUM_IDLE_1;
1785 case 10: return ANNCE_NUM_KILL_10;
1786 case 9: return ANNCE_NUM_KILL_9;
1787 case 8: return ANNCE_NUM_KILL_8;
1788 case 7: return ANNCE_NUM_KILL_7;
1789 case 6: return ANNCE_NUM_KILL_6;
1790 case 5: return ANNCE_NUM_KILL_5;
1791 case 4: return ANNCE_NUM_KILL_4;
1792 case 3: return ANNCE_NUM_KILL_3;
1793 case 2: return ANNCE_NUM_KILL_2;
1794 case 1: return ANNCE_NUM_KILL_1;
1802 case 10: return ANNCE_NUM_RESPAWN_10;
1803 case 9: return ANNCE_NUM_RESPAWN_9;
1804 case 8: return ANNCE_NUM_RESPAWN_8;
1805 case 7: return ANNCE_NUM_RESPAWN_7;
1806 case 6: return ANNCE_NUM_RESPAWN_6;
1807 case 5: return ANNCE_NUM_RESPAWN_5;
1808 case 4: return ANNCE_NUM_RESPAWN_4;
1809 case 3: return ANNCE_NUM_RESPAWN_3;
1810 case 2: return ANNCE_NUM_RESPAWN_2;
1811 case 1: return ANNCE_NUM_RESPAWN_1;
1815 case CNT_ROUNDSTART:
1819 case 10: return ANNCE_NUM_ROUNDSTART_10;
1820 case 9: return ANNCE_NUM_ROUNDSTART_9;
1821 case 8: return ANNCE_NUM_ROUNDSTART_8;
1822 case 7: return ANNCE_NUM_ROUNDSTART_7;
1823 case 6: return ANNCE_NUM_ROUNDSTART_6;
1824 case 5: return ANNCE_NUM_ROUNDSTART_5;
1825 case 4: return ANNCE_NUM_ROUNDSTART_4;
1826 case 3: return ANNCE_NUM_ROUNDSTART_3;
1827 case 2: return ANNCE_NUM_ROUNDSTART_2;
1828 case 1: return ANNCE_NUM_ROUNDSTART_1;
1836 case 10: return ANNCE_NUM_10;
1837 case 9: return ANNCE_NUM_9;
1838 case 8: return ANNCE_NUM_8;
1839 case 7: return ANNCE_NUM_7;
1840 case 6: return ANNCE_NUM_6;
1841 case 5: return ANNCE_NUM_5;
1842 case 4: return ANNCE_NUM_4;
1843 case 3: return ANNCE_NUM_3;
1844 case 2: return ANNCE_NUM_2;
1845 case 1: return ANNCE_NUM_1;
1854 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1856 switch(nativecontents)
1861 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1863 return DPCONTENTS_WATER;
1865 return DPCONTENTS_SLIME;
1867 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1869 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1874 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1876 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1877 return CONTENT_SOLID;
1878 if(supercontents & DPCONTENTS_SKY)
1880 if(supercontents & DPCONTENTS_LAVA)
1881 return CONTENT_LAVA;
1882 if(supercontents & DPCONTENTS_SLIME)
1883 return CONTENT_SLIME;
1884 if(supercontents & DPCONTENTS_WATER)
1885 return CONTENT_WATER;
1886 return CONTENT_EMPTY;
1891 void attach_sameorigin(entity e, entity to, string tag)
1893 vector org, t_forward, t_left, t_up, e_forward, e_up;
1896 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1897 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1898 t_forward = v_forward * tagscale;
1899 t_left = v_right * -tagscale;
1900 t_up = v_up * tagscale;
1902 e.origin_x = org * t_forward;
1903 e.origin_y = org * t_left;
1904 e.origin_z = org * t_up;
1906 // current forward and up directions
1907 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1908 e.angles = AnglesTransform_FromVAngles(e.angles);
1910 e.angles = AnglesTransform_FromAngles(e.angles);
1911 fixedmakevectors(e.angles);
1913 // untransform forward, up!
1914 e_forward.x = v_forward * t_forward;
1915 e_forward.y = v_forward * t_left;
1916 e_forward.z = v_forward * t_up;
1917 e_up.x = v_up * t_forward;
1918 e_up.y = v_up * t_left;
1919 e_up.z = v_up * t_up;
1921 e.angles = fixedvectoangles2(e_forward, e_up);
1922 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1923 e.angles = AnglesTransform_ToVAngles(e.angles);
1925 e.angles = AnglesTransform_ToAngles(e.angles);
1927 setattachment(e, to, tag);
1928 setorigin(e, e.origin);
1931 void detach_sameorigin(entity e)
1934 org = gettaginfo(e, 0);
1935 e.angles = fixedvectoangles2(v_forward, v_up);
1936 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1937 e.angles = AnglesTransform_ToVAngles(e.angles);
1939 e.angles = AnglesTransform_ToAngles(e.angles);
1941 setattachment(e, NULL, "");
1942 setorigin(e, e.origin);
1945 void follow_sameorigin(entity e, entity to)
1947 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1948 e.aiment = to; // make the hole follow bmodel
1949 e.punchangle = to.angles; // the original angles of bmodel
1950 e.view_ofs = e.origin - to.origin; // relative origin
1951 e.v_angle = e.angles - to.angles; // relative angles
1955 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1956 void unfollow_sameorigin(entity e)
1958 set_movetype(e, MOVETYPE_NONE);
1962 .string aiment_classname;
1963 .float aiment_deadflag;
1964 void SetMovetypeFollow(entity ent, entity e)
1966 // FIXME this may not be warpzone aware
1967 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1968 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.
1969 ent.aiment = e; // make the hole follow bmodel
1970 ent.punchangle = e.angles; // the original angles of bmodel
1971 ent.view_ofs = ent.origin - e.origin; // relative origin
1972 ent.v_angle = ent.angles - e.angles; // relative angles
1973 ent.aiment_classname = strzone(e.classname);
1974 ent.aiment_deadflag = e.deadflag;
1976 void UnsetMovetypeFollow(entity ent)
1978 set_movetype(ent, MOVETYPE_FLY);
1979 PROJECTILE_MAKETRIGGER(ent);
1982 float LostMovetypeFollow(entity ent)
1985 if(ent.move_movetype != MOVETYPE_FOLLOW)
1991 if(ent.aiment.classname != ent.aiment_classname)
1993 if(ent.aiment.deadflag != ent.aiment_deadflag)
2001 // decolorizes and team colors the player name when needed
2002 string playername(string thename, int teamid, bool team_colorize)
2005 bool do_colorize = (teamplay && team_colorize);
2007 if(do_colorize && !intermission_running)
2012 string t = Team_ColorCode(teamid);
2013 return strcat(t, strdecolorize(thename));
2019 float trace_hits_box_a0, trace_hits_box_a1;
2021 float trace_hits_box_1d(float end, float thmi, float thma)
2025 // just check if x is in range
2033 // do the trace with respect to x
2034 // 0 -> end has to stay in thmi -> thma
2035 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2036 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2037 if (trace_hits_box_a0 > trace_hits_box_a1)
2043 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2048 // now it is a trace from 0 to end
2050 trace_hits_box_a0 = 0;
2051 trace_hits_box_a1 = 1;
2053 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2055 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2057 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2063 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2065 return trace_hits_box(start, end, thmi - ma, thma - mi);
2070 float cvar_or(string cv, float v)
2072 string s = cvar_string(cv);
2079 // NOTE base is the central value
2080 // freq: circle frequency, = 2*pi*frequency in hertz
2082 // -1 start from the lower value
2083 // 0 start from the base value
2084 // 1 start from the higher value
2086 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2089 // RMS = sqrt(base^2 + 0.5 * range^2)
2091 // base = sqrt(RMS^2 - 0.5 * range^2)
2094 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2098 float blink(float base, float range, float freq)
2100 return blink_synced(base, range, freq, 0, 0);