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));
1301 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1307 string get_model_datafilename(string m, float sk, string fil)
1312 m = "models/player/*_";
1314 m = strcat(m, ftos(sk));
1317 return strcat(m, ".", fil);
1320 float get_model_parameters(string m, float sk)
1322 get_model_parameters_modelname = string_null;
1323 get_model_parameters_modelskin = -1;
1324 get_model_parameters_name = string_null;
1325 get_model_parameters_species = -1;
1326 get_model_parameters_sex = string_null;
1327 get_model_parameters_weight = -1;
1328 get_model_parameters_age = -1;
1329 get_model_parameters_desc = string_null;
1330 get_model_parameters_bone_upperbody = string_null;
1331 get_model_parameters_bone_weapon = string_null;
1332 for(int i = 0; i < MAX_AIM_BONES; ++i)
1334 get_model_parameters_bone_aim[i] = string_null;
1335 get_model_parameters_bone_aimweight[i] = 0;
1337 get_model_parameters_fixbone = 0;
1338 get_model_parameters_hidden = false;
1341 MUTATOR_CALLHOOK(ClearModelParams);
1347 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1348 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1352 if(substring(m, -4, -1) != ".txt")
1354 if(substring(m, -6, 1) != "_")
1356 sk = stof(substring(m, -5, 1));
1357 m = substring(m, 0, -7);
1360 string fn = get_model_datafilename(m, sk, "txt");
1361 int fh = fopen(fn, FILE_READ);
1365 fn = get_model_datafilename(m, sk, "txt");
1366 fh = fopen(fn, FILE_READ);
1371 get_model_parameters_modelname = m;
1372 get_model_parameters_modelskin = sk;
1374 while((s = fgets(fh)))
1377 break; // next lines will be description
1381 get_model_parameters_name = s;
1385 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1386 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1387 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1388 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1389 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1390 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1391 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1394 get_model_parameters_sex = s;
1396 get_model_parameters_weight = stof(s);
1398 get_model_parameters_age = stof(s);
1399 if(c == "description")
1400 get_model_parameters_description = s;
1401 if(c == "bone_upperbody")
1402 get_model_parameters_bone_upperbody = s;
1403 if(c == "bone_weapon")
1404 get_model_parameters_bone_weapon = s;
1406 MUTATOR_CALLHOOK(GetModelParams, c, s);
1408 for(int i = 0; i < MAX_AIM_BONES; ++i)
1409 if(c == strcat("bone_aim", ftos(i)))
1411 get_model_parameters_bone_aimweight[i] = stof(car(s));
1412 get_model_parameters_bone_aim[i] = cdr(s);
1415 get_model_parameters_fixbone = stof(s);
1417 get_model_parameters_hidden = stob(s);
1420 while((s = fgets(fh)))
1422 if(get_model_parameters_desc)
1423 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1425 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1433 // x-encoding (encoding as zero length invisible string)
1434 const string XENCODE_2 = "xX";
1435 const string XENCODE_22 = "0123456789abcdefABCDEF";
1436 string xencode(int f)
1439 d = f % 22; f = floor(f / 22);
1440 c = f % 22; f = floor(f / 22);
1441 b = f % 22; f = floor(f / 22);
1442 a = f % 2; // f = floor(f / 2);
1445 substring(XENCODE_2, a, 1),
1446 substring(XENCODE_22, b, 1),
1447 substring(XENCODE_22, c, 1),
1448 substring(XENCODE_22, d, 1)
1451 float xdecode(string s)
1454 if(substring(s, 0, 1) != "^")
1458 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1459 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1460 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1461 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1462 if(a < 0 || b < 0 || c < 0 || d < 0)
1464 return ((a * 22 + b) * 22 + c) * 22 + d;
1468 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1470 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1473 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1476 float shutdown_running;
1481 void CSQC_Shutdown()
1487 if(shutdown_running)
1489 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1493 shutdown_running = 1;
1497 cvar_settemp_restore(); // this must be done LAST, but in any case
1501 .float skeleton_bones_index;
1502 void Skeleton_SetBones(entity e)
1504 // set skeleton_bones to the total number of bones on the model
1505 if(e.skeleton_bones_index == e.modelindex)
1506 return; // same model, nothing to update
1509 skelindex = skel_create(e.modelindex);
1510 e.skeleton_bones = skel_get_numbones(skelindex);
1511 skel_delete(skelindex);
1512 e.skeleton_bones_index = e.modelindex;
1516 string to_execute_next_frame;
1517 void execute_next_frame()
1519 if(to_execute_next_frame)
1521 localcmd("\n", to_execute_next_frame, "\n");
1522 strfree(to_execute_next_frame);
1525 void queue_to_execute_next_frame(string s)
1527 if(to_execute_next_frame)
1529 s = strcat(s, "\n", to_execute_next_frame);
1531 strcpy(to_execute_next_frame, s);
1534 .float FindConnectedComponent_processing;
1535 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1537 entity queue_start, queue_end;
1539 // we build a queue of to-be-processed entities.
1540 // queue_start is the next entity to be checked for neighbors
1541 // queue_end is the last entity added
1543 if(e.FindConnectedComponent_processing)
1544 error("recursion or broken cleanup");
1546 // start with a 1-element queue
1547 queue_start = queue_end = e;
1548 queue_end.(fld) = NULL;
1549 queue_end.FindConnectedComponent_processing = 1;
1551 // for each queued item:
1552 for (; queue_start; queue_start = queue_start.(fld))
1554 // find all neighbors of queue_start
1556 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1558 if(t.FindConnectedComponent_processing)
1560 if(iscon(t, queue_start, pass))
1562 // it is connected? ADD IT. It will look for neighbors soon too.
1563 queue_end.(fld) = t;
1565 queue_end.(fld) = NULL;
1566 queue_end.FindConnectedComponent_processing = 1;
1572 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1573 queue_start.FindConnectedComponent_processing = 0;
1577 vector animfixfps(entity e, vector a, vector b)
1579 // multi-frame anim: keep as-is
1582 float dur = frameduration(e.modelindex, a.x);
1583 if (dur <= 0 && b.y)
1586 dur = frameduration(e.modelindex, a.x);
1596 Notification Announcer_PickNumber(int type, int num)
1605 case 10: return ANNCE_NUM_GAMESTART_10;
1606 case 9: return ANNCE_NUM_GAMESTART_9;
1607 case 8: return ANNCE_NUM_GAMESTART_8;
1608 case 7: return ANNCE_NUM_GAMESTART_7;
1609 case 6: return ANNCE_NUM_GAMESTART_6;
1610 case 5: return ANNCE_NUM_GAMESTART_5;
1611 case 4: return ANNCE_NUM_GAMESTART_4;
1612 case 3: return ANNCE_NUM_GAMESTART_3;
1613 case 2: return ANNCE_NUM_GAMESTART_2;
1614 case 1: return ANNCE_NUM_GAMESTART_1;
1622 case 10: return ANNCE_NUM_IDLE_10;
1623 case 9: return ANNCE_NUM_IDLE_9;
1624 case 8: return ANNCE_NUM_IDLE_8;
1625 case 7: return ANNCE_NUM_IDLE_7;
1626 case 6: return ANNCE_NUM_IDLE_6;
1627 case 5: return ANNCE_NUM_IDLE_5;
1628 case 4: return ANNCE_NUM_IDLE_4;
1629 case 3: return ANNCE_NUM_IDLE_3;
1630 case 2: return ANNCE_NUM_IDLE_2;
1631 case 1: return ANNCE_NUM_IDLE_1;
1639 case 10: return ANNCE_NUM_KILL_10;
1640 case 9: return ANNCE_NUM_KILL_9;
1641 case 8: return ANNCE_NUM_KILL_8;
1642 case 7: return ANNCE_NUM_KILL_7;
1643 case 6: return ANNCE_NUM_KILL_6;
1644 case 5: return ANNCE_NUM_KILL_5;
1645 case 4: return ANNCE_NUM_KILL_4;
1646 case 3: return ANNCE_NUM_KILL_3;
1647 case 2: return ANNCE_NUM_KILL_2;
1648 case 1: return ANNCE_NUM_KILL_1;
1656 case 10: return ANNCE_NUM_RESPAWN_10;
1657 case 9: return ANNCE_NUM_RESPAWN_9;
1658 case 8: return ANNCE_NUM_RESPAWN_8;
1659 case 7: return ANNCE_NUM_RESPAWN_7;
1660 case 6: return ANNCE_NUM_RESPAWN_6;
1661 case 5: return ANNCE_NUM_RESPAWN_5;
1662 case 4: return ANNCE_NUM_RESPAWN_4;
1663 case 3: return ANNCE_NUM_RESPAWN_3;
1664 case 2: return ANNCE_NUM_RESPAWN_2;
1665 case 1: return ANNCE_NUM_RESPAWN_1;
1669 case CNT_ROUNDSTART:
1673 case 10: return ANNCE_NUM_ROUNDSTART_10;
1674 case 9: return ANNCE_NUM_ROUNDSTART_9;
1675 case 8: return ANNCE_NUM_ROUNDSTART_8;
1676 case 7: return ANNCE_NUM_ROUNDSTART_7;
1677 case 6: return ANNCE_NUM_ROUNDSTART_6;
1678 case 5: return ANNCE_NUM_ROUNDSTART_5;
1679 case 4: return ANNCE_NUM_ROUNDSTART_4;
1680 case 3: return ANNCE_NUM_ROUNDSTART_3;
1681 case 2: return ANNCE_NUM_ROUNDSTART_2;
1682 case 1: return ANNCE_NUM_ROUNDSTART_1;
1690 case 10: return ANNCE_NUM_10;
1691 case 9: return ANNCE_NUM_9;
1692 case 8: return ANNCE_NUM_8;
1693 case 7: return ANNCE_NUM_7;
1694 case 6: return ANNCE_NUM_6;
1695 case 5: return ANNCE_NUM_5;
1696 case 4: return ANNCE_NUM_4;
1697 case 3: return ANNCE_NUM_3;
1698 case 2: return ANNCE_NUM_2;
1699 case 1: return ANNCE_NUM_1;
1708 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1710 switch(nativecontents)
1715 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1717 return DPCONTENTS_WATER;
1719 return DPCONTENTS_SLIME;
1721 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1723 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1728 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1730 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1731 return CONTENT_SOLID;
1732 if(supercontents & DPCONTENTS_SKY)
1734 if(supercontents & DPCONTENTS_LAVA)
1735 return CONTENT_LAVA;
1736 if(supercontents & DPCONTENTS_SLIME)
1737 return CONTENT_SLIME;
1738 if(supercontents & DPCONTENTS_WATER)
1739 return CONTENT_WATER;
1740 return CONTENT_EMPTY;