4 #include <client/mutators/_mod.qh>
5 #include <common/constants.qh>
6 #include <common/deathtypes/all.qh>
7 #include <common/gamemodes/_mod.qh>
8 #include <common/mapinfo.qh>
9 #include <common/notifications/all.qh>
10 #include <common/scores.qh>
13 #include <common/constants.qh>
14 #include <common/deathtypes/all.qh>
15 #include <common/gamemodes/_mod.qh>
16 #include <common/mapinfo.qh>
17 #include <common/notifications/all.qh>
18 #include <common/scores.qh>
19 #include <server/mutators/_mod.qh>
23 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
29 //nudge = 2 * cvar("collision_impactnudge"); // why not?
32 dir = normalize(v2 - v1);
34 pos = v1 + dir * nudge;
41 if(pos * dir >= v2 * dir)
49 tracebox(pos, mi, ma, v2, nomonsters, forent);
54 LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2));
55 LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos));
56 LOG_TRACE(" trace_endpos is ", vtos(trace_endpos));
57 LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos)));
60 stopentity = trace_ent;
64 // we started inside solid.
65 // then trace from endpos to pos
67 tracebox(t, mi, ma, pos, nomonsters, forent);
71 // t is still inside solid? bad
72 // force advance, then, and retry
73 pos = t + dir * nudge;
75 // but if we hit an entity, stop RIGHT before it
76 if(stopatentity && stopentity && stopentity != ignorestopatentity)
78 trace_ent = stopentity;
80 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
86 // we actually LEFT solid!
87 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
93 // pos is outside solid?!? but why?!? never mind, just return it.
95 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
101 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
103 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
112 Returns a point at least 12 units away from walls
113 (useful for explosion animations, although the blast is performed where it really happened)
117 vector findbetterlocation (vector org, float mindist)
119 vector vec = mindist * '1 0 0';
123 traceline (org, org + vec, true, NULL);
125 if (trace_fraction < 1)
127 vector loc = trace_endpos;
128 traceline (loc, loc + vec, true, NULL);
129 if (trace_fraction >= 1)
146 * Get "real" origin, in worldspace, even if ent is attached to something else.
148 vector real_origin(entity ent)
150 vector v = ((ent.absmin + ent.absmax) * 0.5);
151 entity e = ent.tag_entity;
155 v = v + ((e.absmin + e.absmax) * 0.5);
163 string wordwrap_buffer;
165 void wordwrap_buffer_put(string s)
167 wordwrap_buffer = strcat(wordwrap_buffer, s);
170 string wordwrap(string s, float l)
173 wordwrap_buffer = "";
174 wordwrap_cb(s, l, wordwrap_buffer_put);
176 wordwrap_buffer = "";
181 entity _wordwrap_buffer_sprint_ent;
182 void wordwrap_buffer_sprint(string s)
184 wordwrap_buffer = strcat(wordwrap_buffer, s);
187 sprint(_wordwrap_buffer_sprint_ent, wordwrap_buffer);
188 wordwrap_buffer = "";
192 void wordwrap_sprint(entity to, string s, float l)
194 wordwrap_buffer = "";
195 _wordwrap_buffer_sprint_ent = to;
196 wordwrap_cb(s, l, wordwrap_buffer_sprint);
197 _wordwrap_buffer_sprint_ent = NULL;
198 if(wordwrap_buffer != "")
199 sprint(to, strcat(wordwrap_buffer, "\n"));
200 wordwrap_buffer = "";
206 string draw_UseSkinFor(string pic)
208 if(substring(pic, 0, 1) == "/")
209 return substring(pic, 1, strlen(pic)-1);
211 return strcat(draw_currentSkin, "/", pic);
214 void mut_set_active(int mut)
217 active_mutators[1] |= BIT(mut - 24);
219 active_mutators[0] |= BIT(mut);
222 bool mut_is_active(int mut)
225 return (active_mutators[1] & (BIT(mut - 24)));
227 return (active_mutators[0] & BIT(mut));
230 // if s == "" (MENUQC) builds the mutator list for the Mutators dialog based on local cvar values
231 // otherwise (CSQC) translates the mutator list (s) that client has received from server
232 // NOTE: this function merges MENUQC and CSQC code in order to avoid duplicating and separating strings
233 string build_mutator_list(string s)
235 int i = -1, n = 0; // allow only 1 iteration in the following for loop if (s == "")
239 n = tokenizebyseparator(s, ", ");
242 for (string arg = ""; i < n; ++i)
244 if (i >= 0) arg = argv(i);
245 // cond is the condition for showing the mutator enabled in the menu
246 #define X(name, translated_name, mut, cond) \
247 if(arg == name || (!n && (cond))) { s2 = cons_mid(s2, ", ", translated_name); mut_set_active(mut); }
248 X("Dodging" , _("Dodging") , MUT_DODGING , cvar("g_dodging"))
249 X("InstaGib" , _("InstaGib") , MUT_INSTAGIB , cvar("g_instagib"))
250 X("New Toys" , _("New Toys") , MUT_NEW_TOYS , cvar("g_new_toys"))
251 X("NIX" , _("NIX") , MUT_NIX , cvar("g_nix"))
252 X("Rocket Flying" , _("Rocket Flying") , MUT_ROCKET_FLYING , cvar("g_rocket_flying"))
253 X("Invincible Projectiles" , _("Invincible Projectiles") , MUT_INVINCIBLE_PROJECTILES , cvar("g_invincible_projectiles"))
254 X("Low gravity" , _("Low gravity") , MUT_GRAVITY , cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
255 X("Cloaked" , _("Cloaked") , MUT_CLOAKED , cvar("g_cloaked"))
256 X("Hook" , _("Hook") , MUT_GRAPPLING_HOOK , cvar("g_grappling_hook"))
257 X("Midair" , _("Midair") , MUT_MIDAIR , cvar("g_midair"))
258 X("Melee only Arena" , _("Melee only Arena") , MUT_MELEE_ONLY , cvar("g_melee_only"))
259 X("Vampire" , _("Vampire") , MUT_VAMPIRE , cvar("g_vampire"))
260 X("Piñata" , _("Piñata") , MUT_PINATA , cvar("g_pinata"))
261 X("Weapons stay" , _("Weapons stay") , MUT_WEAPON_STAY , cvar("g_weapon_stay"))
262 X("Blood loss" , _("Blood loss") , MUT_BLOODLOSS , cvar("g_bloodloss") > 0)
263 X("Jetpack" , _("Jetpack") , MUT_JETPACK , cvar("g_jetpack"))
264 X("Buffs" , _("Buffs") , MUT_BUFFS , cvar("g_buffs") > 0)
265 X("Overkill" , _("Overkill") , MUT_OVERKILL , cvar("g_overkill"))
266 X("No powerups" , _("No powerups") , MUT_NO_POWERUPS , cvar("g_powerups") == 0)
267 X("Powerups" , _("Powerups") , MUT_POWERUPS , cvar("g_powerups") > 0)
268 X("Touch explode" , _("Touch explode") , MUT_TOUCHEXPLODE , cvar("g_touchexplode") > 0)
269 X("Wall jumping" , _("Wall jumping") , MUT_WALLJUMP , cvar("g_walljump"))
270 X("No start weapons" , _("No start weapons") , MUT_NO_START_WEAPONS , cvar_string("g_weaponarena") == "0" && cvar("g_balance_blaster_weaponstartoverride") == 0)
271 X("Nades" , _("Nades") , MUT_NADES , cvar("g_nades"))
272 X("Offhand blaster" , _("Offhand blaster") , MUT_OFFHAND_BLASTER , cvar("g_offhand_blaster"))
279 void wordwrap_cb(string s, float l, void(string) callback)
282 float lleft, i, j, wlen;
287 for (i = 0; i < len; ++i)
289 if (substring(s, i, 2) == "\\n")
295 else if (substring(s, i, 1) == "\n")
300 else if (substring(s, i, 1) == " ")
310 for (j = i+1; j < len; ++j)
311 // ^^ this skips over the first character of a word, which
312 // is ALWAYS part of the word
313 // this is safe since if i+1 == strlen(s), i will become
314 // strlen(s)-1 at the end of this block and the function
315 // will terminate. A space can't be the first character we
316 // read here, and neither can a \n be the start, since these
317 // two cases have been handled above.
319 c = substring(s, j, 1);
326 // we need to keep this tempstring alive even if substring is
327 // called repeatedly, so call strcat even though we're not
337 callback(substring(s, i, wlen));
345 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
375 string ScoreString(int pFlags, float pValue, int rounds_played)
380 pValue = floor(pValue + 0.5); // round
382 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
384 else if(pFlags & SFL_RANK)
385 valstr = (pValue < 256 ? count_ordinal(pValue) : _("N/A"));
386 else if(pFlags & SFL_TIME)
387 valstr = TIME_ENCODED_TOSTRING(pValue, true);
388 else if (rounds_played)
389 valstr = sprintf("%.1f", pValue / rounds_played);
391 valstr = ftos(pValue);
397 // compressed vector format:
398 // like MD3, just even shorter
399 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
400 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
401 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
402 // length = 2^(length_encoded/8) / 8
403 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
404 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
405 // the special value 0 indicates the zero vector
407 float lengthLogTable[128];
409 float invertLengthLog(float dist)
413 if(dist >= lengthLogTable[127])
415 if(dist <= lengthLogTable[0])
423 m = floor((l + r) / 2);
424 if(lengthLogTable[m] < dist)
430 // now: r is >=, l is <
431 float lerr = (dist - lengthLogTable[l]);
432 float rerr = (lengthLogTable[r] - dist);
438 vector decompressShortVector(int data)
443 float p = (data & 0xF000) / 0x1000;
444 float q = (data & 0x0F80) / 0x80;
445 int len = (data & 0x007F);
447 //print("\ndecompress: p:", ftos(p)); print(" q:", ftos(q)); print(" len:", ftos(len), "\n");
460 q = .19634954084936207740 * q;
461 p = .19634954084936207740 * p - 1.57079632679489661922;
462 out.x = cos(q) * cos(p);
463 out.y = sin(q) * cos(p);
467 //print("decompressed: ", vtos(out), "\n");
469 return out * lengthLogTable[len];
472 float compressShortVector(vector vec)
478 //print("compress: ", vtos(vec), "\n");
479 ang = vectoangles(vec);
483 if(ang.x < -90 && ang.x > +90)
484 error("BOGUS vectoangles");
485 //print("angles: ", vtos(ang), "\n");
487 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
496 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
497 len = invertLengthLog(vlen(vec));
499 //print("compressed: p:", ftos(p)); print(" y:", ftos(y)); print(" len:", ftos(len), "\n");
501 return (p * 0x1000) + (y * 0x80) + len;
504 STATIC_INIT(compressShortVector)
507 float f = (2 ** (1/8));
509 for(i = 0; i < 128; ++i)
511 lengthLogTable[i] = l;
515 if(cvar("developer") > 0)
517 LOG_TRACE("Verifying vector compression table...");
518 for(i = 0x0F00; i < 0xFFFF; ++i)
519 if(i != compressShortVector(decompressShortVector(i)))
522 "BROKEN vector compression: %s -> %s -> %s",
524 vtos(decompressShortVector(i)),
525 ftos(compressShortVector(decompressShortVector(i)))
533 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
535 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
536 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
537 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
538 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
539 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
540 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
541 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
542 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
543 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
544 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
545 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
546 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
551 string fixPriorityList(string order, float from, float to, float subtract, float complete)
556 n = tokenize_console(order);
558 for(i = 0; i < n; ++i)
563 if(w >= from && w <= to)
564 neworder = strcat(neworder, ftos(w), " ");
568 if(w >= from && w <= to)
569 neworder = strcat(neworder, ftos(w), " ");
576 n = tokenize_console(neworder);
577 for(w = to; w >= from; --w)
579 int wflags = REGISTRY_GET(Weapons, w).spawnflags;
580 if(wflags & WEP_FLAG_SPECIALATTACK)
582 for(i = 0; i < n; ++i)
583 if(stof(argv(i)) == w)
585 if(i == n) // not found
586 neworder = strcat(neworder, ftos(w), " ");
590 return substring(neworder, 0, strlen(neworder) - 1);
593 string mapPriorityList(string order, string(string) mapfunc)
598 n = tokenize_console(order);
600 for(float i = 0; i < n; ++i)
601 neworder = strcat(neworder, mapfunc(argv(i)), " ");
603 return substring(neworder, 0, strlen(neworder) - 1);
606 string swapInPriorityList(string order, float i, float j)
608 float n = tokenize_console(order);
610 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
613 for(float w = 0; w < n; ++w)
616 s = strcat(s, argv(j), " ");
618 s = strcat(s, argv(i), " ");
620 s = strcat(s, argv(w), " ");
622 return substring(s, 0, strlen(s) - 1);
629 void get_mi_min_max(float mode)
634 if(!strcasecmp(substring(s, 0, 5), "maps/"))
635 s = substring(s, 5, strlen(s) - 5);
636 if(!strcasecmp(substring(s, strlen(s) - 4, 4), ".bsp"))
637 s = substring(s, 0, strlen(s) - 4);
638 strcpy(mi_shortname, s);
650 MapInfo_Get_ByName(mi_shortname, 0, NULL);
651 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
653 mi_min = MapInfo_Map_mins;
654 mi_max = MapInfo_Map_maxs;
662 tracebox('1 0 0' * mi.x,
663 '0 1 0' * mi.y + '0 0 1' * mi.z,
664 '0 1 0' * ma.y + '0 0 1' * ma.z,
668 if(!trace_startsolid)
669 mi_min.x = trace_endpos.x;
671 tracebox('0 1 0' * mi.y,
672 '1 0 0' * mi.x + '0 0 1' * mi.z,
673 '1 0 0' * ma.x + '0 0 1' * ma.z,
677 if(!trace_startsolid)
678 mi_min.y = trace_endpos.y;
680 tracebox('0 0 1' * mi.z,
681 '1 0 0' * mi.x + '0 1 0' * mi.y,
682 '1 0 0' * ma.x + '0 1 0' * ma.y,
686 if(!trace_startsolid)
687 mi_min.z = trace_endpos.z;
689 tracebox('1 0 0' * ma.x,
690 '0 1 0' * mi.y + '0 0 1' * mi.z,
691 '0 1 0' * ma.y + '0 0 1' * ma.z,
695 if(!trace_startsolid)
696 mi_max.x = trace_endpos.x;
698 tracebox('0 1 0' * ma.y,
699 '1 0 0' * mi.x + '0 0 1' * mi.z,
700 '1 0 0' * ma.x + '0 0 1' * ma.z,
704 if(!trace_startsolid)
705 mi_max.y = trace_endpos.y;
707 tracebox('0 0 1' * ma.z,
708 '1 0 0' * mi.x + '0 1 0' * mi.y,
709 '1 0 0' * ma.x + '0 1 0' * ma.y,
713 if(!trace_startsolid)
714 mi_max.z = trace_endpos.z;
719 void get_mi_min_max_texcoords(float mode)
723 get_mi_min_max(mode);
728 // extend mi_picmax to get a square aspect ratio
729 // center the map in that area
730 extend = mi_picmax - mi_picmin;
731 if(extend.y > extend.x)
733 mi_picmin.x -= (extend.y - extend.x) * 0.5;
734 mi_picmax.x += (extend.y - extend.x) * 0.5;
738 mi_picmin.y -= (extend.x - extend.y) * 0.5;
739 mi_picmax.y += (extend.x - extend.y) * 0.5;
742 // add another some percent
743 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
747 // calculate the texcoords
748 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
749 // first the two corners of the origin
750 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
751 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
752 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
753 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
754 // then the other corners
755 mi_pictexcoord1_x = mi_pictexcoord0_x;
756 mi_pictexcoord1_y = mi_pictexcoord2_y;
757 mi_pictexcoord3_x = mi_pictexcoord2_x;
758 mi_pictexcoord3_y = mi_pictexcoord0_y;
762 float cvar_settemp(string tmp_cvar, string tmp_value)
764 float created_saved_value;
766 created_saved_value = 0;
768 if (!(tmp_cvar || tmp_value))
770 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
774 if(!cvar_type(tmp_cvar))
776 LOG_INFOF("Error: cvar %s doesn't exist!", tmp_cvar);
780 IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
782 created_saved_value = -1; // skip creation
783 break; // no need to continue
786 if(created_saved_value != -1)
788 // creating a new entity to keep track of this cvar
789 entity e = new_pure(saved_cvar_value);
790 IL_PUSH(g_saved_cvars, e);
791 e.netname = strzone(tmp_cvar);
792 e.message = strzone(cvar_string(tmp_cvar));
793 created_saved_value = 1;
796 // update the cvar to the value given
797 cvar_set(tmp_cvar, tmp_value);
799 return created_saved_value;
802 int cvar_settemp_restore()
805 // FIXME this new-style loop fails!
807 FOREACH_ENTITY_CLASS("saved_cvar_value", true,
809 if(cvar_type(it.netname))
811 cvar_set(it.netname, it.message);
812 strunzone(it.netname);
813 strunzone(it.message);
818 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", it.netname);
823 while((e = find(e, classname, "saved_cvar_value")))
825 if(cvar_type(e.netname))
827 cvar_set(e.netname, e.message);
832 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", e.netname));
839 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
842 // The following function is SLOW.
843 // For your safety and for the protection of those around you...
844 // DO NOT CALL THIS AT HOME.
846 if(w(theText, theSize) <= maxWidth)
847 return strlen(theText); // yeah!
849 bool colors = (w("^7", theSize) == 0);
851 // binary search for right place to cut string
852 int len, left, right, middle;
854 right = len = strlen(theText);
858 middle = floor((left + right) / 2);
861 vector res = checkColorCode(theText, len, middle, false);
862 ofs = (res.x) ? res.x - res.y : 0;
865 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
870 while(left < right - 1);
875 float textLengthUpToLength(string theText, int maxLength, textLengthUpToLength_lenFunction_t w)
878 // The following function is SLOW.
879 // For your safety and for the protection of those around you...
880 // DO NOT CALL THIS AT HOME.
882 if(w(theText) <= maxLength)
883 return strlen(theText); // yeah!
885 bool colors = (w("^7") == 0);
887 // binary search for right place to cut string
888 int len, left, right, middle;
890 right = len = strlen(theText);
894 middle = floor((left + right) / 2);
897 vector res = checkColorCode(theText, len, middle, true);
898 ofs = (!res.x) ? 0 : res.x - res.y;
901 if(w(substring(theText, 0, middle + ofs)) <= maxLength)
906 while(left < right - 1);
911 string find_last_color_code(string s)
913 int start = strstrofs(s, "^", 0);
914 if (start == -1) // no caret found
916 int len = strlen(s)-1;
917 for(int i = len; i >= start; --i)
919 if(substring(s, i, 1) != "^")
923 while (i-carets >= start && substring(s, i-carets, 1) == "^")
926 // check if carets aren't all escaped
930 if(IS_DIGIT(substring(s, i+1, 1)))
931 return substring(s, i, 2);
934 if(substring(s, i+1, 1) == "x")
935 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
936 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
937 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
938 return substring(s, i, 5);
940 i -= carets; // this also skips one char before the carets
946 string getWrappedLine(float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
948 string s = getWrappedLine_remaining;
952 getWrappedLine_remaining = string_null;
953 return s; // the line has no size ANYWAY, nothing would be displayed.
956 int take_until = textLengthUpToWidth(s, maxWidth, theFontSize, tw);
957 if(take_until > 0 && take_until < strlen(s))
959 int last_word = take_until - 1;
960 while(last_word > 0 && substring(s, last_word, 1) != " ")
966 take_until = last_word;
970 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
971 if(getWrappedLine_remaining == "")
972 getWrappedLine_remaining = string_null;
973 else if (tw("^7", theFontSize) == 0)
974 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
975 return substring(s, 0, take_until);
979 getWrappedLine_remaining = string_null;
984 string getWrappedLineLen(int maxLength, textLengthUpToLength_lenFunction_t tw)
986 string s = getWrappedLine_remaining;
990 getWrappedLine_remaining = string_null;
991 return s; // the line has no size ANYWAY, nothing would be displayed.
994 int take_until = textLengthUpToLength(s, maxLength, tw);
995 if(take_until > 0 && take_until < strlen(s))
997 int last_word = take_until - 1;
998 while(last_word > 0 && substring(s, last_word, 1) != " ")
1004 take_until = last_word;
1008 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
1009 if(getWrappedLine_remaining == "")
1010 getWrappedLine_remaining = string_null;
1011 else if (tw("^7") == 0)
1012 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
1013 return substring(s, 0, take_until);
1017 getWrappedLine_remaining = string_null;
1022 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1024 if(tw(theText, theFontSize) <= maxWidth)
1027 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1030 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1032 if(tw(theText) <= maxWidth)
1035 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1038 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1040 string subpattern, subpattern2, subpattern3, subpattern4;
1041 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1043 subpattern2 = ",teams,";
1045 subpattern2 = ",noteams,";
1047 subpattern3 = ",teamspawns,";
1049 subpattern3 = ",noteamspawns,";
1050 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1051 subpattern4 = ",race,";
1053 subpattern4 = string_null;
1055 if(substring(pattern, 0, 1) == "-")
1057 pattern = substring(pattern, 1, strlen(pattern) - 1);
1058 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1060 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1062 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1064 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1069 if(substring(pattern, 0, 1) == "+")
1070 pattern = substring(pattern, 1, strlen(pattern) - 1);
1071 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1072 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1073 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1077 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1084 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1088 // make origin and speed relative
1093 // now solve for ret, ret normalized:
1094 // eorg + t * evel == t * ret * spd
1095 // or, rather, solve for t:
1096 // |eorg + t * evel| == t * spd
1097 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1098 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1099 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1100 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1101 // q = (eorg * eorg) / (evel * evel - spd * spd)
1102 if(!solution.z) // no real solution
1105 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1106 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1107 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1108 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1109 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1110 // spd < |evel| * sin angle(evel, eorg)
1113 else if(solution.x > 0)
1115 // both solutions > 0: take the smaller one
1116 // happens if p < 0 and q > 0
1117 ret = normalize(eorg + solution.x * evel);
1119 else if(solution.y > 0)
1121 // one solution > 0: take the larger one
1122 // happens if q < 0 or q == 0 and p < 0
1123 ret = normalize(eorg + solution.y * evel);
1127 // no solution > 0: reject
1128 // happens if p > 0 and q >= 0
1129 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1130 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1135 // "Enemy is moving away from me at more than spd"
1139 // NOTE: we always got a solution if spd > |evel|
1141 if(newton_style == 2)
1142 ret = normalize(ret * spd + myvel);
1147 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1152 if(newton_style == 2)
1154 // true Newtonian projectiles with automatic aim adjustment
1156 // solve: |outspeed * mydir - myvel| = spd
1157 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1158 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1162 // myvel^2 - (mydir * myvel)^2 > spd^2
1163 // velocity without mydir component > spd
1164 // fire at smallest possible spd that works?
1165 // |(mydir * myvel) * myvel - myvel| = spd
1167 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1171 outspeed = solution.y; // the larger one
1174 //outspeed = 0; // slowest possible shot
1175 outspeed = solution.x; // the real part (that is, the average!)
1176 //dprint("impossible shot, adjusting\n");
1179 outspeed = bound(spd * mi, outspeed, spd * ma);
1180 return mydir * outspeed;
1184 return myvel + spd * mydir;
1188 * Compresses the shot origin offset vector to an int with the following format:
1189 * xxxxxxxx|Syyyyyyy|Syzzzzzz
1190 * 32109876|54321098|76543210
1191 * 1st byte: x component
1192 * 2nd byte: y component in the last 7 bits and the sign of the x component in the 1st bit
1193 * 3rd byte: z component in the last 7 bits and the sign of the z component in the 1st bit
1194 * All values are multiplied on compression and divided on decompression
1195 * so the precision of the values is 0.5 if multiplied by 2 and 0.25 if multiplied by 4
1196 * Values are bound to the following ranges:
1198 * y: 0 +31.75 negative values not allowed because the gun can't be aligned to the left by default
1201 float compressShotOrigin(vector v)
1203 int rx_neg = (v.x < 0) ? 1 : 0;
1204 int rz_neg = (v.z < 0) ? 1 : 0;
1205 int rx = rint(fabs(v.x) * 2);
1206 int ry = rint(v.y * 4);
1207 int rz = rint(fabs(v.z) * 4);
1208 if(rx > 255) // 128 * 2 - 1
1210 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1211 rx = bound(0, rx, 255);
1213 if(ry > 127 || ry < 0) // 32 * 4 - 1
1215 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1216 ry = bound(0, ry, 127);
1218 if(rz > 127) // 32 * 4 - 1
1220 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1221 rz = bound(0, rz, 127);
1223 ry |= rx_neg * BIT(7);
1224 rz |= rz_neg * BIT(7);
1225 return rx * 0x10000 + ry * 0x100 + rz;
1227 vector decompressShotOrigin(int f)
1231 v.y = (f & 0xFF00) >> 8;
1233 // remove sign bits and apply sign
1234 if (v.y & BIT(7)) { v.y &= ~BIT(7); v.x *= -1; }
1235 if (v.z & BIT(7)) { v.z &= ~BIT(7); v.z *= -1; }
1236 return vec3(v.x / 2, v.y / 4, v.z / 4);
1240 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1242 // NOTE: we'll always choose the SMALLER value...
1243 float healthdamage, armordamage, armorideal;
1244 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1247 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1248 armordamage = a + (h - 1); // damage we can take if we could use more armor
1249 armorideal = healthdamage * armorblock;
1251 if(armordamage < healthdamage)
1264 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1267 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1269 if (deathtype & HITTYPE_ARMORPIERCE)
1271 v.y = bound(0, damage * armorblock, a); // save
1272 v.x = bound(0, damage - v.y, damage); // take
1278 string getcurrentmod()
1282 m = cvar_string("fs_gamedir");
1283 n = tokenize_console(m);
1290 float matchacl(string acl, string str)
1297 t = car(acl); acl = cdr(acl);
1300 if(substring(t, 0, 1) == "-")
1303 t = substring(t, 1, strlen(t) - 1);
1305 else if(substring(t, 0, 1) == "+")
1306 t = substring(t, 1, strlen(t) - 1);
1308 if(substring(t, -1, 1) == "*")
1310 t = substring(t, 0, strlen(t) - 1);
1311 s = substring(str, 0, strlen(t));
1319 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1326 void write_String_To_File(int fh, string str, bool alsoprint)
1329 if (alsoprint) LOG_HELP(str);
1332 string get_model_datafilename(string m, float sk, string fil)
1337 m = "models/player/*_";
1339 m = strcat(m, ftos(sk));
1342 return strcat(m, ".", fil);
1345 float get_model_parameters(string m, float sk)
1347 get_model_parameters_modelname = string_null;
1348 get_model_parameters_modelskin = -1;
1349 get_model_parameters_name = string_null;
1350 get_model_parameters_species = -1;
1351 get_model_parameters_sex = string_null;
1352 get_model_parameters_weight = -1;
1353 get_model_parameters_age = -1;
1354 get_model_parameters_desc = string_null;
1355 get_model_parameters_bone_upperbody = string_null;
1356 get_model_parameters_bone_weapon = string_null;
1357 for(int i = 0; i < MAX_AIM_BONES; ++i)
1359 get_model_parameters_bone_aim[i] = string_null;
1360 get_model_parameters_bone_aimweight[i] = 0;
1362 get_model_parameters_fixbone = 0;
1363 get_model_parameters_hidden = false;
1366 MUTATOR_CALLHOOK(ClearModelParams);
1372 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1373 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1377 if(substring(m, -4, -1) != ".txt")
1379 if(substring(m, -6, 1) != "_")
1381 sk = stof(substring(m, -5, 1));
1382 m = substring(m, 0, -7);
1385 string fn = get_model_datafilename(m, sk, "txt");
1386 int fh = fopen(fn, FILE_READ);
1390 fn = get_model_datafilename(m, sk, "txt");
1391 fh = fopen(fn, FILE_READ);
1396 get_model_parameters_modelname = m;
1397 get_model_parameters_modelskin = sk;
1399 while((s = fgets(fh)))
1402 break; // next lines will be description
1406 get_model_parameters_name = s;
1410 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1411 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1412 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1413 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1414 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1415 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1416 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1420 if (s == "Male") s = _("Male");
1421 else if (s == "Female") s = _("Female");
1422 else if (s == "Undisclosed") s = _("Undisclosed");
1423 get_model_parameters_sex = s;
1426 get_model_parameters_weight = stof(s);
1428 get_model_parameters_age = stof(s);
1429 if(c == "description")
1430 get_model_parameters_description = s;
1431 if(c == "bone_upperbody")
1432 get_model_parameters_bone_upperbody = s;
1433 if(c == "bone_weapon")
1434 get_model_parameters_bone_weapon = s;
1436 MUTATOR_CALLHOOK(GetModelParams, c, s);
1438 for(int i = 0; i < MAX_AIM_BONES; ++i)
1439 if(c == strcat("bone_aim", ftos(i)))
1441 get_model_parameters_bone_aimweight[i] = stof(car(s));
1442 get_model_parameters_bone_aim[i] = cdr(s);
1445 get_model_parameters_fixbone = stof(s);
1447 get_model_parameters_hidden = stob(s);
1450 while((s = fgets(fh)))
1452 if(get_model_parameters_desc)
1453 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1455 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1463 string translate_key(string key)
1465 if (prvm_language == "en") return key;
1467 if (substring(key, 0, 1) == "<")
1469 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1470 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1475 case "TAB": return _("TAB");
1476 case "ENTER": return _("ENTER");
1477 case "ESCAPE": return _("ESCAPE");
1478 case "SPACE": return _("SPACE");
1480 case "BACKSPACE": return _("BACKSPACE");
1481 case "UPARROW": return _("UPARROW");
1482 case "DOWNARROW": return _("DOWNARROW");
1483 case "LEFTARROW": return _("LEFTARROW");
1484 case "RIGHTARROW": return _("RIGHTARROW");
1486 case "ALT": return _("ALT");
1487 case "CTRL": return _("CTRL");
1488 case "SHIFT": return _("SHIFT");
1490 case "INS": return _("INS");
1491 case "DEL": return _("DEL");
1492 case "PGDN": return _("PGDN");
1493 case "PGUP": return _("PGUP");
1494 case "HOME": return _("HOME");
1495 case "END": return _("END");
1497 case "PAUSE": return _("PAUSE");
1499 case "NUMLOCK": return _("NUMLOCK");
1500 case "CAPSLOCK": return _("CAPSLOCK");
1501 case "SCROLLOCK": return _("SCROLLOCK");
1503 case "SEMICOLON": return _("SEMICOLON");
1504 case "TILDE": return _("TILDE");
1505 case "BACKQUOTE": return _("BACKQUOTE");
1506 case "QUOTE": return _("QUOTE");
1507 case "APOSTROPHE": return _("APOSTROPHE");
1508 case "BACKSLASH": return _("BACKSLASH");
1511 if (substring(key, 0, 1) == "F")
1513 string subkey = substring(key, 1, -1);
1514 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1516 return sprintf(_("F%d"), stof(subkey));
1518 // continue in case there is another key name starting with F
1521 if (substring(key, 0, 3) == "KP_")
1523 string subkey = substring(key, 3, -1);
1524 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1526 return sprintf(_("KP_%d"), stof(subkey));
1531 case "INS": return sprintf(_("KP_%s"), _("INS"));
1532 case "END": return sprintf(_("KP_%s"), _("END"));
1533 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1534 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1535 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1536 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1537 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1538 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1539 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1540 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1541 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1542 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1543 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1544 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1545 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1546 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1547 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1548 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1549 default: return key;
1553 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1555 if (substring(key, 0, 5) == "MOUSE")
1556 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1558 if (key == "MWHEELUP") return _("MWHEELUP");
1559 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1561 if (substring(key, 0,3) == "JOY")
1562 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1564 if (substring(key, 0,3) == "AUX")
1565 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1567 if (substring(key, 0, 4) == "X360_")
1569 string subkey = substring(key, 4, -1);
1572 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1573 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1574 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1575 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1576 case "START": return sprintf(_("X360_%s"), _("START"));
1577 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1578 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1579 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1580 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1581 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1582 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1583 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1584 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1585 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1586 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1587 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1588 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1589 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1590 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1591 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1592 default: return key;
1596 if (substring(key, 0, 4) == "JOY_")
1598 string subkey = substring(key, 4, -1);
1601 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1602 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1603 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1604 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1605 default: return key;
1609 if (substring(key, 0, 8) == "MIDINOTE")
1610 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1615 // x-encoding (encoding as zero length invisible string)
1616 const string XENCODE_2 = "xX";
1617 const string XENCODE_22 = "0123456789abcdefABCDEF";
1618 string xencode(int f)
1621 d = f % 22; f = floor(f / 22);
1622 c = f % 22; f = floor(f / 22);
1623 b = f % 22; f = floor(f / 22);
1624 a = f % 2; // f = floor(f / 2);
1627 substring(XENCODE_2, a, 1),
1628 substring(XENCODE_22, b, 1),
1629 substring(XENCODE_22, c, 1),
1630 substring(XENCODE_22, d, 1)
1633 float xdecode(string s)
1636 if(substring(s, 0, 1) != "^")
1640 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1641 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1642 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1643 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1644 if(a < 0 || b < 0 || c < 0 || d < 0)
1646 return ((a * 22 + b) * 22 + c) * 22 + d;
1650 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1652 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1655 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1658 float shutdown_running;
1663 void CSQC_Shutdown()
1669 if(shutdown_running)
1671 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1675 shutdown_running = 1;
1679 cvar_settemp_restore(); // this must be done LAST, but in any case
1683 .float skeleton_bones_index;
1684 void Skeleton_SetBones(entity e)
1686 // set skeleton_bones to the total number of bones on the model
1687 if(e.skeleton_bones_index == e.modelindex)
1688 return; // same model, nothing to update
1691 skelindex = skel_create(e.modelindex);
1692 e.skeleton_bones = skel_get_numbones(skelindex);
1693 skel_delete(skelindex);
1694 e.skeleton_bones_index = e.modelindex;
1698 string to_execute_next_frame;
1699 void execute_next_frame()
1701 if(to_execute_next_frame)
1703 localcmd("\n", to_execute_next_frame, "\n");
1704 strfree(to_execute_next_frame);
1707 void queue_to_execute_next_frame(string s)
1709 if(to_execute_next_frame)
1711 s = strcat(s, "\n", to_execute_next_frame);
1713 strcpy(to_execute_next_frame, s);
1716 .float FindConnectedComponent_processing;
1717 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1719 entity queue_start, queue_end;
1721 // we build a queue of to-be-processed entities.
1722 // queue_start is the next entity to be checked for neighbors
1723 // queue_end is the last entity added
1725 if(e.FindConnectedComponent_processing)
1726 error("recursion or broken cleanup");
1728 // start with a 1-element queue
1729 queue_start = queue_end = e;
1730 queue_end.(fld) = NULL;
1731 queue_end.FindConnectedComponent_processing = 1;
1733 // for each queued item:
1734 for (; queue_start; queue_start = queue_start.(fld))
1736 // find all neighbors of queue_start
1738 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1740 if(t.FindConnectedComponent_processing)
1742 if(iscon(t, queue_start, pass))
1744 // it is connected? ADD IT. It will look for neighbors soon too.
1745 queue_end.(fld) = t;
1747 queue_end.(fld) = NULL;
1748 queue_end.FindConnectedComponent_processing = 1;
1754 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1755 queue_start.FindConnectedComponent_processing = 0;
1759 vector animfixfps(entity e, vector a, vector b)
1761 // multi-frame anim: keep as-is
1764 float dur = frameduration(e.modelindex, a.x);
1765 if (dur <= 0 && b.y)
1768 dur = frameduration(e.modelindex, a.x);
1778 Notification Announcer_PickNumber(int type, int num)
1787 case 10: return ANNCE_NUM_GAMESTART_10;
1788 case 9: return ANNCE_NUM_GAMESTART_9;
1789 case 8: return ANNCE_NUM_GAMESTART_8;
1790 case 7: return ANNCE_NUM_GAMESTART_7;
1791 case 6: return ANNCE_NUM_GAMESTART_6;
1792 case 5: return ANNCE_NUM_GAMESTART_5;
1793 case 4: return ANNCE_NUM_GAMESTART_4;
1794 case 3: return ANNCE_NUM_GAMESTART_3;
1795 case 2: return ANNCE_NUM_GAMESTART_2;
1796 case 1: return ANNCE_NUM_GAMESTART_1;
1804 case 10: return ANNCE_NUM_KILL_10;
1805 case 9: return ANNCE_NUM_KILL_9;
1806 case 8: return ANNCE_NUM_KILL_8;
1807 case 7: return ANNCE_NUM_KILL_7;
1808 case 6: return ANNCE_NUM_KILL_6;
1809 case 5: return ANNCE_NUM_KILL_5;
1810 case 4: return ANNCE_NUM_KILL_4;
1811 case 3: return ANNCE_NUM_KILL_3;
1812 case 2: return ANNCE_NUM_KILL_2;
1813 case 1: return ANNCE_NUM_KILL_1;
1821 case 10: return ANNCE_NUM_RESPAWN_10;
1822 case 9: return ANNCE_NUM_RESPAWN_9;
1823 case 8: return ANNCE_NUM_RESPAWN_8;
1824 case 7: return ANNCE_NUM_RESPAWN_7;
1825 case 6: return ANNCE_NUM_RESPAWN_6;
1826 case 5: return ANNCE_NUM_RESPAWN_5;
1827 case 4: return ANNCE_NUM_RESPAWN_4;
1828 case 3: return ANNCE_NUM_RESPAWN_3;
1829 case 2: return ANNCE_NUM_RESPAWN_2;
1830 case 1: return ANNCE_NUM_RESPAWN_1;
1834 case CNT_ROUNDSTART:
1838 case 10: return ANNCE_NUM_ROUNDSTART_10;
1839 case 9: return ANNCE_NUM_ROUNDSTART_9;
1840 case 8: return ANNCE_NUM_ROUNDSTART_8;
1841 case 7: return ANNCE_NUM_ROUNDSTART_7;
1842 case 6: return ANNCE_NUM_ROUNDSTART_6;
1843 case 5: return ANNCE_NUM_ROUNDSTART_5;
1844 case 4: return ANNCE_NUM_ROUNDSTART_4;
1845 case 3: return ANNCE_NUM_ROUNDSTART_3;
1846 case 2: return ANNCE_NUM_ROUNDSTART_2;
1847 case 1: return ANNCE_NUM_ROUNDSTART_1;
1856 case 10: return ANNCE_NUM_10;
1857 case 9: return ANNCE_NUM_9;
1858 case 8: return ANNCE_NUM_8;
1859 case 7: return ANNCE_NUM_7;
1860 case 6: return ANNCE_NUM_6;
1861 case 5: return ANNCE_NUM_5;
1862 case 4: return ANNCE_NUM_4;
1863 case 3: return ANNCE_NUM_3;
1864 case 2: return ANNCE_NUM_2;
1865 case 1: return ANNCE_NUM_1;
1874 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1876 switch(nativecontents)
1881 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1883 return DPCONTENTS_WATER;
1885 return DPCONTENTS_SLIME;
1887 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1889 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1894 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1896 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1897 return CONTENT_SOLID;
1898 if(supercontents & DPCONTENTS_SKY)
1900 if(supercontents & DPCONTENTS_LAVA)
1901 return CONTENT_LAVA;
1902 if(supercontents & DPCONTENTS_SLIME)
1903 return CONTENT_SLIME;
1904 if(supercontents & DPCONTENTS_WATER)
1905 return CONTENT_WATER;
1906 return CONTENT_EMPTY;
1911 void attach_sameorigin(entity e, entity to, string tag)
1913 vector org, t_forward, t_left, t_up, e_forward, e_up;
1916 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1917 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1918 t_forward = v_forward * tagscale;
1919 t_left = v_right * -tagscale;
1920 t_up = v_up * tagscale;
1922 e.origin_x = org * t_forward;
1923 e.origin_y = org * t_left;
1924 e.origin_z = org * t_up;
1926 // current forward and up directions
1927 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1928 e.angles = AnglesTransform_FromVAngles(e.angles);
1930 e.angles = AnglesTransform_FromAngles(e.angles);
1931 fixedmakevectors(e.angles);
1933 // untransform forward, up!
1934 e_forward.x = v_forward * t_forward;
1935 e_forward.y = v_forward * t_left;
1936 e_forward.z = v_forward * t_up;
1937 e_up.x = v_up * t_forward;
1938 e_up.y = v_up * t_left;
1939 e_up.z = v_up * t_up;
1941 e.angles = fixedvectoangles2(e_forward, e_up);
1942 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1943 e.angles = AnglesTransform_ToVAngles(e.angles);
1945 e.angles = AnglesTransform_ToAngles(e.angles);
1947 setattachment(e, to, tag);
1948 setorigin(e, e.origin);
1951 void detach_sameorigin(entity e)
1954 org = gettaginfo(e, 0);
1955 e.angles = fixedvectoangles2(v_forward, v_up);
1956 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1957 e.angles = AnglesTransform_ToVAngles(e.angles);
1959 e.angles = AnglesTransform_ToAngles(e.angles);
1961 setattachment(e, NULL, "");
1962 setorigin(e, e.origin);
1965 void follow_sameorigin(entity e, entity to)
1967 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1968 e.aiment = to; // make the hole follow bmodel
1969 e.punchangle = to.angles; // the original angles of bmodel
1970 e.view_ofs = e.origin - to.origin; // relative origin
1971 e.v_angle = e.angles - to.angles; // relative angles
1975 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1976 void unfollow_sameorigin(entity e)
1978 set_movetype(e, MOVETYPE_NONE);
1982 .string aiment_classname;
1983 .float aiment_deadflag;
1984 void SetMovetypeFollow(entity ent, entity e)
1986 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1987 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.
1988 ent.aiment = e; // make the hole follow bmodel
1989 ent.punchangle = e.angles; // the original angles of bmodel
1990 ent.view_ofs = ent.origin - e.origin; // relative origin
1991 ent.v_angle = ent.angles - e.angles; // relative angles
1992 ent.aiment_classname = e.classname;
1993 ent.aiment_deadflag = e.deadflag;
1995 if(IS_PLAYER(ent.aiment))
1997 entity pl = ent.aiment;
1998 ent.view_ofs.x = bound(pl.mins.x + 4, ent.view_ofs.x, pl.maxs.x - 4);
1999 ent.view_ofs.y = bound(pl.mins.y + 4, ent.view_ofs.y, pl.maxs.y - 4);
2000 ent.view_ofs.z = bound(pl.mins.z + 4, ent.view_ofs.z, pl.maxs.z - 4);
2004 void UnsetMovetypeFollow(entity ent)
2006 set_movetype(ent, MOVETYPE_FLY);
2007 PROJECTILE_MAKETRIGGER(ent);
2008 ent.aiment_classname = string_null;
2009 // FIXME: engine bug?
2010 // resetting aiment the engine will set orb's origin close to world's origin
2011 //ent.aiment = NULL;
2014 int LostMovetypeFollow(entity ent)
2017 if(ent.move_movetype != MOVETYPE_FOLLOW)
2021 // FIXME: engine bug?
2022 // when aiment disconnects the engine will set orb's origin close to world's origin
2025 if(ent.aiment.classname != ent.aiment_classname || ent.aiment.deadflag != ent.aiment_deadflag)
2032 // decolorizes and team colors the player name when needed
2033 string playername(string thename, int teamid, bool team_colorize)
2036 bool do_colorize = (teamplay && team_colorize);
2038 if(do_colorize && !intermission_running)
2043 string t = Team_ColorCode(teamid);
2044 return strcat(t, strdecolorize(thename));
2050 float trace_hits_box_a0, trace_hits_box_a1;
2052 float trace_hits_box_1d(float end, float thmi, float thma)
2056 // just check if x is in range
2064 // do the trace with respect to x
2065 // 0 -> end has to stay in thmi -> thma
2066 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2067 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2068 if (trace_hits_box_a0 > trace_hits_box_a1)
2074 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2079 // now it is a trace from 0 to end
2081 trace_hits_box_a0 = 0;
2082 trace_hits_box_a1 = 1;
2084 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2086 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2088 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2094 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2096 return trace_hits_box(start, end, thmi - ma, thma - mi);
2101 float cvar_or(string cv, float v)
2103 string s = cvar_string(cv);
2110 // NOTE base is the central value
2111 // freq: circle frequency, = 2*pi*frequency in hertz
2113 // -1 start from the lower value
2114 // 0 start from the base value
2115 // 1 start from the higher value
2117 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2120 // RMS = sqrt(base^2 + 0.5 * range^2)
2122 // base = sqrt(RMS^2 - 0.5 * range^2)
2125 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2129 float blink(float base, float range, float freq)
2131 return blink_synced(base, range, freq, 0, 0);