4 #include "constants.qh"
5 #include <client/mutators/_mod.qh>
7 #include "notifications/all.qh"
9 #include <common/deathtypes/all.qh>
12 #include "constants.qh"
13 #include <server/mutators/_mod.qh>
14 #include "notifications/all.qh"
15 #include <common/deathtypes/all.qh>
21 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
27 //nudge = 2 * cvar("collision_impactnudge"); // why not?
30 dir = normalize(v2 - v1);
32 pos = v1 + dir * nudge;
39 if(pos * dir >= v2 * dir)
47 tracebox(pos, mi, ma, v2, nomonsters, forent);
52 LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2));
53 LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos));
54 LOG_TRACE(" trace_endpos is ", vtos(trace_endpos));
55 LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos)));
58 stopentity = trace_ent;
62 // we started inside solid.
63 // then trace from endpos to pos
65 tracebox(t, mi, ma, pos, nomonsters, forent);
69 // t is still inside solid? bad
70 // force advance, then, and retry
71 pos = t + dir * nudge;
73 // but if we hit an entity, stop RIGHT before it
74 if(stopatentity && stopentity && stopentity != ignorestopatentity)
76 trace_ent = stopentity;
78 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
84 // we actually LEFT solid!
85 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
91 // pos is outside solid?!? but why?!? never mind, just return it.
93 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
99 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
101 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
110 Returns a point at least 12 units away from walls
111 (useful for explosion animations, although the blast is performed where it really happened)
115 vector findbetterlocation (vector org, float mindist)
117 vector vec = mindist * '1 0 0';
121 traceline (org, org + vec, true, NULL);
123 if (trace_fraction < 1)
125 vector loc = trace_endpos;
126 traceline (loc, loc + vec, true, NULL);
127 if (trace_fraction >= 1)
144 * Get "real" origin, in worldspace, even if ent is attached to something else.
146 vector real_origin(entity ent)
148 vector v = ((ent.absmin + ent.absmax) * 0.5);
149 entity e = ent.tag_entity;
153 v = v + ((e.absmin + e.absmax) * 0.5);
161 string wordwrap_buffer;
163 void wordwrap_buffer_put(string s)
165 wordwrap_buffer = strcat(wordwrap_buffer, s);
168 string wordwrap(string s, float l)
171 wordwrap_buffer = "";
172 wordwrap_cb(s, l, wordwrap_buffer_put);
174 wordwrap_buffer = "";
179 entity _wordwrap_buffer_sprint_ent;
180 void wordwrap_buffer_sprint(string s)
182 wordwrap_buffer = strcat(wordwrap_buffer, s);
185 sprint(_wordwrap_buffer_sprint_ent, wordwrap_buffer);
186 wordwrap_buffer = "";
190 void wordwrap_sprint(entity to, string s, float l)
192 wordwrap_buffer = "";
193 _wordwrap_buffer_sprint_ent = to;
194 wordwrap_cb(s, l, wordwrap_buffer_sprint);
195 _wordwrap_buffer_sprint_ent = NULL;
196 if(wordwrap_buffer != "")
197 sprint(to, strcat(wordwrap_buffer, "\n"));
198 wordwrap_buffer = "";
204 string draw_UseSkinFor(string pic)
206 if(substring(pic, 0, 1) == "/")
207 return substring(pic, 1, strlen(pic)-1);
209 return strcat(draw_currentSkin, "/", pic);
213 void wordwrap_cb(string s, float l, void(string) callback)
216 float lleft, i, j, wlen;
220 for (i = 0;i < strlen(s);++i)
222 if (substring(s, i, 2) == "\\n")
228 else if (substring(s, i, 1) == "\n")
233 else if (substring(s, i, 1) == " ")
243 for (j = i+1;j < strlen(s);++j)
244 // ^^ this skips over the first character of a word, which
245 // is ALWAYS part of the word
246 // this is safe since if i+1 == strlen(s), i will become
247 // strlen(s)-1 at the end of this block and the function
248 // will terminate. A space can't be the first character we
249 // read here, and neither can a \n be the start, since these
250 // two cases have been handled above.
252 c = substring(s, j, 1);
259 // we need to keep this tempstring alive even if substring is
260 // called repeatedly, so call strcat even though we're not
270 callback(substring(s, i, wlen));
271 lleft = lleft - wlen;
278 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
308 string ScoreString(int pFlags, float pValue)
313 pValue = floor(pValue + 0.5); // round
315 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
317 else if(pFlags & SFL_RANK)
319 valstr = ftos(pValue);
321 if((l >= 2) && (substring(valstr, l - 2, 1) == "1"))
322 valstr = strcat(valstr, "th");
323 else if(substring(valstr, l - 1, 1) == "1")
324 valstr = strcat(valstr, "st");
325 else if(substring(valstr, l - 1, 1) == "2")
326 valstr = strcat(valstr, "nd");
327 else if(substring(valstr, l - 1, 1) == "3")
328 valstr = strcat(valstr, "rd");
330 valstr = strcat(valstr, "th");
332 else if(pFlags & SFL_TIME)
333 valstr = TIME_ENCODED_TOSTRING(pValue);
335 valstr = ftos(pValue);
341 // compressed vector format:
342 // like MD3, just even shorter
343 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
344 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
345 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
346 // length = 2^(length_encoded/8) / 8
347 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
348 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
349 // the special value 0 indicates the zero vector
351 float lengthLogTable[128];
353 float invertLengthLog(float dist)
357 if(dist >= lengthLogTable[127])
359 if(dist <= lengthLogTable[0])
367 m = floor((l + r) / 2);
368 if(lengthLogTable[m] < dist)
374 // now: r is >=, l is <
375 float lerr = (dist - lengthLogTable[l]);
376 float rerr = (lengthLogTable[r] - dist);
382 vector decompressShortVector(int data)
387 float p = (data & 0xF000) / 0x1000;
388 float q = (data & 0x0F80) / 0x80;
389 int len = (data & 0x007F);
391 //print("\ndecompress: p ", ftos(p)); print("q ", ftos(q)); print("len ", ftos(len), "\n");
404 q = .19634954084936207740 * q;
405 p = .19634954084936207740 * p - 1.57079632679489661922;
406 out.x = cos(q) * cos(p);
407 out.y = sin(q) * cos(p);
411 //print("decompressed: ", vtos(out), "\n");
413 return out * lengthLogTable[len];
416 float compressShortVector(vector vec)
422 //print("compress: ", vtos(vec), "\n");
423 ang = vectoangles(vec);
427 if(ang.x < -90 && ang.x > +90)
428 error("BOGUS vectoangles");
429 //print("angles: ", vtos(ang), "\n");
431 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
440 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
441 len = invertLengthLog(vlen(vec));
443 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
445 return (p * 0x1000) + (y * 0x80) + len;
448 STATIC_INIT(compressShortVector)
451 float f = (2 ** (1/8));
453 for(i = 0; i < 128; ++i)
455 lengthLogTable[i] = l;
459 if(cvar("developer"))
461 LOG_TRACE("Verifying vector compression table...");
462 for(i = 0x0F00; i < 0xFFFF; ++i)
463 if(i != compressShortVector(decompressShortVector(i)))
466 "BROKEN vector compression: %s -> %s -> %s",
468 vtos(decompressShortVector(i)),
469 ftos(compressShortVector(decompressShortVector(i)))
477 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
479 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
480 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
481 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
482 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
483 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
484 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
485 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
486 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
487 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
488 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
489 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
490 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
495 string fixPriorityList(string order, float from, float to, float subtract, float complete)
500 n = tokenize_console(order);
502 for(i = 0; i < n; ++i)
507 if(w >= from && w <= to)
508 neworder = strcat(neworder, ftos(w), " ");
512 if(w >= from && w <= to)
513 neworder = strcat(neworder, ftos(w), " ");
520 n = tokenize_console(neworder);
521 for(w = to; w >= from; --w)
523 int wflags = Weapons_from(w).spawnflags;
524 if((wflags & WEP_FLAG_HIDDEN) && (wflags & WEP_FLAG_MUTATORBLOCKED) && !(wflags & WEP_FLAG_NORMAL))
526 for(i = 0; i < n; ++i)
527 if(stof(argv(i)) == w)
529 if(i == n) // not found
530 neworder = strcat(neworder, ftos(w), " ");
534 return substring(neworder, 0, strlen(neworder) - 1);
537 string mapPriorityList(string order, string(string) mapfunc)
542 n = tokenize_console(order);
544 for(float i = 0; i < n; ++i)
545 neworder = strcat(neworder, mapfunc(argv(i)), " ");
547 return substring(neworder, 0, strlen(neworder) - 1);
550 string swapInPriorityList(string order, float i, float j)
552 float n = tokenize_console(order);
554 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
557 for(float w = 0; w < n; ++w)
560 s = strcat(s, argv(j), " ");
562 s = strcat(s, argv(i), " ");
564 s = strcat(s, argv(w), " ");
566 return substring(s, 0, strlen(s) - 1);
573 void get_mi_min_max(float mode)
578 if(!strcasecmp(substring(s, 0, 5), "maps/"))
579 s = substring(s, 5, strlen(s) - 5);
580 if(!strcasecmp(substring(s, strlen(s) - 4, 4), ".bsp"))
581 s = substring(s, 0, strlen(s) - 4);
582 strcpy(mi_shortname, s);
594 MapInfo_Get_ByName(mi_shortname, 0, NULL);
595 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
597 mi_min = MapInfo_Map_mins;
598 mi_max = MapInfo_Map_maxs;
606 tracebox('1 0 0' * mi.x,
607 '0 1 0' * mi.y + '0 0 1' * mi.z,
608 '0 1 0' * ma.y + '0 0 1' * ma.z,
612 if(!trace_startsolid)
613 mi_min.x = trace_endpos.x;
615 tracebox('0 1 0' * mi.y,
616 '1 0 0' * mi.x + '0 0 1' * mi.z,
617 '1 0 0' * ma.x + '0 0 1' * ma.z,
621 if(!trace_startsolid)
622 mi_min.y = trace_endpos.y;
624 tracebox('0 0 1' * mi.z,
625 '1 0 0' * mi.x + '0 1 0' * mi.y,
626 '1 0 0' * ma.x + '0 1 0' * ma.y,
630 if(!trace_startsolid)
631 mi_min.z = trace_endpos.z;
633 tracebox('1 0 0' * ma.x,
634 '0 1 0' * mi.y + '0 0 1' * mi.z,
635 '0 1 0' * ma.y + '0 0 1' * ma.z,
639 if(!trace_startsolid)
640 mi_max.x = trace_endpos.x;
642 tracebox('0 1 0' * ma.y,
643 '1 0 0' * mi.x + '0 0 1' * mi.z,
644 '1 0 0' * ma.x + '0 0 1' * ma.z,
648 if(!trace_startsolid)
649 mi_max.y = trace_endpos.y;
651 tracebox('0 0 1' * ma.z,
652 '1 0 0' * mi.x + '0 1 0' * mi.y,
653 '1 0 0' * ma.x + '0 1 0' * ma.y,
657 if(!trace_startsolid)
658 mi_max.z = trace_endpos.z;
663 void get_mi_min_max_texcoords(float mode)
667 get_mi_min_max(mode);
672 // extend mi_picmax to get a square aspect ratio
673 // center the map in that area
674 extend = mi_picmax - mi_picmin;
675 if(extend.y > extend.x)
677 mi_picmin.x -= (extend.y - extend.x) * 0.5;
678 mi_picmax.x += (extend.y - extend.x) * 0.5;
682 mi_picmin.y -= (extend.x - extend.y) * 0.5;
683 mi_picmax.y += (extend.x - extend.y) * 0.5;
686 // add another some percent
687 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
691 // calculate the texcoords
692 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
693 // first the two corners of the origin
694 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
695 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
696 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
697 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
698 // then the other corners
699 mi_pictexcoord1_x = mi_pictexcoord0_x;
700 mi_pictexcoord1_y = mi_pictexcoord2_y;
701 mi_pictexcoord3_x = mi_pictexcoord2_x;
702 mi_pictexcoord3_y = mi_pictexcoord0_y;
706 float cvar_settemp(string tmp_cvar, string tmp_value)
708 float created_saved_value;
710 created_saved_value = 0;
712 if (!(tmp_cvar || tmp_value))
714 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
718 if(!cvar_type(tmp_cvar))
720 LOG_INFOF("Error: cvar %s doesn't exist!", tmp_cvar);
724 IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
726 created_saved_value = -1; // skip creation
727 break; // no need to continue
730 if(created_saved_value != -1)
732 // creating a new entity to keep track of this cvar
733 entity e = new_pure(saved_cvar_value);
734 IL_PUSH(g_saved_cvars, e);
735 e.netname = strzone(tmp_cvar);
736 e.message = strzone(cvar_string(tmp_cvar));
737 created_saved_value = 1;
740 // update the cvar to the value given
741 cvar_set(tmp_cvar, tmp_value);
743 return created_saved_value;
746 int cvar_settemp_restore()
749 // FIXME this new-style loop fails!
751 FOREACH_ENTITY_CLASS("saved_cvar_value", true,
753 if(cvar_type(it.netname))
755 cvar_set(it.netname, it.message);
756 strunzone(it.netname);
757 strunzone(it.message);
762 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", it.netname);
767 while((e = find(e, classname, "saved_cvar_value")))
769 if(cvar_type(e.netname))
771 cvar_set(e.netname, e.message);
776 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", e.netname));
783 bool isCaretEscaped(string theText, float pos)
786 while(pos - i >= 1 && substring(theText, pos - i - 1, 1) == "^")
791 int skipIncompleteTag(string theText, float pos, int len)
795 if(substring(theText, pos - 1, 1) == "^")
797 if(isCaretEscaped(theText, pos - 1) || pos >= len)
800 int ch = str2chr(theText, pos);
801 if(ch >= '0' && ch <= '9')
802 return 1; // ^[0-9] color code found
804 tag_start = pos - 1; // ^x tag found
810 for(int i = 2; pos - i >= 0 && i <= 4; ++i)
812 if(substring(theText, pos - i, 2) == "^x")
814 tag_start = pos - i; // ^x tag found
822 if(tag_start + 5 < len)
823 if(IS_HEXDIGIT(substring(theText, tag_start + 2, 1)))
824 if(IS_HEXDIGIT(substring(theText, tag_start + 3, 1)))
825 if(IS_HEXDIGIT(substring(theText, tag_start + 4, 1)))
827 if(!isCaretEscaped(theText, tag_start))
828 return 5 - (pos - tag_start); // ^xRGB color code found
834 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
837 // The following function is SLOW.
838 // For your safety and for the protection of those around you...
839 // DO NOT CALL THIS AT HOME.
841 if(w(theText, theSize) <= maxWidth)
842 return strlen(theText); // yeah!
844 bool colors = (w("^7", theSize) == 0);
846 // binary search for right place to cut string
847 int len, left, right, middle;
849 right = len = strlen(theText);
853 middle = floor((left + right) / 2);
855 ofs = skipIncompleteTag(theText, middle, len);
856 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
861 while(left < right - 1);
866 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
869 // The following function is SLOW.
870 // For your safety and for the protection of those around you...
871 // DO NOT CALL THIS AT HOME.
873 if(w(theText) <= maxWidth)
874 return strlen(theText); // yeah!
876 bool colors = (w("^7") == 0);
878 // binary search for right place to cut string
879 int len, left, right, middle;
881 right = len = strlen(theText);
885 middle = floor((left + right) / 2);
887 ofs = skipIncompleteTag(theText, middle, len);
888 if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
893 while(left < right - 1);
898 string find_last_color_code(string s)
900 int start = strstrofs(s, "^", 0);
901 if (start == -1) // no caret found
903 int len = strlen(s)-1;
904 for(int i = len; i >= start; --i)
906 if(substring(s, i, 1) != "^")
910 while (i-carets >= start && substring(s, i-carets, 1) == "^")
913 // check if carets aren't all escaped
917 if(IS_DIGIT(substring(s, i+1, 1)))
918 return substring(s, i, 2);
921 if(substring(s, i+1, 1) == "x")
922 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
923 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
924 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
925 return substring(s, i, 5);
927 i -= carets; // this also skips one char before the carets
933 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
939 s = getWrappedLine_remaining;
943 getWrappedLine_remaining = string_null;
944 return s; // the line has no size ANYWAY, nothing would be displayed.
947 cantake = textLengthUpToWidth(s, w, theFontSize, tw);
948 if(cantake > 0 && cantake < strlen(s))
951 while(take > 0 && substring(s, take, 1) != " ")
955 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
956 if(getWrappedLine_remaining == "")
957 getWrappedLine_remaining = string_null;
958 else if (tw("^7", theFontSize) == 0)
959 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
960 return substring(s, 0, cantake);
964 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
965 if(getWrappedLine_remaining == "")
966 getWrappedLine_remaining = string_null;
967 else if (tw("^7", theFontSize) == 0)
968 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
969 return substring(s, 0, take);
974 getWrappedLine_remaining = string_null;
979 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
985 s = getWrappedLine_remaining;
989 getWrappedLine_remaining = string_null;
990 return s; // the line has no size ANYWAY, nothing would be displayed.
993 cantake = textLengthUpToLength(s, w, tw);
994 if(cantake > 0 && cantake < strlen(s))
997 while(take > 0 && substring(s, take, 1) != " ")
1001 getWrappedLine_remaining = substring(s, cantake, strlen(s) - cantake);
1002 if(getWrappedLine_remaining == "")
1003 getWrappedLine_remaining = string_null;
1004 else if (tw("^7") == 0)
1005 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, cantake)), getWrappedLine_remaining);
1006 return substring(s, 0, cantake);
1010 getWrappedLine_remaining = substring(s, take + 1, strlen(s) - take);
1011 if(getWrappedLine_remaining == "")
1012 getWrappedLine_remaining = string_null;
1013 else if (tw("^7") == 0)
1014 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take)), getWrappedLine_remaining);
1015 return substring(s, 0, take);
1020 getWrappedLine_remaining = string_null;
1025 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1027 if(tw(theText, theFontSize) <= maxWidth)
1030 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1033 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1035 if(tw(theText) <= maxWidth)
1038 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1041 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1043 string subpattern, subpattern2, subpattern3, subpattern4;
1044 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1046 subpattern2 = ",teams,";
1048 subpattern2 = ",noteams,";
1050 subpattern3 = ",teamspawns,";
1052 subpattern3 = ",noteamspawns,";
1053 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1054 subpattern4 = ",race,";
1056 subpattern4 = string_null;
1058 if(substring(pattern, 0, 1) == "-")
1060 pattern = substring(pattern, 1, strlen(pattern) - 1);
1061 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1063 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1065 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1067 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1072 if(substring(pattern, 0, 1) == "+")
1073 pattern = substring(pattern, 1, strlen(pattern) - 1);
1074 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1075 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1076 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1080 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1087 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1091 // make origin and speed relative
1096 // now solve for ret, ret normalized:
1097 // eorg + t * evel == t * ret * spd
1098 // or, rather, solve for t:
1099 // |eorg + t * evel| == t * spd
1100 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1101 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1102 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1103 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1104 // q = (eorg * eorg) / (evel * evel - spd * spd)
1105 if(!solution.z) // no real solution
1108 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1109 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1110 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1111 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1112 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1113 // spd < |evel| * sin angle(evel, eorg)
1116 else if(solution.x > 0)
1118 // both solutions > 0: take the smaller one
1119 // happens if p < 0 and q > 0
1120 ret = normalize(eorg + solution.x * evel);
1122 else if(solution.y > 0)
1124 // one solution > 0: take the larger one
1125 // happens if q < 0 or q == 0 and p < 0
1126 ret = normalize(eorg + solution.y * evel);
1130 // no solution > 0: reject
1131 // happens if p > 0 and q >= 0
1132 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1133 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1138 // "Enemy is moving away from me at more than spd"
1142 // NOTE: we always got a solution if spd > |evel|
1144 if(newton_style == 2)
1145 ret = normalize(ret * spd + myvel);
1150 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1155 if(newton_style == 2)
1157 // true Newtonian projectiles with automatic aim adjustment
1159 // solve: |outspeed * mydir - myvel| = spd
1160 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1161 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1165 // myvel^2 - (mydir * myvel)^2 > spd^2
1166 // velocity without mydir component > spd
1167 // fire at smallest possible spd that works?
1168 // |(mydir * myvel) * myvel - myvel| = spd
1170 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1174 outspeed = solution.y; // the larger one
1177 //outspeed = 0; // slowest possible shot
1178 outspeed = solution.x; // the real part (that is, the average!)
1179 //dprint("impossible shot, adjusting\n");
1182 outspeed = bound(spd * mi, outspeed, spd * ma);
1183 return mydir * outspeed;
1187 return myvel + spd * mydir;
1190 float compressShotOrigin(vector v)
1192 float rx = rint(v.x * 2);
1193 float ry = rint(v.y * 4) + 128;
1194 float rz = rint(v.z * 4) + 128;
1195 if(rx > 255 || rx < 0)
1197 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1198 rx = bound(0, rx, 255);
1200 if(ry > 255 || ry < 0)
1202 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1203 ry = bound(0, ry, 255);
1205 if(rz > 255 || rz < 0)
1207 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1208 rz = bound(0, rz, 255);
1210 return rx * 0x10000 + ry * 0x100 + rz;
1212 vector decompressShotOrigin(int f)
1215 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1216 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1217 v.z = ((f & 0xFF) - 128) / 4;
1222 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1224 // NOTE: we'll always choose the SMALLER value...
1225 float healthdamage, armordamage, armorideal;
1226 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1229 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1230 armordamage = a + (h - 1); // damage we can take if we could use more armor
1231 armorideal = healthdamage * armorblock;
1233 if(armordamage < healthdamage)
1246 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1249 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1251 if (deathtype & HITTYPE_ARMORPIERCE)
1253 v.y = bound(0, damage * armorblock, a); // save
1254 v.x = bound(0, damage - v.y, damage); // take
1260 string getcurrentmod()
1264 m = cvar_string("fs_gamedir");
1265 n = tokenize_console(m);
1272 float matchacl(string acl, string str)
1279 t = car(acl); acl = cdr(acl);
1282 if(substring(t, 0, 1) == "-")
1285 t = substring(t, 1, strlen(t) - 1);
1287 else if(substring(t, 0, 1) == "+")
1288 t = substring(t, 1, strlen(t) - 1);
1290 if(substring(t, -1, 1) == "*")
1292 t = substring(t, 0, strlen(t) - 1);
1293 s = substring(str, 0, strlen(t));
1306 string get_model_datafilename(string m, float sk, string fil)
1311 m = "models/player/*_";
1313 m = strcat(m, ftos(sk));
1316 return strcat(m, ".", fil);
1319 float get_model_parameters(string m, float sk)
1321 get_model_parameters_modelname = string_null;
1322 get_model_parameters_modelskin = -1;
1323 get_model_parameters_name = string_null;
1324 get_model_parameters_species = -1;
1325 get_model_parameters_sex = string_null;
1326 get_model_parameters_weight = -1;
1327 get_model_parameters_age = -1;
1328 get_model_parameters_desc = string_null;
1329 get_model_parameters_bone_upperbody = string_null;
1330 get_model_parameters_bone_weapon = string_null;
1331 for(int i = 0; i < MAX_AIM_BONES; ++i)
1333 get_model_parameters_bone_aim[i] = string_null;
1334 get_model_parameters_bone_aimweight[i] = 0;
1336 get_model_parameters_fixbone = 0;
1337 get_model_parameters_hidden = false;
1340 MUTATOR_CALLHOOK(ClearModelParams);
1346 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1347 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1351 if(substring(m, -4, -1) != ".txt")
1353 if(substring(m, -6, 1) != "_")
1355 sk = stof(substring(m, -5, 1));
1356 m = substring(m, 0, -7);
1359 string fn = get_model_datafilename(m, sk, "txt");
1360 int fh = fopen(fn, FILE_READ);
1364 fn = get_model_datafilename(m, sk, "txt");
1365 fh = fopen(fn, FILE_READ);
1370 get_model_parameters_modelname = m;
1371 get_model_parameters_modelskin = sk;
1373 while((s = fgets(fh)))
1376 break; // next lines will be description
1380 get_model_parameters_name = s;
1384 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1385 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1386 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1387 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1388 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1389 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1390 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1393 get_model_parameters_sex = s;
1395 get_model_parameters_weight = stof(s);
1397 get_model_parameters_age = stof(s);
1398 if(c == "description")
1399 get_model_parameters_description = s;
1400 if(c == "bone_upperbody")
1401 get_model_parameters_bone_upperbody = s;
1402 if(c == "bone_weapon")
1403 get_model_parameters_bone_weapon = s;
1405 MUTATOR_CALLHOOK(GetModelParams, c, s);
1407 for(int i = 0; i < MAX_AIM_BONES; ++i)
1408 if(c == strcat("bone_aim", ftos(i)))
1410 get_model_parameters_bone_aimweight[i] = stof(car(s));
1411 get_model_parameters_bone_aim[i] = cdr(s);
1414 get_model_parameters_fixbone = stof(s);
1416 get_model_parameters_hidden = stob(s);
1419 while((s = fgets(fh)))
1421 if(get_model_parameters_desc)
1422 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1424 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1432 // x-encoding (encoding as zero length invisible string)
1433 const string XENCODE_2 = "xX";
1434 const string XENCODE_22 = "0123456789abcdefABCDEF";
1435 string xencode(int f)
1438 d = f % 22; f = floor(f / 22);
1439 c = f % 22; f = floor(f / 22);
1440 b = f % 22; f = floor(f / 22);
1441 a = f % 2; // f = floor(f / 2);
1444 substring(XENCODE_2, a, 1),
1445 substring(XENCODE_22, b, 1),
1446 substring(XENCODE_22, c, 1),
1447 substring(XENCODE_22, d, 1)
1450 float xdecode(string s)
1453 if(substring(s, 0, 1) != "^")
1457 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1458 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1459 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1460 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1461 if(a < 0 || b < 0 || c < 0 || d < 0)
1463 return ((a * 22 + b) * 22 + c) * 22 + d;
1467 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1469 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1472 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1475 float shutdown_running;
1480 void CSQC_Shutdown()
1486 if(shutdown_running)
1488 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1492 shutdown_running = 1;
1496 cvar_settemp_restore(); // this must be done LAST, but in any case
1500 .float skeleton_bones_index;
1501 void Skeleton_SetBones(entity e)
1503 // set skeleton_bones to the total number of bones on the model
1504 if(e.skeleton_bones_index == e.modelindex)
1505 return; // same model, nothing to update
1508 skelindex = skel_create(e.modelindex);
1509 e.skeleton_bones = skel_get_numbones(skelindex);
1510 skel_delete(skelindex);
1511 e.skeleton_bones_index = e.modelindex;
1515 string to_execute_next_frame;
1516 void execute_next_frame()
1518 if(to_execute_next_frame)
1520 localcmd("\n", to_execute_next_frame, "\n");
1521 strfree(to_execute_next_frame);
1524 void queue_to_execute_next_frame(string s)
1526 if(to_execute_next_frame)
1528 s = strcat(s, "\n", to_execute_next_frame);
1530 strcpy(to_execute_next_frame, s);
1533 .float FindConnectedComponent_processing;
1534 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1536 entity queue_start, queue_end;
1538 // we build a queue of to-be-processed entities.
1539 // queue_start is the next entity to be checked for neighbors
1540 // queue_end is the last entity added
1542 if(e.FindConnectedComponent_processing)
1543 error("recursion or broken cleanup");
1545 // start with a 1-element queue
1546 queue_start = queue_end = e;
1547 queue_end.(fld) = NULL;
1548 queue_end.FindConnectedComponent_processing = 1;
1550 // for each queued item:
1551 for (; queue_start; queue_start = queue_start.(fld))
1553 // find all neighbors of queue_start
1555 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1557 if(t.FindConnectedComponent_processing)
1559 if(iscon(t, queue_start, pass))
1561 // it is connected? ADD IT. It will look for neighbors soon too.
1562 queue_end.(fld) = t;
1564 queue_end.(fld) = NULL;
1565 queue_end.FindConnectedComponent_processing = 1;
1571 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1572 queue_start.FindConnectedComponent_processing = 0;
1576 vector animfixfps(entity e, vector a, vector b)
1578 // multi-frame anim: keep as-is
1581 float dur = frameduration(e.modelindex, a.x);
1582 if (dur <= 0 && b.y)
1585 dur = frameduration(e.modelindex, a.x);
1595 Notification Announcer_PickNumber(int type, int num)
1604 case 10: return ANNCE_NUM_GAMESTART_10;
1605 case 9: return ANNCE_NUM_GAMESTART_9;
1606 case 8: return ANNCE_NUM_GAMESTART_8;
1607 case 7: return ANNCE_NUM_GAMESTART_7;
1608 case 6: return ANNCE_NUM_GAMESTART_6;
1609 case 5: return ANNCE_NUM_GAMESTART_5;
1610 case 4: return ANNCE_NUM_GAMESTART_4;
1611 case 3: return ANNCE_NUM_GAMESTART_3;
1612 case 2: return ANNCE_NUM_GAMESTART_2;
1613 case 1: return ANNCE_NUM_GAMESTART_1;
1621 case 10: return ANNCE_NUM_IDLE_10;
1622 case 9: return ANNCE_NUM_IDLE_9;
1623 case 8: return ANNCE_NUM_IDLE_8;
1624 case 7: return ANNCE_NUM_IDLE_7;
1625 case 6: return ANNCE_NUM_IDLE_6;
1626 case 5: return ANNCE_NUM_IDLE_5;
1627 case 4: return ANNCE_NUM_IDLE_4;
1628 case 3: return ANNCE_NUM_IDLE_3;
1629 case 2: return ANNCE_NUM_IDLE_2;
1630 case 1: return ANNCE_NUM_IDLE_1;
1638 case 10: return ANNCE_NUM_KILL_10;
1639 case 9: return ANNCE_NUM_KILL_9;
1640 case 8: return ANNCE_NUM_KILL_8;
1641 case 7: return ANNCE_NUM_KILL_7;
1642 case 6: return ANNCE_NUM_KILL_6;
1643 case 5: return ANNCE_NUM_KILL_5;
1644 case 4: return ANNCE_NUM_KILL_4;
1645 case 3: return ANNCE_NUM_KILL_3;
1646 case 2: return ANNCE_NUM_KILL_2;
1647 case 1: return ANNCE_NUM_KILL_1;
1655 case 10: return ANNCE_NUM_RESPAWN_10;
1656 case 9: return ANNCE_NUM_RESPAWN_9;
1657 case 8: return ANNCE_NUM_RESPAWN_8;
1658 case 7: return ANNCE_NUM_RESPAWN_7;
1659 case 6: return ANNCE_NUM_RESPAWN_6;
1660 case 5: return ANNCE_NUM_RESPAWN_5;
1661 case 4: return ANNCE_NUM_RESPAWN_4;
1662 case 3: return ANNCE_NUM_RESPAWN_3;
1663 case 2: return ANNCE_NUM_RESPAWN_2;
1664 case 1: return ANNCE_NUM_RESPAWN_1;
1668 case CNT_ROUNDSTART:
1672 case 10: return ANNCE_NUM_ROUNDSTART_10;
1673 case 9: return ANNCE_NUM_ROUNDSTART_9;
1674 case 8: return ANNCE_NUM_ROUNDSTART_8;
1675 case 7: return ANNCE_NUM_ROUNDSTART_7;
1676 case 6: return ANNCE_NUM_ROUNDSTART_6;
1677 case 5: return ANNCE_NUM_ROUNDSTART_5;
1678 case 4: return ANNCE_NUM_ROUNDSTART_4;
1679 case 3: return ANNCE_NUM_ROUNDSTART_3;
1680 case 2: return ANNCE_NUM_ROUNDSTART_2;
1681 case 1: return ANNCE_NUM_ROUNDSTART_1;
1689 case 10: return ANNCE_NUM_10;
1690 case 9: return ANNCE_NUM_9;
1691 case 8: return ANNCE_NUM_8;
1692 case 7: return ANNCE_NUM_7;
1693 case 6: return ANNCE_NUM_6;
1694 case 5: return ANNCE_NUM_5;
1695 case 4: return ANNCE_NUM_4;
1696 case 3: return ANNCE_NUM_3;
1697 case 2: return ANNCE_NUM_2;
1698 case 1: return ANNCE_NUM_1;
1707 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1709 switch(nativecontents)
1714 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1716 return DPCONTENTS_WATER;
1718 return DPCONTENTS_SLIME;
1720 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1722 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1727 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1729 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1730 return CONTENT_SOLID;
1731 if(supercontents & DPCONTENTS_SKY)
1733 if(supercontents & DPCONTENTS_LAVA)
1734 return CONTENT_LAVA;
1735 if(supercontents & DPCONTENTS_SLIME)
1736 return CONTENT_SLIME;
1737 if(supercontents & DPCONTENTS_WATER)
1738 return CONTENT_WATER;
1739 return CONTENT_EMPTY;