4 #include "constants.qh"
5 #include <client/mutators/_mod.qh>
7 #include "notifications/all.qh"
9 #include <common/deathtypes/all.qh>
10 #include <common/gamemodes/_mod.qh>
13 #include "constants.qh"
14 #include <server/mutators/_mod.qh>
15 #include "notifications/all.qh"
16 #include <common/deathtypes/all.qh>
17 #include <common/gamemodes/_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) != " ")
945 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
946 if(getWrappedLine_remaining == "")
947 getWrappedLine_remaining = string_null;
948 else if (tw("^7", theFontSize) == 0)
949 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
950 return substring(s, 0, cantake);
954 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
955 if(getWrappedLine_remaining == "")
956 getWrappedLine_remaining = string_null;
957 else if (tw("^7", theFontSize) == 0)
958 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
959 return substring(s, 0, take);
964 getWrappedLine_remaining = string_null;
969 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
975 s = getWrappedLine_remaining;
979 getWrappedLine_remaining = string_null;
980 return s; // the line has no size ANYWAY, nothing would be displayed.
983 cantake = textLengthUpToLength(s, w, tw);
984 if(cantake > 0 && cantake < strlen(s))
987 while(take > 0 && substring(s, take, 1) != " ")
991 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
992 if(getWrappedLine_remaining == "")
993 getWrappedLine_remaining = string_null;
994 else if (tw("^7") == 0)
995 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
996 return substring(s, 0, cantake);
1000 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1001 if(getWrappedLine_remaining == "")
1002 getWrappedLine_remaining = string_null;
1003 else if (tw("^7") == 0)
1004 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1005 return substring(s, 0, take);
1010 getWrappedLine_remaining = string_null;
1015 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1017 if(tw(theText, theFontSize) <= maxWidth)
1020 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1023 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1025 if(tw(theText) <= maxWidth)
1028 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1031 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1033 string subpattern, subpattern2, subpattern3, subpattern4;
1034 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1036 subpattern2 = ",teams,";
1038 subpattern2 = ",noteams,";
1040 subpattern3 = ",teamspawns,";
1042 subpattern3 = ",noteamspawns,";
1043 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1044 subpattern4 = ",race,";
1046 subpattern4 = string_null;
1048 if(substring(pattern, 0, 1) == "-")
1050 pattern = substring(pattern, 1, strlen(pattern) - 1);
1051 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1053 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1055 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1057 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1062 if(substring(pattern, 0, 1) == "+")
1063 pattern = substring(pattern, 1, strlen(pattern) - 1);
1064 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1065 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1066 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1070 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1077 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1081 // make origin and speed relative
1086 // now solve for ret, ret normalized:
1087 // eorg + t * evel == t * ret * spd
1088 // or, rather, solve for t:
1089 // |eorg + t * evel| == t * spd
1090 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1091 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1092 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1093 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1094 // q = (eorg * eorg) / (evel * evel - spd * spd)
1095 if(!solution.z) // no real solution
1098 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1099 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1100 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1101 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1102 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1103 // spd < |evel| * sin angle(evel, eorg)
1106 else if(solution.x > 0)
1108 // both solutions > 0: take the smaller one
1109 // happens if p < 0 and q > 0
1110 ret = normalize(eorg + solution.x * evel);
1112 else if(solution.y > 0)
1114 // one solution > 0: take the larger one
1115 // happens if q < 0 or q == 0 and p < 0
1116 ret = normalize(eorg + solution.y * evel);
1120 // no solution > 0: reject
1121 // happens if p > 0 and q >= 0
1122 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1123 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1128 // "Enemy is moving away from me at more than spd"
1132 // NOTE: we always got a solution if spd > |evel|
1134 if(newton_style == 2)
1135 ret = normalize(ret * spd + myvel);
1140 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1145 if(newton_style == 2)
1147 // true Newtonian projectiles with automatic aim adjustment
1149 // solve: |outspeed * mydir - myvel| = spd
1150 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1151 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1155 // myvel^2 - (mydir * myvel)^2 > spd^2
1156 // velocity without mydir component > spd
1157 // fire at smallest possible spd that works?
1158 // |(mydir * myvel) * myvel - myvel| = spd
1160 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1164 outspeed = solution.y; // the larger one
1167 //outspeed = 0; // slowest possible shot
1168 outspeed = solution.x; // the real part (that is, the average!)
1169 //dprint("impossible shot, adjusting\n");
1172 outspeed = bound(spd * mi, outspeed, spd * ma);
1173 return mydir * outspeed;
1177 return myvel + spd * mydir;
1180 float compressShotOrigin(vector v)
1182 float rx = rint(v.x * 2);
1183 float ry = rint(v.y * 4) + 128;
1184 float rz = rint(v.z * 4) + 128;
1185 if(rx > 255 || rx < 0)
1187 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1188 rx = bound(0, rx, 255);
1190 if(ry > 255 || ry < 0)
1192 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1193 ry = bound(0, ry, 255);
1195 if(rz > 255 || rz < 0)
1197 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1198 rz = bound(0, rz, 255);
1200 return rx * 0x10000 + ry * 0x100 + rz;
1202 vector decompressShotOrigin(int f)
1205 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1206 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1207 v.z = ((f & 0xFF) - 128) / 4;
1212 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1214 // NOTE: we'll always choose the SMALLER value...
1215 float healthdamage, armordamage, armorideal;
1216 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1219 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1220 armordamage = a + (h - 1); // damage we can take if we could use more armor
1221 armorideal = healthdamage * armorblock;
1223 if(armordamage < healthdamage)
1236 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1239 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1241 if (deathtype & HITTYPE_ARMORPIERCE)
1243 v.y = bound(0, damage * armorblock, a); // save
1244 v.x = bound(0, damage - v.y, damage); // take
1250 string getcurrentmod()
1254 m = cvar_string("fs_gamedir");
1255 n = tokenize_console(m);
1262 float matchacl(string acl, string str)
1269 t = car(acl); acl = cdr(acl);
1272 if(substring(t, 0, 1) == "-")
1275 t = substring(t, 1, strlen(t) - 1);
1277 else if(substring(t, 0, 1) == "+")
1278 t = substring(t, 1, strlen(t) - 1);
1280 if(substring(t, -1, 1) == "*")
1282 t = substring(t, 0, strlen(t) - 1);
1283 s = substring(str, 0, strlen(t));
1291 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1298 void write_String_To_File(int fh, string str, bool alsoprint)
1301 if (alsoprint) LOG_INFO(str);
1304 string get_model_datafilename(string m, float sk, string fil)
1309 m = "models/player/*_";
1311 m = strcat(m, ftos(sk));
1314 return strcat(m, ".", fil);
1317 float get_model_parameters(string m, float sk)
1319 get_model_parameters_modelname = string_null;
1320 get_model_parameters_modelskin = -1;
1321 get_model_parameters_name = string_null;
1322 get_model_parameters_species = -1;
1323 get_model_parameters_sex = string_null;
1324 get_model_parameters_weight = -1;
1325 get_model_parameters_age = -1;
1326 get_model_parameters_desc = string_null;
1327 get_model_parameters_bone_upperbody = string_null;
1328 get_model_parameters_bone_weapon = string_null;
1329 for(int i = 0; i < MAX_AIM_BONES; ++i)
1331 get_model_parameters_bone_aim[i] = string_null;
1332 get_model_parameters_bone_aimweight[i] = 0;
1334 get_model_parameters_fixbone = 0;
1335 get_model_parameters_hidden = false;
1338 MUTATOR_CALLHOOK(ClearModelParams);
1344 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1345 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1349 if(substring(m, -4, -1) != ".txt")
1351 if(substring(m, -6, 1) != "_")
1353 sk = stof(substring(m, -5, 1));
1354 m = substring(m, 0, -7);
1357 string fn = get_model_datafilename(m, sk, "txt");
1358 int fh = fopen(fn, FILE_READ);
1362 fn = get_model_datafilename(m, sk, "txt");
1363 fh = fopen(fn, FILE_READ);
1368 get_model_parameters_modelname = m;
1369 get_model_parameters_modelskin = sk;
1371 while((s = fgets(fh)))
1374 break; // next lines will be description
1378 get_model_parameters_name = s;
1382 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1383 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1384 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1385 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1386 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1387 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1388 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1392 if (s == "Male") s = _("Male");
1393 else if (s == "Female") s = _("Female");
1394 else if (s == "Undisclosed") s = _("Undisclosed");
1395 get_model_parameters_sex = s;
1398 get_model_parameters_weight = stof(s);
1400 get_model_parameters_age = stof(s);
1401 if(c == "description")
1402 get_model_parameters_description = s;
1403 if(c == "bone_upperbody")
1404 get_model_parameters_bone_upperbody = s;
1405 if(c == "bone_weapon")
1406 get_model_parameters_bone_weapon = s;
1408 MUTATOR_CALLHOOK(GetModelParams, c, s);
1410 for(int i = 0; i < MAX_AIM_BONES; ++i)
1411 if(c == strcat("bone_aim", ftos(i)))
1413 get_model_parameters_bone_aimweight[i] = stof(car(s));
1414 get_model_parameters_bone_aim[i] = cdr(s);
1417 get_model_parameters_fixbone = stof(s);
1419 get_model_parameters_hidden = stob(s);
1422 while((s = fgets(fh)))
1424 if(get_model_parameters_desc)
1425 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1427 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1435 string translate_key(string key)
1437 if (prvm_language == "en") return key;
1439 if (substring(key, 0, 1) == "<")
1441 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1442 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1447 case "TAB": return _("TAB");
1448 case "ENTER": return _("ENTER");
1449 case "ESCAPE": return _("ESCAPE");
1450 case "SPACE": return _("SPACE");
1452 case "BACKSPACE": return _("BACKSPACE");
1453 case "UPARROW": return _("UPARROW");
1454 case "DOWNARROW": return _("DOWNARROW");
1455 case "LEFTARROW": return _("LEFTARROW");
1456 case "RIGHTARROW": return _("RIGHTARROW");
1458 case "ALT": return _("ALT");
1459 case "CTRL": return _("CTRL");
1460 case "SHIFT": return _("SHIFT");
1462 case "INS": return _("INS");
1463 case "DEL": return _("DEL");
1464 case "PGDN": return _("PGDN");
1465 case "PGUP": return _("PGUP");
1466 case "HOME": return _("HOME");
1467 case "END": return _("END");
1469 case "PAUSE": return _("PAUSE");
1471 case "NUMLOCK": return _("NUMLOCK");
1472 case "CAPSLOCK": return _("CAPSLOCK");
1473 case "SCROLLOCK": return _("SCROLLOCK");
1475 case "SEMICOLON": return _("SEMICOLON");
1476 case "TILDE": return _("TILDE");
1477 case "BACKQUOTE": return _("BACKQUOTE");
1478 case "QUOTE": return _("QUOTE");
1479 case "APOSTROPHE": return _("APOSTROPHE");
1480 case "BACKSLASH": return _("BACKSLASH");
1483 if (substring(key, 0, 1) == "F")
1485 string subkey = substring(key, 1, -1);
1486 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1488 return sprintf(_("F%d"), stof(subkey));
1490 // continue in case there is another key name starting with F
1493 if (substring(key, 0, 3) == "KP_")
1495 string subkey = substring(key, 3, -1);
1496 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1498 return sprintf(_("KP_%d"), stof(subkey));
1503 case "INS": return sprintf(_("KP_%s"), _("INS"));
1504 case "END": return sprintf(_("KP_%s"), _("END"));
1505 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1506 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1507 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1508 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1509 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1510 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1511 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1512 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1513 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1514 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1515 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1516 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1517 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1518 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1519 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1520 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1521 default: return key;
1525 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1527 if (substring(key, 0, 5) == "MOUSE")
1528 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1530 if (key == "MWHEELUP") return _("MWHEELUP");
1531 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1533 if (substring(key, 0,3) == "JOY")
1534 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1536 if (substring(key, 0,3) == "AUX")
1537 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1539 if (substring(key, 0, 4) == "X360_")
1541 string subkey = substring(key, 4, -1);
1544 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1545 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1546 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1547 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1548 case "START": return sprintf(_("X360_%s"), _("START"));
1549 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1550 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1551 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1552 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1553 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1554 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1555 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1556 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1557 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1558 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1559 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1560 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1561 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1562 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1563 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1564 default: return key;
1568 if (substring(key, 0, 4) == "JOY_")
1570 string subkey = substring(key, 4, -1);
1573 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1574 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1575 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1576 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1577 default: return key;
1581 if (substring(key, 0, 8) == "MIDINOTE")
1582 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1587 // x-encoding (encoding as zero length invisible string)
1588 const string XENCODE_2 = "xX";
1589 const string XENCODE_22 = "0123456789abcdefABCDEF";
1590 string xencode(int f)
1593 d = f % 22; f = floor(f / 22);
1594 c = f % 22; f = floor(f / 22);
1595 b = f % 22; f = floor(f / 22);
1596 a = f % 2; // f = floor(f / 2);
1599 substring(XENCODE_2, a, 1),
1600 substring(XENCODE_22, b, 1),
1601 substring(XENCODE_22, c, 1),
1602 substring(XENCODE_22, d, 1)
1605 float xdecode(string s)
1608 if(substring(s, 0, 1) != "^")
1612 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1613 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1614 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1615 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1616 if(a < 0 || b < 0 || c < 0 || d < 0)
1618 return ((a * 22 + b) * 22 + c) * 22 + d;
1622 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1624 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1627 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1630 float shutdown_running;
1635 void CSQC_Shutdown()
1641 if(shutdown_running)
1643 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1647 shutdown_running = 1;
1651 cvar_settemp_restore(); // this must be done LAST, but in any case
1655 .float skeleton_bones_index;
1656 void Skeleton_SetBones(entity e)
1658 // set skeleton_bones to the total number of bones on the model
1659 if(e.skeleton_bones_index == e.modelindex)
1660 return; // same model, nothing to update
1663 skelindex = skel_create(e.modelindex);
1664 e.skeleton_bones = skel_get_numbones(skelindex);
1665 skel_delete(skelindex);
1666 e.skeleton_bones_index = e.modelindex;
1670 string to_execute_next_frame;
1671 void execute_next_frame()
1673 if(to_execute_next_frame)
1675 localcmd("\n", to_execute_next_frame, "\n");
1676 strfree(to_execute_next_frame);
1679 void queue_to_execute_next_frame(string s)
1681 if(to_execute_next_frame)
1683 s = strcat(s, "\n", to_execute_next_frame);
1685 strcpy(to_execute_next_frame, s);
1688 .float FindConnectedComponent_processing;
1689 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1691 entity queue_start, queue_end;
1693 // we build a queue of to-be-processed entities.
1694 // queue_start is the next entity to be checked for neighbors
1695 // queue_end is the last entity added
1697 if(e.FindConnectedComponent_processing)
1698 error("recursion or broken cleanup");
1700 // start with a 1-element queue
1701 queue_start = queue_end = e;
1702 queue_end.(fld) = NULL;
1703 queue_end.FindConnectedComponent_processing = 1;
1705 // for each queued item:
1706 for (; queue_start; queue_start = queue_start.(fld))
1708 // find all neighbors of queue_start
1710 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1712 if(t.FindConnectedComponent_processing)
1714 if(iscon(t, queue_start, pass))
1716 // it is connected? ADD IT. It will look for neighbors soon too.
1717 queue_end.(fld) = t;
1719 queue_end.(fld) = NULL;
1720 queue_end.FindConnectedComponent_processing = 1;
1726 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1727 queue_start.FindConnectedComponent_processing = 0;
1731 vector animfixfps(entity e, vector a, vector b)
1733 // multi-frame anim: keep as-is
1736 float dur = frameduration(e.modelindex, a.x);
1737 if (dur <= 0 && b.y)
1740 dur = frameduration(e.modelindex, a.x);
1750 Notification Announcer_PickNumber(int type, int num)
1759 case 10: return ANNCE_NUM_GAMESTART_10;
1760 case 9: return ANNCE_NUM_GAMESTART_9;
1761 case 8: return ANNCE_NUM_GAMESTART_8;
1762 case 7: return ANNCE_NUM_GAMESTART_7;
1763 case 6: return ANNCE_NUM_GAMESTART_6;
1764 case 5: return ANNCE_NUM_GAMESTART_5;
1765 case 4: return ANNCE_NUM_GAMESTART_4;
1766 case 3: return ANNCE_NUM_GAMESTART_3;
1767 case 2: return ANNCE_NUM_GAMESTART_2;
1768 case 1: return ANNCE_NUM_GAMESTART_1;
1776 case 10: return ANNCE_NUM_IDLE_10;
1777 case 9: return ANNCE_NUM_IDLE_9;
1778 case 8: return ANNCE_NUM_IDLE_8;
1779 case 7: return ANNCE_NUM_IDLE_7;
1780 case 6: return ANNCE_NUM_IDLE_6;
1781 case 5: return ANNCE_NUM_IDLE_5;
1782 case 4: return ANNCE_NUM_IDLE_4;
1783 case 3: return ANNCE_NUM_IDLE_3;
1784 case 2: return ANNCE_NUM_IDLE_2;
1785 case 1: return ANNCE_NUM_IDLE_1;
1793 case 10: return ANNCE_NUM_KILL_10;
1794 case 9: return ANNCE_NUM_KILL_9;
1795 case 8: return ANNCE_NUM_KILL_8;
1796 case 7: return ANNCE_NUM_KILL_7;
1797 case 6: return ANNCE_NUM_KILL_6;
1798 case 5: return ANNCE_NUM_KILL_5;
1799 case 4: return ANNCE_NUM_KILL_4;
1800 case 3: return ANNCE_NUM_KILL_3;
1801 case 2: return ANNCE_NUM_KILL_2;
1802 case 1: return ANNCE_NUM_KILL_1;
1810 case 10: return ANNCE_NUM_RESPAWN_10;
1811 case 9: return ANNCE_NUM_RESPAWN_9;
1812 case 8: return ANNCE_NUM_RESPAWN_8;
1813 case 7: return ANNCE_NUM_RESPAWN_7;
1814 case 6: return ANNCE_NUM_RESPAWN_6;
1815 case 5: return ANNCE_NUM_RESPAWN_5;
1816 case 4: return ANNCE_NUM_RESPAWN_4;
1817 case 3: return ANNCE_NUM_RESPAWN_3;
1818 case 2: return ANNCE_NUM_RESPAWN_2;
1819 case 1: return ANNCE_NUM_RESPAWN_1;
1823 case CNT_ROUNDSTART:
1827 case 10: return ANNCE_NUM_ROUNDSTART_10;
1828 case 9: return ANNCE_NUM_ROUNDSTART_9;
1829 case 8: return ANNCE_NUM_ROUNDSTART_8;
1830 case 7: return ANNCE_NUM_ROUNDSTART_7;
1831 case 6: return ANNCE_NUM_ROUNDSTART_6;
1832 case 5: return ANNCE_NUM_ROUNDSTART_5;
1833 case 4: return ANNCE_NUM_ROUNDSTART_4;
1834 case 3: return ANNCE_NUM_ROUNDSTART_3;
1835 case 2: return ANNCE_NUM_ROUNDSTART_2;
1836 case 1: return ANNCE_NUM_ROUNDSTART_1;
1844 case 10: return ANNCE_NUM_10;
1845 case 9: return ANNCE_NUM_9;
1846 case 8: return ANNCE_NUM_8;
1847 case 7: return ANNCE_NUM_7;
1848 case 6: return ANNCE_NUM_6;
1849 case 5: return ANNCE_NUM_5;
1850 case 4: return ANNCE_NUM_4;
1851 case 3: return ANNCE_NUM_3;
1852 case 2: return ANNCE_NUM_2;
1853 case 1: return ANNCE_NUM_1;
1862 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1864 switch(nativecontents)
1869 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1871 return DPCONTENTS_WATER;
1873 return DPCONTENTS_SLIME;
1875 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1877 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1882 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1884 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1885 return CONTENT_SOLID;
1886 if(supercontents & DPCONTENTS_SKY)
1888 if(supercontents & DPCONTENTS_LAVA)
1889 return CONTENT_LAVA;
1890 if(supercontents & DPCONTENTS_SLIME)
1891 return CONTENT_SLIME;
1892 if(supercontents & DPCONTENTS_WATER)
1893 return CONTENT_WATER;
1894 return CONTENT_EMPTY;
1899 void attach_sameorigin(entity e, entity to, string tag)
1901 vector org, t_forward, t_left, t_up, e_forward, e_up;
1904 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1905 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1906 t_forward = v_forward * tagscale;
1907 t_left = v_right * -tagscale;
1908 t_up = v_up * tagscale;
1910 e.origin_x = org * t_forward;
1911 e.origin_y = org * t_left;
1912 e.origin_z = org * t_up;
1914 // current forward and up directions
1915 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1916 e.angles = AnglesTransform_FromVAngles(e.angles);
1918 e.angles = AnglesTransform_FromAngles(e.angles);
1919 fixedmakevectors(e.angles);
1921 // untransform forward, up!
1922 e_forward.x = v_forward * t_forward;
1923 e_forward.y = v_forward * t_left;
1924 e_forward.z = v_forward * t_up;
1925 e_up.x = v_up * t_forward;
1926 e_up.y = v_up * t_left;
1927 e_up.z = v_up * t_up;
1929 e.angles = fixedvectoangles2(e_forward, e_up);
1930 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1931 e.angles = AnglesTransform_ToVAngles(e.angles);
1933 e.angles = AnglesTransform_ToAngles(e.angles);
1935 setattachment(e, to, tag);
1936 setorigin(e, e.origin);
1939 void detach_sameorigin(entity e)
1942 org = gettaginfo(e, 0);
1943 e.angles = fixedvectoangles2(v_forward, v_up);
1944 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1945 e.angles = AnglesTransform_ToVAngles(e.angles);
1947 e.angles = AnglesTransform_ToAngles(e.angles);
1949 setattachment(e, NULL, "");
1950 setorigin(e, e.origin);
1953 void follow_sameorigin(entity e, entity to)
1955 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1956 e.aiment = to; // make the hole follow bmodel
1957 e.punchangle = to.angles; // the original angles of bmodel
1958 e.view_ofs = e.origin - to.origin; // relative origin
1959 e.v_angle = e.angles - to.angles; // relative angles
1963 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1964 void unfollow_sameorigin(entity e)
1966 set_movetype(e, MOVETYPE_NONE);
1970 .string aiment_classname;
1971 .float aiment_deadflag;
1972 void SetMovetypeFollow(entity ent, entity e)
1974 // FIXME this may not be warpzone aware
1975 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1976 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.
1977 ent.aiment = e; // make the hole follow bmodel
1978 ent.punchangle = e.angles; // the original angles of bmodel
1979 ent.view_ofs = ent.origin - e.origin; // relative origin
1980 ent.v_angle = ent.angles - e.angles; // relative angles
1981 ent.aiment_classname = strzone(e.classname);
1982 ent.aiment_deadflag = e.deadflag;
1984 void UnsetMovetypeFollow(entity ent)
1986 set_movetype(ent, MOVETYPE_FLY);
1987 PROJECTILE_MAKETRIGGER(ent);
1990 float LostMovetypeFollow(entity ent)
1993 if(ent.move_movetype != MOVETYPE_FOLLOW)
1999 if(ent.aiment.classname != ent.aiment_classname)
2001 if(ent.aiment.deadflag != ent.aiment_deadflag)
2009 // decolorizes and team colors the player name when needed
2010 string playername(string thename, int teamid, bool team_colorize)
2013 bool do_colorize = (teamplay && team_colorize);
2015 if(do_colorize && !intermission_running)
2020 string t = Team_ColorCode(teamid);
2021 return strcat(t, strdecolorize(thename));
2027 float trace_hits_box_a0, trace_hits_box_a1;
2029 float trace_hits_box_1d(float end, float thmi, float thma)
2033 // just check if x is in range
2041 // do the trace with respect to x
2042 // 0 -> end has to stay in thmi -> thma
2043 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2044 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2045 if (trace_hits_box_a0 > trace_hits_box_a1)
2051 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2056 // now it is a trace from 0 to end
2058 trace_hits_box_a0 = 0;
2059 trace_hits_box_a1 = 1;
2061 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2063 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2065 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2071 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2073 return trace_hits_box(start, end, thmi - ma, thma - mi);
2078 float cvar_or(string cv, float v)
2080 string s = cvar_string(cv);
2087 // NOTE base is the central value
2088 // freq: circle frequency, = 2*pi*frequency in hertz
2090 // -1 start from the lower value
2091 // 0 start from the base value
2092 // 1 start from the higher value
2094 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2097 // RMS = sqrt(base^2 + 0.5 * range^2)
2099 // base = sqrt(RMS^2 - 0.5 * range^2)
2102 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2106 float blink(float base, float range, float freq)
2108 return blink_synced(base, range, freq, 0, 0);