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 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
776 // The following function is SLOW.
777 // For your safety and for the protection of those around you...
778 // DO NOT CALL THIS AT HOME.
780 if(w(theText, theSize) <= maxWidth)
781 return strlen(theText); // yeah!
783 bool colors = (w("^7", theSize) == 0);
785 // binary search for right place to cut string
786 int len, left, right, middle;
788 right = len = strlen(theText);
792 middle = floor((left + right) / 2);
795 vector res = checkColorCode(theText, len, middle, false);
796 ofs = (res.x) ? res.x - res.y : 0;
799 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
804 while(left < right - 1);
809 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
812 // The following function is SLOW.
813 // For your safety and for the protection of those around you...
814 // DO NOT CALL THIS AT HOME.
816 if(w(theText) <= maxWidth)
817 return strlen(theText); // yeah!
819 bool colors = (w("^7") == 0);
821 // binary search for right place to cut string
822 int len, left, right, middle;
824 right = len = strlen(theText);
828 middle = floor((left + right) / 2);
831 vector res = checkColorCode(theText, len, middle, true);
832 ofs = (!res.x) ? 0 : res.x - res.y;
835 if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
840 while(left < right - 1);
845 string find_last_color_code(string s)
847 int start = strstrofs(s, "^", 0);
848 if (start == -1) // no caret found
850 int len = strlen(s)-1;
851 for(int i = len; i >= start; --i)
853 if(substring(s, i, 1) != "^")
857 while (i-carets >= start && substring(s, i-carets, 1) == "^")
860 // check if carets aren't all escaped
864 if(IS_DIGIT(substring(s, i+1, 1)))
865 return substring(s, i, 2);
868 if(substring(s, i+1, 1) == "x")
869 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
870 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
871 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
872 return substring(s, i, 5);
874 i -= carets; // this also skips one char before the carets
880 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
882 string s = getWrappedLine_remaining;
886 getWrappedLine_remaining = string_null;
887 return s; // the line has no size ANYWAY, nothing would be displayed.
890 int take_until = textLengthUpToWidth(s, w, theFontSize, tw);
891 if(take_until > 0 && take_until < strlen(s))
893 int last_word = take_until - 1;
894 while(last_word > 0 && substring(s, last_word, 1) != " ")
900 take_until = last_word;
904 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
905 if(getWrappedLine_remaining == "")
906 getWrappedLine_remaining = string_null;
907 else if (tw("^7", theFontSize) == 0)
908 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
909 return substring(s, 0, take_until);
913 getWrappedLine_remaining = string_null;
918 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
920 string s = getWrappedLine_remaining;
924 getWrappedLine_remaining = string_null;
925 return s; // the line has no size ANYWAY, nothing would be displayed.
928 int take_until = textLengthUpToLength(s, w, tw);
929 if(take_until > 0 && take_until < strlen(s))
931 int last_word = take_until - 1;
932 while(last_word > 0 && substring(s, last_word, 1) != " ")
938 take_until = last_word;
942 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
943 if(getWrappedLine_remaining == "")
944 getWrappedLine_remaining = string_null;
945 else if (tw("^7") == 0)
946 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
947 return substring(s, 0, take_until);
951 getWrappedLine_remaining = string_null;
956 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
958 if(tw(theText, theFontSize) <= maxWidth)
961 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
964 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
966 if(tw(theText) <= maxWidth)
969 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
972 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
974 string subpattern, subpattern2, subpattern3, subpattern4;
975 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
977 subpattern2 = ",teams,";
979 subpattern2 = ",noteams,";
981 subpattern3 = ",teamspawns,";
983 subpattern3 = ",noteamspawns,";
984 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
985 subpattern4 = ",race,";
987 subpattern4 = string_null;
989 if(substring(pattern, 0, 1) == "-")
991 pattern = substring(pattern, 1, strlen(pattern) - 1);
992 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
994 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
996 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
998 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1003 if(substring(pattern, 0, 1) == "+")
1004 pattern = substring(pattern, 1, strlen(pattern) - 1);
1005 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1006 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1007 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1011 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1018 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1022 // make origin and speed relative
1027 // now solve for ret, ret normalized:
1028 // eorg + t * evel == t * ret * spd
1029 // or, rather, solve for t:
1030 // |eorg + t * evel| == t * spd
1031 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1032 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1033 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1034 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1035 // q = (eorg * eorg) / (evel * evel - spd * spd)
1036 if(!solution.z) // no real solution
1039 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1040 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1041 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1042 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1043 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1044 // spd < |evel| * sin angle(evel, eorg)
1047 else if(solution.x > 0)
1049 // both solutions > 0: take the smaller one
1050 // happens if p < 0 and q > 0
1051 ret = normalize(eorg + solution.x * evel);
1053 else if(solution.y > 0)
1055 // one solution > 0: take the larger one
1056 // happens if q < 0 or q == 0 and p < 0
1057 ret = normalize(eorg + solution.y * evel);
1061 // no solution > 0: reject
1062 // happens if p > 0 and q >= 0
1063 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1064 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1069 // "Enemy is moving away from me at more than spd"
1073 // NOTE: we always got a solution if spd > |evel|
1075 if(newton_style == 2)
1076 ret = normalize(ret * spd + myvel);
1081 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1086 if(newton_style == 2)
1088 // true Newtonian projectiles with automatic aim adjustment
1090 // solve: |outspeed * mydir - myvel| = spd
1091 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1092 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1096 // myvel^2 - (mydir * myvel)^2 > spd^2
1097 // velocity without mydir component > spd
1098 // fire at smallest possible spd that works?
1099 // |(mydir * myvel) * myvel - myvel| = spd
1101 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1105 outspeed = solution.y; // the larger one
1108 //outspeed = 0; // slowest possible shot
1109 outspeed = solution.x; // the real part (that is, the average!)
1110 //dprint("impossible shot, adjusting\n");
1113 outspeed = bound(spd * mi, outspeed, spd * ma);
1114 return mydir * outspeed;
1118 return myvel + spd * mydir;
1121 float compressShotOrigin(vector v)
1123 float rx = rint(v.x * 2);
1124 float ry = rint(v.y * 4) + 128;
1125 float rz = rint(v.z * 4) + 128;
1126 if(rx > 255 || rx < 0)
1128 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1129 rx = bound(0, rx, 255);
1131 if(ry > 255 || ry < 0)
1133 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1134 ry = bound(0, ry, 255);
1136 if(rz > 255 || rz < 0)
1138 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1139 rz = bound(0, rz, 255);
1141 return rx * 0x10000 + ry * 0x100 + rz;
1143 vector decompressShotOrigin(int f)
1146 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1147 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1148 v.z = ((f & 0xFF) - 128) / 4;
1153 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1155 // NOTE: we'll always choose the SMALLER value...
1156 float healthdamage, armordamage, armorideal;
1157 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1160 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1161 armordamage = a + (h - 1); // damage we can take if we could use more armor
1162 armorideal = healthdamage * armorblock;
1164 if(armordamage < healthdamage)
1177 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1180 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1182 if (deathtype & HITTYPE_ARMORPIERCE)
1184 v.y = bound(0, damage * armorblock, a); // save
1185 v.x = bound(0, damage - v.y, damage); // take
1191 string getcurrentmod()
1195 m = cvar_string("fs_gamedir");
1196 n = tokenize_console(m);
1203 float matchacl(string acl, string str)
1210 t = car(acl); acl = cdr(acl);
1213 if(substring(t, 0, 1) == "-")
1216 t = substring(t, 1, strlen(t) - 1);
1218 else if(substring(t, 0, 1) == "+")
1219 t = substring(t, 1, strlen(t) - 1);
1221 if(substring(t, -1, 1) == "*")
1223 t = substring(t, 0, strlen(t) - 1);
1224 s = substring(str, 0, strlen(t));
1232 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1239 void write_String_To_File(int fh, string str, bool alsoprint)
1242 if (alsoprint) LOG_HELP(str);
1245 string get_model_datafilename(string m, float sk, string fil)
1250 m = "models/player/*_";
1252 m = strcat(m, ftos(sk));
1255 return strcat(m, ".", fil);
1258 float get_model_parameters(string m, float sk)
1260 get_model_parameters_modelname = string_null;
1261 get_model_parameters_modelskin = -1;
1262 get_model_parameters_name = string_null;
1263 get_model_parameters_species = -1;
1264 get_model_parameters_sex = string_null;
1265 get_model_parameters_weight = -1;
1266 get_model_parameters_age = -1;
1267 get_model_parameters_desc = string_null;
1268 get_model_parameters_bone_upperbody = string_null;
1269 get_model_parameters_bone_weapon = string_null;
1270 for(int i = 0; i < MAX_AIM_BONES; ++i)
1272 get_model_parameters_bone_aim[i] = string_null;
1273 get_model_parameters_bone_aimweight[i] = 0;
1275 get_model_parameters_fixbone = 0;
1276 get_model_parameters_hidden = false;
1279 MUTATOR_CALLHOOK(ClearModelParams);
1285 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1286 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1290 if(substring(m, -4, -1) != ".txt")
1292 if(substring(m, -6, 1) != "_")
1294 sk = stof(substring(m, -5, 1));
1295 m = substring(m, 0, -7);
1298 string fn = get_model_datafilename(m, sk, "txt");
1299 int fh = fopen(fn, FILE_READ);
1303 fn = get_model_datafilename(m, sk, "txt");
1304 fh = fopen(fn, FILE_READ);
1309 get_model_parameters_modelname = m;
1310 get_model_parameters_modelskin = sk;
1312 while((s = fgets(fh)))
1315 break; // next lines will be description
1319 get_model_parameters_name = s;
1323 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1324 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1325 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1326 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1327 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1328 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1329 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1333 if (s == "Male") s = _("Male");
1334 else if (s == "Female") s = _("Female");
1335 else if (s == "Undisclosed") s = _("Undisclosed");
1336 get_model_parameters_sex = s;
1339 get_model_parameters_weight = stof(s);
1341 get_model_parameters_age = stof(s);
1342 if(c == "description")
1343 get_model_parameters_description = s;
1344 if(c == "bone_upperbody")
1345 get_model_parameters_bone_upperbody = s;
1346 if(c == "bone_weapon")
1347 get_model_parameters_bone_weapon = s;
1349 MUTATOR_CALLHOOK(GetModelParams, c, s);
1351 for(int i = 0; i < MAX_AIM_BONES; ++i)
1352 if(c == strcat("bone_aim", ftos(i)))
1354 get_model_parameters_bone_aimweight[i] = stof(car(s));
1355 get_model_parameters_bone_aim[i] = cdr(s);
1358 get_model_parameters_fixbone = stof(s);
1360 get_model_parameters_hidden = stob(s);
1363 while((s = fgets(fh)))
1365 if(get_model_parameters_desc)
1366 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1368 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1376 string translate_key(string key)
1378 if (prvm_language == "en") return key;
1380 if (substring(key, 0, 1) == "<")
1382 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1383 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1388 case "TAB": return _("TAB");
1389 case "ENTER": return _("ENTER");
1390 case "ESCAPE": return _("ESCAPE");
1391 case "SPACE": return _("SPACE");
1393 case "BACKSPACE": return _("BACKSPACE");
1394 case "UPARROW": return _("UPARROW");
1395 case "DOWNARROW": return _("DOWNARROW");
1396 case "LEFTARROW": return _("LEFTARROW");
1397 case "RIGHTARROW": return _("RIGHTARROW");
1399 case "ALT": return _("ALT");
1400 case "CTRL": return _("CTRL");
1401 case "SHIFT": return _("SHIFT");
1403 case "INS": return _("INS");
1404 case "DEL": return _("DEL");
1405 case "PGDN": return _("PGDN");
1406 case "PGUP": return _("PGUP");
1407 case "HOME": return _("HOME");
1408 case "END": return _("END");
1410 case "PAUSE": return _("PAUSE");
1412 case "NUMLOCK": return _("NUMLOCK");
1413 case "CAPSLOCK": return _("CAPSLOCK");
1414 case "SCROLLOCK": return _("SCROLLOCK");
1416 case "SEMICOLON": return _("SEMICOLON");
1417 case "TILDE": return _("TILDE");
1418 case "BACKQUOTE": return _("BACKQUOTE");
1419 case "QUOTE": return _("QUOTE");
1420 case "APOSTROPHE": return _("APOSTROPHE");
1421 case "BACKSLASH": return _("BACKSLASH");
1424 if (substring(key, 0, 1) == "F")
1426 string subkey = substring(key, 1, -1);
1427 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1429 return sprintf(_("F%d"), stof(subkey));
1431 // continue in case there is another key name starting with F
1434 if (substring(key, 0, 3) == "KP_")
1436 string subkey = substring(key, 3, -1);
1437 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1439 return sprintf(_("KP_%d"), stof(subkey));
1444 case "INS": return sprintf(_("KP_%s"), _("INS"));
1445 case "END": return sprintf(_("KP_%s"), _("END"));
1446 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1447 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1448 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1449 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1450 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1451 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1452 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1453 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1454 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1455 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1456 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1457 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1458 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1459 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1460 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1461 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1462 default: return key;
1466 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1468 if (substring(key, 0, 5) == "MOUSE")
1469 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1471 if (key == "MWHEELUP") return _("MWHEELUP");
1472 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1474 if (substring(key, 0,3) == "JOY")
1475 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1477 if (substring(key, 0,3) == "AUX")
1478 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1480 if (substring(key, 0, 4) == "X360_")
1482 string subkey = substring(key, 4, -1);
1485 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1486 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1487 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1488 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1489 case "START": return sprintf(_("X360_%s"), _("START"));
1490 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1491 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1492 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1493 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1494 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1495 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1496 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1497 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1498 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1499 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1500 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1501 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1502 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1503 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1504 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1505 default: return key;
1509 if (substring(key, 0, 4) == "JOY_")
1511 string subkey = substring(key, 4, -1);
1514 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1515 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1516 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1517 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1518 default: return key;
1522 if (substring(key, 0, 8) == "MIDINOTE")
1523 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1528 // x-encoding (encoding as zero length invisible string)
1529 const string XENCODE_2 = "xX";
1530 const string XENCODE_22 = "0123456789abcdefABCDEF";
1531 string xencode(int f)
1534 d = f % 22; f = floor(f / 22);
1535 c = f % 22; f = floor(f / 22);
1536 b = f % 22; f = floor(f / 22);
1537 a = f % 2; // f = floor(f / 2);
1540 substring(XENCODE_2, a, 1),
1541 substring(XENCODE_22, b, 1),
1542 substring(XENCODE_22, c, 1),
1543 substring(XENCODE_22, d, 1)
1546 float xdecode(string s)
1549 if(substring(s, 0, 1) != "^")
1553 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1554 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1555 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1556 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1557 if(a < 0 || b < 0 || c < 0 || d < 0)
1559 return ((a * 22 + b) * 22 + c) * 22 + d;
1563 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1565 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1568 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1571 float shutdown_running;
1576 void CSQC_Shutdown()
1582 if(shutdown_running)
1584 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1588 shutdown_running = 1;
1592 cvar_settemp_restore(); // this must be done LAST, but in any case
1596 .float skeleton_bones_index;
1597 void Skeleton_SetBones(entity e)
1599 // set skeleton_bones to the total number of bones on the model
1600 if(e.skeleton_bones_index == e.modelindex)
1601 return; // same model, nothing to update
1604 skelindex = skel_create(e.modelindex);
1605 e.skeleton_bones = skel_get_numbones(skelindex);
1606 skel_delete(skelindex);
1607 e.skeleton_bones_index = e.modelindex;
1611 string to_execute_next_frame;
1612 void execute_next_frame()
1614 if(to_execute_next_frame)
1616 localcmd("\n", to_execute_next_frame, "\n");
1617 strfree(to_execute_next_frame);
1620 void queue_to_execute_next_frame(string s)
1622 if(to_execute_next_frame)
1624 s = strcat(s, "\n", to_execute_next_frame);
1626 strcpy(to_execute_next_frame, s);
1629 .float FindConnectedComponent_processing;
1630 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1632 entity queue_start, queue_end;
1634 // we build a queue of to-be-processed entities.
1635 // queue_start is the next entity to be checked for neighbors
1636 // queue_end is the last entity added
1638 if(e.FindConnectedComponent_processing)
1639 error("recursion or broken cleanup");
1641 // start with a 1-element queue
1642 queue_start = queue_end = e;
1643 queue_end.(fld) = NULL;
1644 queue_end.FindConnectedComponent_processing = 1;
1646 // for each queued item:
1647 for (; queue_start; queue_start = queue_start.(fld))
1649 // find all neighbors of queue_start
1651 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1653 if(t.FindConnectedComponent_processing)
1655 if(iscon(t, queue_start, pass))
1657 // it is connected? ADD IT. It will look for neighbors soon too.
1658 queue_end.(fld) = t;
1660 queue_end.(fld) = NULL;
1661 queue_end.FindConnectedComponent_processing = 1;
1667 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1668 queue_start.FindConnectedComponent_processing = 0;
1672 vector animfixfps(entity e, vector a, vector b)
1674 // multi-frame anim: keep as-is
1677 float dur = frameduration(e.modelindex, a.x);
1678 if (dur <= 0 && b.y)
1681 dur = frameduration(e.modelindex, a.x);
1691 Notification Announcer_PickNumber(int type, int num)
1700 case 10: return ANNCE_NUM_GAMESTART_10;
1701 case 9: return ANNCE_NUM_GAMESTART_9;
1702 case 8: return ANNCE_NUM_GAMESTART_8;
1703 case 7: return ANNCE_NUM_GAMESTART_7;
1704 case 6: return ANNCE_NUM_GAMESTART_6;
1705 case 5: return ANNCE_NUM_GAMESTART_5;
1706 case 4: return ANNCE_NUM_GAMESTART_4;
1707 case 3: return ANNCE_NUM_GAMESTART_3;
1708 case 2: return ANNCE_NUM_GAMESTART_2;
1709 case 1: return ANNCE_NUM_GAMESTART_1;
1717 case 10: return ANNCE_NUM_KILL_10;
1718 case 9: return ANNCE_NUM_KILL_9;
1719 case 8: return ANNCE_NUM_KILL_8;
1720 case 7: return ANNCE_NUM_KILL_7;
1721 case 6: return ANNCE_NUM_KILL_6;
1722 case 5: return ANNCE_NUM_KILL_5;
1723 case 4: return ANNCE_NUM_KILL_4;
1724 case 3: return ANNCE_NUM_KILL_3;
1725 case 2: return ANNCE_NUM_KILL_2;
1726 case 1: return ANNCE_NUM_KILL_1;
1734 case 10: return ANNCE_NUM_RESPAWN_10;
1735 case 9: return ANNCE_NUM_RESPAWN_9;
1736 case 8: return ANNCE_NUM_RESPAWN_8;
1737 case 7: return ANNCE_NUM_RESPAWN_7;
1738 case 6: return ANNCE_NUM_RESPAWN_6;
1739 case 5: return ANNCE_NUM_RESPAWN_5;
1740 case 4: return ANNCE_NUM_RESPAWN_4;
1741 case 3: return ANNCE_NUM_RESPAWN_3;
1742 case 2: return ANNCE_NUM_RESPAWN_2;
1743 case 1: return ANNCE_NUM_RESPAWN_1;
1747 case CNT_ROUNDSTART:
1751 case 10: return ANNCE_NUM_ROUNDSTART_10;
1752 case 9: return ANNCE_NUM_ROUNDSTART_9;
1753 case 8: return ANNCE_NUM_ROUNDSTART_8;
1754 case 7: return ANNCE_NUM_ROUNDSTART_7;
1755 case 6: return ANNCE_NUM_ROUNDSTART_6;
1756 case 5: return ANNCE_NUM_ROUNDSTART_5;
1757 case 4: return ANNCE_NUM_ROUNDSTART_4;
1758 case 3: return ANNCE_NUM_ROUNDSTART_3;
1759 case 2: return ANNCE_NUM_ROUNDSTART_2;
1760 case 1: return ANNCE_NUM_ROUNDSTART_1;
1769 case 10: return ANNCE_NUM_10;
1770 case 9: return ANNCE_NUM_9;
1771 case 8: return ANNCE_NUM_8;
1772 case 7: return ANNCE_NUM_7;
1773 case 6: return ANNCE_NUM_6;
1774 case 5: return ANNCE_NUM_5;
1775 case 4: return ANNCE_NUM_4;
1776 case 3: return ANNCE_NUM_3;
1777 case 2: return ANNCE_NUM_2;
1778 case 1: return ANNCE_NUM_1;
1787 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1789 switch(nativecontents)
1794 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1796 return DPCONTENTS_WATER;
1798 return DPCONTENTS_SLIME;
1800 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1802 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1807 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1809 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1810 return CONTENT_SOLID;
1811 if(supercontents & DPCONTENTS_SKY)
1813 if(supercontents & DPCONTENTS_LAVA)
1814 return CONTENT_LAVA;
1815 if(supercontents & DPCONTENTS_SLIME)
1816 return CONTENT_SLIME;
1817 if(supercontents & DPCONTENTS_WATER)
1818 return CONTENT_WATER;
1819 return CONTENT_EMPTY;
1824 void attach_sameorigin(entity e, entity to, string tag)
1826 vector org, t_forward, t_left, t_up, e_forward, e_up;
1829 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1830 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1831 t_forward = v_forward * tagscale;
1832 t_left = v_right * -tagscale;
1833 t_up = v_up * tagscale;
1835 e.origin_x = org * t_forward;
1836 e.origin_y = org * t_left;
1837 e.origin_z = org * t_up;
1839 // current forward and up directions
1840 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1841 e.angles = AnglesTransform_FromVAngles(e.angles);
1843 e.angles = AnglesTransform_FromAngles(e.angles);
1844 fixedmakevectors(e.angles);
1846 // untransform forward, up!
1847 e_forward.x = v_forward * t_forward;
1848 e_forward.y = v_forward * t_left;
1849 e_forward.z = v_forward * t_up;
1850 e_up.x = v_up * t_forward;
1851 e_up.y = v_up * t_left;
1852 e_up.z = v_up * t_up;
1854 e.angles = fixedvectoangles2(e_forward, e_up);
1855 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1856 e.angles = AnglesTransform_ToVAngles(e.angles);
1858 e.angles = AnglesTransform_ToAngles(e.angles);
1860 setattachment(e, to, tag);
1861 setorigin(e, e.origin);
1864 void detach_sameorigin(entity e)
1867 org = gettaginfo(e, 0);
1868 e.angles = fixedvectoangles2(v_forward, v_up);
1869 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1870 e.angles = AnglesTransform_ToVAngles(e.angles);
1872 e.angles = AnglesTransform_ToAngles(e.angles);
1874 setattachment(e, NULL, "");
1875 setorigin(e, e.origin);
1878 void follow_sameorigin(entity e, entity to)
1880 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1881 e.aiment = to; // make the hole follow bmodel
1882 e.punchangle = to.angles; // the original angles of bmodel
1883 e.view_ofs = e.origin - to.origin; // relative origin
1884 e.v_angle = e.angles - to.angles; // relative angles
1888 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1889 void unfollow_sameorigin(entity e)
1891 set_movetype(e, MOVETYPE_NONE);
1895 .string aiment_classname;
1896 .float aiment_deadflag;
1897 void SetMovetypeFollow(entity ent, entity e)
1899 // FIXME this may not be warpzone aware
1900 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1901 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.
1902 ent.aiment = e; // make the hole follow bmodel
1903 ent.punchangle = e.angles; // the original angles of bmodel
1904 ent.view_ofs = ent.origin - e.origin; // relative origin
1905 ent.v_angle = ent.angles - e.angles; // relative angles
1906 ent.aiment_classname = strzone(e.classname);
1907 ent.aiment_deadflag = e.deadflag;
1909 if(IS_PLAYER(ent.aiment))
1911 entity pl = ent.aiment;
1912 ent.view_ofs.x = bound(pl.mins.x + 4, ent.view_ofs.x, pl.maxs.x - 4);
1913 ent.view_ofs.y = bound(pl.mins.y + 4, ent.view_ofs.y, pl.maxs.y - 4);
1914 ent.view_ofs.z = bound(pl.mins.z + 4, ent.view_ofs.z, pl.maxs.z - 4);
1918 void UnsetMovetypeFollow(entity ent)
1920 set_movetype(ent, MOVETYPE_FLY);
1921 PROJECTILE_MAKETRIGGER(ent);
1922 if (ent.aiment_classname)
1923 strunzone(ent.classname);
1924 // FIXME: engine bug?
1925 // resetting aiment the engine will set orb's origin close to world's origin
1926 //ent.aiment = NULL;
1929 int LostMovetypeFollow(entity ent)
1932 if(ent.move_movetype != MOVETYPE_FOLLOW)
1936 // FIXME: engine bug?
1937 // when aiment disconnects the engine will set orb's origin close to world's origin
1940 if(ent.aiment.classname != ent.aiment_classname || ent.aiment.deadflag != ent.aiment_deadflag)
1947 // decolorizes and team colors the player name when needed
1948 string playername(string thename, int teamid, bool team_colorize)
1951 bool do_colorize = (teamplay && team_colorize);
1953 if(do_colorize && !intermission_running)
1958 string t = Team_ColorCode(teamid);
1959 return strcat(t, strdecolorize(thename));
1965 float trace_hits_box_a0, trace_hits_box_a1;
1967 float trace_hits_box_1d(float end, float thmi, float thma)
1971 // just check if x is in range
1979 // do the trace with respect to x
1980 // 0 -> end has to stay in thmi -> thma
1981 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1982 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1983 if (trace_hits_box_a0 > trace_hits_box_a1)
1989 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1994 // now it is a trace from 0 to end
1996 trace_hits_box_a0 = 0;
1997 trace_hits_box_a1 = 1;
1999 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2001 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2003 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2009 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2011 return trace_hits_box(start, end, thmi - ma, thma - mi);
2016 float cvar_or(string cv, float v)
2018 string s = cvar_string(cv);
2025 // NOTE base is the central value
2026 // freq: circle frequency, = 2*pi*frequency in hertz
2028 // -1 start from the lower value
2029 // 0 start from the base value
2030 // 1 start from the higher value
2032 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2035 // RMS = sqrt(base^2 + 0.5 * range^2)
2037 // base = sqrt(RMS^2 - 0.5 * range^2)
2040 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2044 float blink(float base, float range, float freq)
2046 return blink_synced(base, range, freq, 0, 0);