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)
925 string s = getWrappedLine_remaining;
929 getWrappedLine_remaining = string_null;
930 return s; // the line has no size ANYWAY, nothing would be displayed.
933 int take_until = textLengthUpToWidth(s, w, theFontSize, tw);
934 if(take_until > 0 && take_until < strlen(s))
936 int last_word = take_until - 1;
937 while(last_word > 0 && substring(s, last_word, 1) != " ")
943 take_until = last_word;
947 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
948 if(getWrappedLine_remaining == "")
949 getWrappedLine_remaining = string_null;
950 else if (tw("^7", theFontSize) == 0)
951 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
952 return substring(s, 0, take_until);
956 getWrappedLine_remaining = string_null;
961 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
963 string s = getWrappedLine_remaining;
967 getWrappedLine_remaining = string_null;
968 return s; // the line has no size ANYWAY, nothing would be displayed.
971 int take_until = textLengthUpToLength(s, w, tw);
972 if(take_until > 0 && take_until < strlen(s))
974 int last_word = take_until - 1;
975 while(last_word > 0 && substring(s, last_word, 1) != " ")
981 take_until = last_word;
985 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
986 if(getWrappedLine_remaining == "")
987 getWrappedLine_remaining = string_null;
988 else if (tw("^7") == 0)
989 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
990 return substring(s, 0, take_until);
994 getWrappedLine_remaining = string_null;
999 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1001 if(tw(theText, theFontSize) <= maxWidth)
1004 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1007 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1009 if(tw(theText) <= maxWidth)
1012 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1015 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1017 string subpattern, subpattern2, subpattern3, subpattern4;
1018 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1020 subpattern2 = ",teams,";
1022 subpattern2 = ",noteams,";
1024 subpattern3 = ",teamspawns,";
1026 subpattern3 = ",noteamspawns,";
1027 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1028 subpattern4 = ",race,";
1030 subpattern4 = string_null;
1032 if(substring(pattern, 0, 1) == "-")
1034 pattern = substring(pattern, 1, strlen(pattern) - 1);
1035 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1037 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1039 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1041 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1046 if(substring(pattern, 0, 1) == "+")
1047 pattern = substring(pattern, 1, strlen(pattern) - 1);
1048 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1049 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1050 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1054 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1061 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1065 // make origin and speed relative
1070 // now solve for ret, ret normalized:
1071 // eorg + t * evel == t * ret * spd
1072 // or, rather, solve for t:
1073 // |eorg + t * evel| == t * spd
1074 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1075 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1076 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1077 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1078 // q = (eorg * eorg) / (evel * evel - spd * spd)
1079 if(!solution.z) // no real solution
1082 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1083 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1084 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1085 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1086 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1087 // spd < |evel| * sin angle(evel, eorg)
1090 else if(solution.x > 0)
1092 // both solutions > 0: take the smaller one
1093 // happens if p < 0 and q > 0
1094 ret = normalize(eorg + solution.x * evel);
1096 else if(solution.y > 0)
1098 // one solution > 0: take the larger one
1099 // happens if q < 0 or q == 0 and p < 0
1100 ret = normalize(eorg + solution.y * evel);
1104 // no solution > 0: reject
1105 // happens if p > 0 and q >= 0
1106 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1107 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1112 // "Enemy is moving away from me at more than spd"
1116 // NOTE: we always got a solution if spd > |evel|
1118 if(newton_style == 2)
1119 ret = normalize(ret * spd + myvel);
1124 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1129 if(newton_style == 2)
1131 // true Newtonian projectiles with automatic aim adjustment
1133 // solve: |outspeed * mydir - myvel| = spd
1134 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1135 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1139 // myvel^2 - (mydir * myvel)^2 > spd^2
1140 // velocity without mydir component > spd
1141 // fire at smallest possible spd that works?
1142 // |(mydir * myvel) * myvel - myvel| = spd
1144 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1148 outspeed = solution.y; // the larger one
1151 //outspeed = 0; // slowest possible shot
1152 outspeed = solution.x; // the real part (that is, the average!)
1153 //dprint("impossible shot, adjusting\n");
1156 outspeed = bound(spd * mi, outspeed, spd * ma);
1157 return mydir * outspeed;
1161 return myvel + spd * mydir;
1164 float compressShotOrigin(vector v)
1166 float rx = rint(v.x * 2);
1167 float ry = rint(v.y * 4) + 128;
1168 float rz = rint(v.z * 4) + 128;
1169 if(rx > 255 || rx < 0)
1171 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1172 rx = bound(0, rx, 255);
1174 if(ry > 255 || ry < 0)
1176 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1177 ry = bound(0, ry, 255);
1179 if(rz > 255 || rz < 0)
1181 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1182 rz = bound(0, rz, 255);
1184 return rx * 0x10000 + ry * 0x100 + rz;
1186 vector decompressShotOrigin(int f)
1189 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1190 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1191 v.z = ((f & 0xFF) - 128) / 4;
1196 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1198 // NOTE: we'll always choose the SMALLER value...
1199 float healthdamage, armordamage, armorideal;
1200 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1203 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1204 armordamage = a + (h - 1); // damage we can take if we could use more armor
1205 armorideal = healthdamage * armorblock;
1207 if(armordamage < healthdamage)
1220 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1223 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1225 if (deathtype & HITTYPE_ARMORPIERCE)
1227 v.y = bound(0, damage * armorblock, a); // save
1228 v.x = bound(0, damage - v.y, damage); // take
1234 string getcurrentmod()
1238 m = cvar_string("fs_gamedir");
1239 n = tokenize_console(m);
1246 float matchacl(string acl, string str)
1253 t = car(acl); acl = cdr(acl);
1256 if(substring(t, 0, 1) == "-")
1259 t = substring(t, 1, strlen(t) - 1);
1261 else if(substring(t, 0, 1) == "+")
1262 t = substring(t, 1, strlen(t) - 1);
1264 if(substring(t, -1, 1) == "*")
1266 t = substring(t, 0, strlen(t) - 1);
1267 s = substring(str, 0, strlen(t));
1275 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1282 void write_String_To_File(int fh, string str, bool alsoprint)
1285 if (alsoprint) LOG_INFO(str);
1288 string get_model_datafilename(string m, float sk, string fil)
1293 m = "models/player/*_";
1295 m = strcat(m, ftos(sk));
1298 return strcat(m, ".", fil);
1301 float get_model_parameters(string m, float sk)
1303 get_model_parameters_modelname = string_null;
1304 get_model_parameters_modelskin = -1;
1305 get_model_parameters_name = string_null;
1306 get_model_parameters_species = -1;
1307 get_model_parameters_sex = string_null;
1308 get_model_parameters_weight = -1;
1309 get_model_parameters_age = -1;
1310 get_model_parameters_desc = string_null;
1311 get_model_parameters_bone_upperbody = string_null;
1312 get_model_parameters_bone_weapon = string_null;
1313 for(int i = 0; i < MAX_AIM_BONES; ++i)
1315 get_model_parameters_bone_aim[i] = string_null;
1316 get_model_parameters_bone_aimweight[i] = 0;
1318 get_model_parameters_fixbone = 0;
1319 get_model_parameters_hidden = false;
1322 MUTATOR_CALLHOOK(ClearModelParams);
1328 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1329 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1333 if(substring(m, -4, -1) != ".txt")
1335 if(substring(m, -6, 1) != "_")
1337 sk = stof(substring(m, -5, 1));
1338 m = substring(m, 0, -7);
1341 string fn = get_model_datafilename(m, sk, "txt");
1342 int fh = fopen(fn, FILE_READ);
1346 fn = get_model_datafilename(m, sk, "txt");
1347 fh = fopen(fn, FILE_READ);
1352 get_model_parameters_modelname = m;
1353 get_model_parameters_modelskin = sk;
1355 while((s = fgets(fh)))
1358 break; // next lines will be description
1362 get_model_parameters_name = s;
1366 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1367 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1368 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1369 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1370 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1371 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1372 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1376 if (s == "Male") s = _("Male");
1377 else if (s == "Female") s = _("Female");
1378 else if (s == "Undisclosed") s = _("Undisclosed");
1379 get_model_parameters_sex = s;
1382 get_model_parameters_weight = stof(s);
1384 get_model_parameters_age = stof(s);
1385 if(c == "description")
1386 get_model_parameters_description = s;
1387 if(c == "bone_upperbody")
1388 get_model_parameters_bone_upperbody = s;
1389 if(c == "bone_weapon")
1390 get_model_parameters_bone_weapon = s;
1392 MUTATOR_CALLHOOK(GetModelParams, c, s);
1394 for(int i = 0; i < MAX_AIM_BONES; ++i)
1395 if(c == strcat("bone_aim", ftos(i)))
1397 get_model_parameters_bone_aimweight[i] = stof(car(s));
1398 get_model_parameters_bone_aim[i] = cdr(s);
1401 get_model_parameters_fixbone = stof(s);
1403 get_model_parameters_hidden = stob(s);
1406 while((s = fgets(fh)))
1408 if(get_model_parameters_desc)
1409 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1411 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1419 string translate_key(string key)
1421 if (prvm_language == "en") return key;
1423 if (substring(key, 0, 1) == "<")
1425 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1426 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1431 case "TAB": return _("TAB");
1432 case "ENTER": return _("ENTER");
1433 case "ESCAPE": return _("ESCAPE");
1434 case "SPACE": return _("SPACE");
1436 case "BACKSPACE": return _("BACKSPACE");
1437 case "UPARROW": return _("UPARROW");
1438 case "DOWNARROW": return _("DOWNARROW");
1439 case "LEFTARROW": return _("LEFTARROW");
1440 case "RIGHTARROW": return _("RIGHTARROW");
1442 case "ALT": return _("ALT");
1443 case "CTRL": return _("CTRL");
1444 case "SHIFT": return _("SHIFT");
1446 case "INS": return _("INS");
1447 case "DEL": return _("DEL");
1448 case "PGDN": return _("PGDN");
1449 case "PGUP": return _("PGUP");
1450 case "HOME": return _("HOME");
1451 case "END": return _("END");
1453 case "PAUSE": return _("PAUSE");
1455 case "NUMLOCK": return _("NUMLOCK");
1456 case "CAPSLOCK": return _("CAPSLOCK");
1457 case "SCROLLOCK": return _("SCROLLOCK");
1459 case "SEMICOLON": return _("SEMICOLON");
1460 case "TILDE": return _("TILDE");
1461 case "BACKQUOTE": return _("BACKQUOTE");
1462 case "QUOTE": return _("QUOTE");
1463 case "APOSTROPHE": return _("APOSTROPHE");
1464 case "BACKSLASH": return _("BACKSLASH");
1467 if (substring(key, 0, 1) == "F")
1469 string subkey = substring(key, 1, -1);
1470 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1472 return sprintf(_("F%d"), stof(subkey));
1474 // continue in case there is another key name starting with F
1477 if (substring(key, 0, 3) == "KP_")
1479 string subkey = substring(key, 3, -1);
1480 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1482 return sprintf(_("KP_%d"), stof(subkey));
1487 case "INS": return sprintf(_("KP_%s"), _("INS"));
1488 case "END": return sprintf(_("KP_%s"), _("END"));
1489 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1490 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1491 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1492 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1493 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1494 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1495 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1496 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1497 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1498 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1499 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1500 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1501 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1502 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1503 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1504 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1505 default: return key;
1509 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1511 if (substring(key, 0, 5) == "MOUSE")
1512 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1514 if (key == "MWHEELUP") return _("MWHEELUP");
1515 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1517 if (substring(key, 0,3) == "JOY")
1518 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1520 if (substring(key, 0,3) == "AUX")
1521 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1523 if (substring(key, 0, 4) == "X360_")
1525 string subkey = substring(key, 4, -1);
1528 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1529 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1530 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1531 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1532 case "START": return sprintf(_("X360_%s"), _("START"));
1533 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1534 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1535 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1536 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1537 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1538 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1539 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1540 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1541 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1542 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1543 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1544 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1545 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1546 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1547 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1548 default: return key;
1552 if (substring(key, 0, 4) == "JOY_")
1554 string subkey = substring(key, 4, -1);
1557 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1558 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1559 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1560 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1561 default: return key;
1565 if (substring(key, 0, 8) == "MIDINOTE")
1566 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1571 // x-encoding (encoding as zero length invisible string)
1572 const string XENCODE_2 = "xX";
1573 const string XENCODE_22 = "0123456789abcdefABCDEF";
1574 string xencode(int f)
1577 d = f % 22; f = floor(f / 22);
1578 c = f % 22; f = floor(f / 22);
1579 b = f % 22; f = floor(f / 22);
1580 a = f % 2; // f = floor(f / 2);
1583 substring(XENCODE_2, a, 1),
1584 substring(XENCODE_22, b, 1),
1585 substring(XENCODE_22, c, 1),
1586 substring(XENCODE_22, d, 1)
1589 float xdecode(string s)
1592 if(substring(s, 0, 1) != "^")
1596 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1597 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1598 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1599 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1600 if(a < 0 || b < 0 || c < 0 || d < 0)
1602 return ((a * 22 + b) * 22 + c) * 22 + d;
1606 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1608 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1611 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1614 float shutdown_running;
1619 void CSQC_Shutdown()
1625 if(shutdown_running)
1627 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1631 shutdown_running = 1;
1635 cvar_settemp_restore(); // this must be done LAST, but in any case
1639 .float skeleton_bones_index;
1640 void Skeleton_SetBones(entity e)
1642 // set skeleton_bones to the total number of bones on the model
1643 if(e.skeleton_bones_index == e.modelindex)
1644 return; // same model, nothing to update
1647 skelindex = skel_create(e.modelindex);
1648 e.skeleton_bones = skel_get_numbones(skelindex);
1649 skel_delete(skelindex);
1650 e.skeleton_bones_index = e.modelindex;
1654 string to_execute_next_frame;
1655 void execute_next_frame()
1657 if(to_execute_next_frame)
1659 localcmd("\n", to_execute_next_frame, "\n");
1660 strfree(to_execute_next_frame);
1663 void queue_to_execute_next_frame(string s)
1665 if(to_execute_next_frame)
1667 s = strcat(s, "\n", to_execute_next_frame);
1669 strcpy(to_execute_next_frame, s);
1672 .float FindConnectedComponent_processing;
1673 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1675 entity queue_start, queue_end;
1677 // we build a queue of to-be-processed entities.
1678 // queue_start is the next entity to be checked for neighbors
1679 // queue_end is the last entity added
1681 if(e.FindConnectedComponent_processing)
1682 error("recursion or broken cleanup");
1684 // start with a 1-element queue
1685 queue_start = queue_end = e;
1686 queue_end.(fld) = NULL;
1687 queue_end.FindConnectedComponent_processing = 1;
1689 // for each queued item:
1690 for (; queue_start; queue_start = queue_start.(fld))
1692 // find all neighbors of queue_start
1694 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1696 if(t.FindConnectedComponent_processing)
1698 if(iscon(t, queue_start, pass))
1700 // it is connected? ADD IT. It will look for neighbors soon too.
1701 queue_end.(fld) = t;
1703 queue_end.(fld) = NULL;
1704 queue_end.FindConnectedComponent_processing = 1;
1710 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1711 queue_start.FindConnectedComponent_processing = 0;
1715 vector animfixfps(entity e, vector a, vector b)
1717 // multi-frame anim: keep as-is
1720 float dur = frameduration(e.modelindex, a.x);
1721 if (dur <= 0 && b.y)
1724 dur = frameduration(e.modelindex, a.x);
1734 Notification Announcer_PickNumber(int type, int num)
1743 case 10: return ANNCE_NUM_GAMESTART_10;
1744 case 9: return ANNCE_NUM_GAMESTART_9;
1745 case 8: return ANNCE_NUM_GAMESTART_8;
1746 case 7: return ANNCE_NUM_GAMESTART_7;
1747 case 6: return ANNCE_NUM_GAMESTART_6;
1748 case 5: return ANNCE_NUM_GAMESTART_5;
1749 case 4: return ANNCE_NUM_GAMESTART_4;
1750 case 3: return ANNCE_NUM_GAMESTART_3;
1751 case 2: return ANNCE_NUM_GAMESTART_2;
1752 case 1: return ANNCE_NUM_GAMESTART_1;
1760 case 10: return ANNCE_NUM_IDLE_10;
1761 case 9: return ANNCE_NUM_IDLE_9;
1762 case 8: return ANNCE_NUM_IDLE_8;
1763 case 7: return ANNCE_NUM_IDLE_7;
1764 case 6: return ANNCE_NUM_IDLE_6;
1765 case 5: return ANNCE_NUM_IDLE_5;
1766 case 4: return ANNCE_NUM_IDLE_4;
1767 case 3: return ANNCE_NUM_IDLE_3;
1768 case 2: return ANNCE_NUM_IDLE_2;
1769 case 1: return ANNCE_NUM_IDLE_1;
1777 case 10: return ANNCE_NUM_KILL_10;
1778 case 9: return ANNCE_NUM_KILL_9;
1779 case 8: return ANNCE_NUM_KILL_8;
1780 case 7: return ANNCE_NUM_KILL_7;
1781 case 6: return ANNCE_NUM_KILL_6;
1782 case 5: return ANNCE_NUM_KILL_5;
1783 case 4: return ANNCE_NUM_KILL_4;
1784 case 3: return ANNCE_NUM_KILL_3;
1785 case 2: return ANNCE_NUM_KILL_2;
1786 case 1: return ANNCE_NUM_KILL_1;
1794 case 10: return ANNCE_NUM_RESPAWN_10;
1795 case 9: return ANNCE_NUM_RESPAWN_9;
1796 case 8: return ANNCE_NUM_RESPAWN_8;
1797 case 7: return ANNCE_NUM_RESPAWN_7;
1798 case 6: return ANNCE_NUM_RESPAWN_6;
1799 case 5: return ANNCE_NUM_RESPAWN_5;
1800 case 4: return ANNCE_NUM_RESPAWN_4;
1801 case 3: return ANNCE_NUM_RESPAWN_3;
1802 case 2: return ANNCE_NUM_RESPAWN_2;
1803 case 1: return ANNCE_NUM_RESPAWN_1;
1807 case CNT_ROUNDSTART:
1811 case 10: return ANNCE_NUM_ROUNDSTART_10;
1812 case 9: return ANNCE_NUM_ROUNDSTART_9;
1813 case 8: return ANNCE_NUM_ROUNDSTART_8;
1814 case 7: return ANNCE_NUM_ROUNDSTART_7;
1815 case 6: return ANNCE_NUM_ROUNDSTART_6;
1816 case 5: return ANNCE_NUM_ROUNDSTART_5;
1817 case 4: return ANNCE_NUM_ROUNDSTART_4;
1818 case 3: return ANNCE_NUM_ROUNDSTART_3;
1819 case 2: return ANNCE_NUM_ROUNDSTART_2;
1820 case 1: return ANNCE_NUM_ROUNDSTART_1;
1828 case 10: return ANNCE_NUM_10;
1829 case 9: return ANNCE_NUM_9;
1830 case 8: return ANNCE_NUM_8;
1831 case 7: return ANNCE_NUM_7;
1832 case 6: return ANNCE_NUM_6;
1833 case 5: return ANNCE_NUM_5;
1834 case 4: return ANNCE_NUM_4;
1835 case 3: return ANNCE_NUM_3;
1836 case 2: return ANNCE_NUM_2;
1837 case 1: return ANNCE_NUM_1;
1846 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1848 switch(nativecontents)
1853 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1855 return DPCONTENTS_WATER;
1857 return DPCONTENTS_SLIME;
1859 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1861 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1866 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1868 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1869 return CONTENT_SOLID;
1870 if(supercontents & DPCONTENTS_SKY)
1872 if(supercontents & DPCONTENTS_LAVA)
1873 return CONTENT_LAVA;
1874 if(supercontents & DPCONTENTS_SLIME)
1875 return CONTENT_SLIME;
1876 if(supercontents & DPCONTENTS_WATER)
1877 return CONTENT_WATER;
1878 return CONTENT_EMPTY;
1883 void attach_sameorigin(entity e, entity to, string tag)
1885 vector org, t_forward, t_left, t_up, e_forward, e_up;
1888 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1889 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1890 t_forward = v_forward * tagscale;
1891 t_left = v_right * -tagscale;
1892 t_up = v_up * tagscale;
1894 e.origin_x = org * t_forward;
1895 e.origin_y = org * t_left;
1896 e.origin_z = org * t_up;
1898 // current forward and up directions
1899 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1900 e.angles = AnglesTransform_FromVAngles(e.angles);
1902 e.angles = AnglesTransform_FromAngles(e.angles);
1903 fixedmakevectors(e.angles);
1905 // untransform forward, up!
1906 e_forward.x = v_forward * t_forward;
1907 e_forward.y = v_forward * t_left;
1908 e_forward.z = v_forward * t_up;
1909 e_up.x = v_up * t_forward;
1910 e_up.y = v_up * t_left;
1911 e_up.z = v_up * t_up;
1913 e.angles = fixedvectoangles2(e_forward, e_up);
1914 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1915 e.angles = AnglesTransform_ToVAngles(e.angles);
1917 e.angles = AnglesTransform_ToAngles(e.angles);
1919 setattachment(e, to, tag);
1920 setorigin(e, e.origin);
1923 void detach_sameorigin(entity e)
1926 org = gettaginfo(e, 0);
1927 e.angles = fixedvectoangles2(v_forward, v_up);
1928 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1929 e.angles = AnglesTransform_ToVAngles(e.angles);
1931 e.angles = AnglesTransform_ToAngles(e.angles);
1933 setattachment(e, NULL, "");
1934 setorigin(e, e.origin);
1937 void follow_sameorigin(entity e, entity to)
1939 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1940 e.aiment = to; // make the hole follow bmodel
1941 e.punchangle = to.angles; // the original angles of bmodel
1942 e.view_ofs = e.origin - to.origin; // relative origin
1943 e.v_angle = e.angles - to.angles; // relative angles
1947 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1948 void unfollow_sameorigin(entity e)
1950 set_movetype(e, MOVETYPE_NONE);
1954 .string aiment_classname;
1955 .float aiment_deadflag;
1956 void SetMovetypeFollow(entity ent, entity e)
1958 // FIXME this may not be warpzone aware
1959 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1960 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.
1961 ent.aiment = e; // make the hole follow bmodel
1962 ent.punchangle = e.angles; // the original angles of bmodel
1963 ent.view_ofs = ent.origin - e.origin; // relative origin
1964 ent.v_angle = ent.angles - e.angles; // relative angles
1965 ent.aiment_classname = strzone(e.classname);
1966 ent.aiment_deadflag = e.deadflag;
1968 void UnsetMovetypeFollow(entity ent)
1970 set_movetype(ent, MOVETYPE_FLY);
1971 PROJECTILE_MAKETRIGGER(ent);
1974 float LostMovetypeFollow(entity ent)
1977 if(ent.move_movetype != MOVETYPE_FOLLOW)
1983 if(ent.aiment.classname != ent.aiment_classname)
1985 if(ent.aiment.deadflag != ent.aiment_deadflag)
1993 // decolorizes and team colors the player name when needed
1994 string playername(string thename, int teamid, bool team_colorize)
1997 bool do_colorize = (teamplay && team_colorize);
1999 if(do_colorize && !intermission_running)
2004 string t = Team_ColorCode(teamid);
2005 return strcat(t, strdecolorize(thename));
2011 float trace_hits_box_a0, trace_hits_box_a1;
2013 float trace_hits_box_1d(float end, float thmi, float thma)
2017 // just check if x is in range
2025 // do the trace with respect to x
2026 // 0 -> end has to stay in thmi -> thma
2027 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2028 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2029 if (trace_hits_box_a0 > trace_hits_box_a1)
2035 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2040 // now it is a trace from 0 to end
2042 trace_hits_box_a0 = 0;
2043 trace_hits_box_a1 = 1;
2045 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2047 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2049 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2055 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2057 return trace_hits_box(start, end, thmi - ma, thma - mi);
2062 float cvar_or(string cv, float v)
2064 string s = cvar_string(cv);
2071 // NOTE base is the central value
2072 // freq: circle frequency, = 2*pi*frequency in hertz
2074 // -1 start from the lower value
2075 // 0 start from the base value
2076 // 1 start from the higher value
2078 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2081 // RMS = sqrt(base^2 + 0.5 * range^2)
2083 // base = sqrt(RMS^2 - 0.5 * range^2)
2086 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2090 float blink(float base, float range, float freq)
2092 return blink_synced(base, range, freq, 0, 0);