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 #define X(name, translated_name, mut, cond) \
246 if(arg == name || (!n && (cond))) { s2 = cons_mid(s2, ", ", translated_name); mut_set_active(mut); }
247 X("Dodging" , _("Dodging") , MUT_DODGING , cvar("g_dodging"))
248 X("InstaGib" , _("InstaGib") , MUT_INSTAGIB , cvar("g_instagib"))
249 X("New Toys" , _("New Toys") , MUT_NEW_TOYS , cvar("g_new_toys"))
250 X("NIX" , _("NIX") , MUT_NIX , cvar("g_nix"))
251 X("Rocket Flying" , _("Rocket Flying") , MUT_ROCKET_FLYING , cvar("g_rocket_flying"))
252 X("Invincible Projectiles" , _("Invincible Projectiles") , MUT_INVINCIBLE_PROJECTILES , cvar("g_invincible_projectiles"))
253 X("Low gravity" , _("Low gravity") , MUT_GRAVITY , cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
254 X("Cloaked" , _("Cloaked") , MUT_CLOAKED , cvar("g_cloaked"))
255 X("Hook" , _("Hook") , MUT_GRAPPLING_HOOK , cvar("g_grappling_hook"))
256 X("Midair" , _("Midair") , MUT_MIDAIR , cvar("g_midair"))
257 X("Melee only" , _("Melee only") , MUT_MELEE_ONLY , cvar("g_melee_only"))
258 X("Vampire" , _("Vampire") , MUT_VAMPIRE , cvar("g_vampire"))
259 X("Piñata" , _("Piñata") , MUT_PINATA , cvar("g_pinata"))
260 X("Weapons stay" , _("Weapons stay") , MUT_WEAPON_STAY , cvar("g_weapon_stay"))
261 X("Blood loss" , _("Blood loss") , MUT_BLOODLOSS , cvar("g_bloodloss") > 0)
262 X("Jetpack" , _("Jetpack") , MUT_JETPACK , cvar("g_jetpack"))
263 X("Buffs" , _("Buffs") , MUT_BUFFS , cvar("g_buffs") > 0)
264 X("Overkill" , _("Overkill") , MUT_OVERKILL , cvar("g_overkill"))
265 X("No powerups" , _("No powerups") , MUT_NO_POWERUPS , cvar("g_powerups") == 0)
266 X("Powerups" , _("Powerups") , MUT_POWERUPS , cvar("g_powerups") > 0)
267 X("Touch explode" , _("Touch explode") , MUT_TOUCHEXPLODE , cvar("g_touchexplode") > 0)
268 X("Wall jumping" , _("Wall jumping") , MUT_WALLJUMP , cvar("g_walljump"))
269 X("No start weapons" , _("No start weapons") , MUT_WEAPONARENA , cvar_string("g_weaponarena") == "0" && cvar("g_balance_blaster_weaponstartoverride") == 0)
270 X("Nades" , _("Nades") , MUT_NADES , cvar("g_nades"))
271 X("Offhand blaster" , _("Offhand blaster") , MUT_OFFHAND_BLASTER , cvar("g_offhand_blaster"))
278 void wordwrap_cb(string s, float l, void(string) callback)
281 float lleft, i, j, wlen;
286 for (i = 0; i < len; ++i)
288 if (substring(s, i, 2) == "\\n")
294 else if (substring(s, i, 1) == "\n")
299 else if (substring(s, i, 1) == " ")
309 for (j = i+1; j < len; ++j)
310 // ^^ this skips over the first character of a word, which
311 // is ALWAYS part of the word
312 // this is safe since if i+1 == strlen(s), i will become
313 // strlen(s)-1 at the end of this block and the function
314 // will terminate. A space can't be the first character we
315 // read here, and neither can a \n be the start, since these
316 // two cases have been handled above.
318 c = substring(s, j, 1);
325 // we need to keep this tempstring alive even if substring is
326 // called repeatedly, so call strcat even though we're not
336 callback(substring(s, i, wlen));
344 void depthfirst(entity start, .entity up, .entity downleft, .entity right, void(entity, entity) funcPre, void(entity, entity) funcPost, entity pass)
374 string ScoreString(int pFlags, float pValue)
379 pValue = floor(pValue + 0.5); // round
381 if((pValue == 0) && (pFlags & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)))
383 else if(pFlags & SFL_RANK)
384 valstr = (pValue < 256 ? count_ordinal(pValue) : _("N/A"));
385 else if(pFlags & SFL_TIME)
386 valstr = TIME_ENCODED_TOSTRING(pValue);
388 valstr = ftos(pValue);
394 // compressed vector format:
395 // like MD3, just even shorter
396 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
397 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
398 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
399 // length = 2^(length_encoded/8) / 8
400 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
401 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
402 // the special value 0 indicates the zero vector
404 float lengthLogTable[128];
406 float invertLengthLog(float dist)
410 if(dist >= lengthLogTable[127])
412 if(dist <= lengthLogTable[0])
420 m = floor((l + r) / 2);
421 if(lengthLogTable[m] < dist)
427 // now: r is >=, l is <
428 float lerr = (dist - lengthLogTable[l]);
429 float rerr = (lengthLogTable[r] - dist);
435 vector decompressShortVector(int data)
440 float p = (data & 0xF000) / 0x1000;
441 float q = (data & 0x0F80) / 0x80;
442 int len = (data & 0x007F);
444 //print("\ndecompress: p ", ftos(p)); print("q ", ftos(q)); print("len ", ftos(len), "\n");
457 q = .19634954084936207740 * q;
458 p = .19634954084936207740 * p - 1.57079632679489661922;
459 out.x = cos(q) * cos(p);
460 out.y = sin(q) * cos(p);
464 //print("decompressed: ", vtos(out), "\n");
466 return out * lengthLogTable[len];
469 float compressShortVector(vector vec)
475 //print("compress: ", vtos(vec), "\n");
476 ang = vectoangles(vec);
480 if(ang.x < -90 && ang.x > +90)
481 error("BOGUS vectoangles");
482 //print("angles: ", vtos(ang), "\n");
484 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
493 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
494 len = invertLengthLog(vlen(vec));
496 //print("compressed: p ", ftos(p)); print("y ", ftos(y)); print("len ", ftos(len), "\n");
498 return (p * 0x1000) + (y * 0x80) + len;
501 STATIC_INIT(compressShortVector)
504 float f = (2 ** (1/8));
506 for(i = 0; i < 128; ++i)
508 lengthLogTable[i] = l;
512 if(cvar("developer") > 0)
514 LOG_TRACE("Verifying vector compression table...");
515 for(i = 0x0F00; i < 0xFFFF; ++i)
516 if(i != compressShortVector(decompressShortVector(i)))
519 "BROKEN vector compression: %s -> %s -> %s",
521 vtos(decompressShortVector(i)),
522 ftos(compressShortVector(decompressShortVector(i)))
530 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
532 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
533 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
534 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
535 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
536 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
537 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
538 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
539 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
540 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
541 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
542 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
543 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
548 string fixPriorityList(string order, float from, float to, float subtract, float complete)
553 n = tokenize_console(order);
555 for(i = 0; i < n; ++i)
560 if(w >= from && w <= to)
561 neworder = strcat(neworder, ftos(w), " ");
565 if(w >= from && w <= to)
566 neworder = strcat(neworder, ftos(w), " ");
573 n = tokenize_console(neworder);
574 for(w = to; w >= from; --w)
576 int wflags = REGISTRY_GET(Weapons, w).spawnflags;
577 if(wflags & WEP_FLAG_SPECIALATTACK)
579 for(i = 0; i < n; ++i)
580 if(stof(argv(i)) == w)
582 if(i == n) // not found
583 neworder = strcat(neworder, ftos(w), " ");
587 return substring(neworder, 0, strlen(neworder) - 1);
590 string mapPriorityList(string order, string(string) mapfunc)
595 n = tokenize_console(order);
597 for(float i = 0; i < n; ++i)
598 neworder = strcat(neworder, mapfunc(argv(i)), " ");
600 return substring(neworder, 0, strlen(neworder) - 1);
603 string swapInPriorityList(string order, float i, float j)
605 float n = tokenize_console(order);
607 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
610 for(float w = 0; w < n; ++w)
613 s = strcat(s, argv(j), " ");
615 s = strcat(s, argv(i), " ");
617 s = strcat(s, argv(w), " ");
619 return substring(s, 0, strlen(s) - 1);
626 void get_mi_min_max(float mode)
631 if(!strcasecmp(substring(s, 0, 5), "maps/"))
632 s = substring(s, 5, strlen(s) - 5);
633 if(!strcasecmp(substring(s, strlen(s) - 4, 4), ".bsp"))
634 s = substring(s, 0, strlen(s) - 4);
635 strcpy(mi_shortname, s);
647 MapInfo_Get_ByName(mi_shortname, 0, NULL);
648 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
650 mi_min = MapInfo_Map_mins;
651 mi_max = MapInfo_Map_maxs;
659 tracebox('1 0 0' * mi.x,
660 '0 1 0' * mi.y + '0 0 1' * mi.z,
661 '0 1 0' * ma.y + '0 0 1' * ma.z,
665 if(!trace_startsolid)
666 mi_min.x = trace_endpos.x;
668 tracebox('0 1 0' * mi.y,
669 '1 0 0' * mi.x + '0 0 1' * mi.z,
670 '1 0 0' * ma.x + '0 0 1' * ma.z,
674 if(!trace_startsolid)
675 mi_min.y = trace_endpos.y;
677 tracebox('0 0 1' * mi.z,
678 '1 0 0' * mi.x + '0 1 0' * mi.y,
679 '1 0 0' * ma.x + '0 1 0' * ma.y,
683 if(!trace_startsolid)
684 mi_min.z = trace_endpos.z;
686 tracebox('1 0 0' * ma.x,
687 '0 1 0' * mi.y + '0 0 1' * mi.z,
688 '0 1 0' * ma.y + '0 0 1' * ma.z,
692 if(!trace_startsolid)
693 mi_max.x = trace_endpos.x;
695 tracebox('0 1 0' * ma.y,
696 '1 0 0' * mi.x + '0 0 1' * mi.z,
697 '1 0 0' * ma.x + '0 0 1' * ma.z,
701 if(!trace_startsolid)
702 mi_max.y = trace_endpos.y;
704 tracebox('0 0 1' * ma.z,
705 '1 0 0' * mi.x + '0 1 0' * mi.y,
706 '1 0 0' * ma.x + '0 1 0' * ma.y,
710 if(!trace_startsolid)
711 mi_max.z = trace_endpos.z;
716 void get_mi_min_max_texcoords(float mode)
720 get_mi_min_max(mode);
725 // extend mi_picmax to get a square aspect ratio
726 // center the map in that area
727 extend = mi_picmax - mi_picmin;
728 if(extend.y > extend.x)
730 mi_picmin.x -= (extend.y - extend.x) * 0.5;
731 mi_picmax.x += (extend.y - extend.x) * 0.5;
735 mi_picmin.y -= (extend.x - extend.y) * 0.5;
736 mi_picmax.y += (extend.x - extend.y) * 0.5;
739 // add another some percent
740 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
744 // calculate the texcoords
745 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
746 // first the two corners of the origin
747 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
748 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
749 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
750 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
751 // then the other corners
752 mi_pictexcoord1_x = mi_pictexcoord0_x;
753 mi_pictexcoord1_y = mi_pictexcoord2_y;
754 mi_pictexcoord3_x = mi_pictexcoord2_x;
755 mi_pictexcoord3_y = mi_pictexcoord0_y;
759 float cvar_settemp(string tmp_cvar, string tmp_value)
761 float created_saved_value;
763 created_saved_value = 0;
765 if (!(tmp_cvar || tmp_value))
767 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
771 if(!cvar_type(tmp_cvar))
773 LOG_INFOF("Error: cvar %s doesn't exist!", tmp_cvar);
777 IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
779 created_saved_value = -1; // skip creation
780 break; // no need to continue
783 if(created_saved_value != -1)
785 // creating a new entity to keep track of this cvar
786 entity e = new_pure(saved_cvar_value);
787 IL_PUSH(g_saved_cvars, e);
788 e.netname = strzone(tmp_cvar);
789 e.message = strzone(cvar_string(tmp_cvar));
790 created_saved_value = 1;
793 // update the cvar to the value given
794 cvar_set(tmp_cvar, tmp_value);
796 return created_saved_value;
799 int cvar_settemp_restore()
802 // FIXME this new-style loop fails!
804 FOREACH_ENTITY_CLASS("saved_cvar_value", true,
806 if(cvar_type(it.netname))
808 cvar_set(it.netname, it.message);
809 strunzone(it.netname);
810 strunzone(it.message);
815 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", it.netname);
820 while((e = find(e, classname, "saved_cvar_value")))
822 if(cvar_type(e.netname))
824 cvar_set(e.netname, e.message);
829 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", e.netname));
836 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
839 // The following function is SLOW.
840 // For your safety and for the protection of those around you...
841 // DO NOT CALL THIS AT HOME.
843 if(w(theText, theSize) <= maxWidth)
844 return strlen(theText); // yeah!
846 bool colors = (w("^7", theSize) == 0);
848 // binary search for right place to cut string
849 int len, left, right, middle;
851 right = len = strlen(theText);
855 middle = floor((left + right) / 2);
858 vector res = checkColorCode(theText, len, middle, false);
859 ofs = (res.x) ? res.x - res.y : 0;
862 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
867 while(left < right - 1);
872 float textLengthUpToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t w)
875 // The following function is SLOW.
876 // For your safety and for the protection of those around you...
877 // DO NOT CALL THIS AT HOME.
879 if(w(theText) <= maxWidth)
880 return strlen(theText); // yeah!
882 bool colors = (w("^7") == 0);
884 // binary search for right place to cut string
885 int len, left, right, middle;
887 right = len = strlen(theText);
891 middle = floor((left + right) / 2);
894 vector res = checkColorCode(theText, len, middle, true);
895 ofs = (!res.x) ? 0 : res.x - res.y;
898 if(w(substring(theText, 0, middle + ofs)) <= maxWidth)
903 while(left < right - 1);
908 string find_last_color_code(string s)
910 int start = strstrofs(s, "^", 0);
911 if (start == -1) // no caret found
913 int len = strlen(s)-1;
914 for(int i = len; i >= start; --i)
916 if(substring(s, i, 1) != "^")
920 while (i-carets >= start && substring(s, i-carets, 1) == "^")
923 // check if carets aren't all escaped
927 if(IS_DIGIT(substring(s, i+1, 1)))
928 return substring(s, i, 2);
931 if(substring(s, i+1, 1) == "x")
932 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
933 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
934 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
935 return substring(s, i, 5);
937 i -= carets; // this also skips one char before the carets
943 string getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
945 string s = getWrappedLine_remaining;
949 getWrappedLine_remaining = string_null;
950 return s; // the line has no size ANYWAY, nothing would be displayed.
953 int take_until = textLengthUpToWidth(s, w, theFontSize, tw);
954 if(take_until > 0 && take_until < strlen(s))
956 int last_word = take_until - 1;
957 while(last_word > 0 && substring(s, last_word, 1) != " ")
963 take_until = last_word;
967 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
968 if(getWrappedLine_remaining == "")
969 getWrappedLine_remaining = string_null;
970 else if (tw("^7", theFontSize) == 0)
971 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
972 return substring(s, 0, take_until);
976 getWrappedLine_remaining = string_null;
981 string getWrappedLineLen(float w, textLengthUpToLength_lenFunction_t tw)
983 string s = getWrappedLine_remaining;
987 getWrappedLine_remaining = string_null;
988 return s; // the line has no size ANYWAY, nothing would be displayed.
991 int take_until = textLengthUpToLength(s, w, tw);
992 if(take_until > 0 && take_until < strlen(s))
994 int last_word = take_until - 1;
995 while(last_word > 0 && substring(s, last_word, 1) != " ")
1001 take_until = last_word;
1005 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
1006 if(getWrappedLine_remaining == "")
1007 getWrappedLine_remaining = string_null;
1008 else if (tw("^7") == 0)
1009 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
1010 return substring(s, 0, take_until);
1014 getWrappedLine_remaining = string_null;
1019 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1021 if(tw(theText, theFontSize) <= maxWidth)
1024 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1027 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1029 if(tw(theText) <= maxWidth)
1032 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1035 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1037 string subpattern, subpattern2, subpattern3, subpattern4;
1038 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1040 subpattern2 = ",teams,";
1042 subpattern2 = ",noteams,";
1044 subpattern3 = ",teamspawns,";
1046 subpattern3 = ",noteamspawns,";
1047 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1048 subpattern4 = ",race,";
1050 subpattern4 = string_null;
1052 if(substring(pattern, 0, 1) == "-")
1054 pattern = substring(pattern, 1, strlen(pattern) - 1);
1055 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1057 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1059 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1061 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1066 if(substring(pattern, 0, 1) == "+")
1067 pattern = substring(pattern, 1, strlen(pattern) - 1);
1068 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1069 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1070 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1074 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1081 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1085 // make origin and speed relative
1090 // now solve for ret, ret normalized:
1091 // eorg + t * evel == t * ret * spd
1092 // or, rather, solve for t:
1093 // |eorg + t * evel| == t * spd
1094 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1095 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1096 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1097 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1098 // q = (eorg * eorg) / (evel * evel - spd * spd)
1099 if(!solution.z) // no real solution
1102 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1103 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1104 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1105 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1106 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1107 // spd < |evel| * sin angle(evel, eorg)
1110 else if(solution.x > 0)
1112 // both solutions > 0: take the smaller one
1113 // happens if p < 0 and q > 0
1114 ret = normalize(eorg + solution.x * evel);
1116 else if(solution.y > 0)
1118 // one solution > 0: take the larger one
1119 // happens if q < 0 or q == 0 and p < 0
1120 ret = normalize(eorg + solution.y * evel);
1124 // no solution > 0: reject
1125 // happens if p > 0 and q >= 0
1126 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1127 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1132 // "Enemy is moving away from me at more than spd"
1136 // NOTE: we always got a solution if spd > |evel|
1138 if(newton_style == 2)
1139 ret = normalize(ret * spd + myvel);
1144 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1149 if(newton_style == 2)
1151 // true Newtonian projectiles with automatic aim adjustment
1153 // solve: |outspeed * mydir - myvel| = spd
1154 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1155 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1159 // myvel^2 - (mydir * myvel)^2 > spd^2
1160 // velocity without mydir component > spd
1161 // fire at smallest possible spd that works?
1162 // |(mydir * myvel) * myvel - myvel| = spd
1164 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1168 outspeed = solution.y; // the larger one
1171 //outspeed = 0; // slowest possible shot
1172 outspeed = solution.x; // the real part (that is, the average!)
1173 //dprint("impossible shot, adjusting\n");
1176 outspeed = bound(spd * mi, outspeed, spd * ma);
1177 return mydir * outspeed;
1181 return myvel + spd * mydir;
1184 float compressShotOrigin(vector v)
1186 float rx = rint(v.x * 2);
1187 float ry = rint(v.y * 4) + 128;
1188 float rz = rint(v.z * 4) + 128;
1189 if(rx > 255 || rx < 0)
1191 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1192 rx = bound(0, rx, 255);
1194 if(ry > 255 || ry < 0)
1196 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1197 ry = bound(0, ry, 255);
1199 if(rz > 255 || rz < 0)
1201 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1202 rz = bound(0, rz, 255);
1204 return rx * 0x10000 + ry * 0x100 + rz;
1206 vector decompressShotOrigin(int f)
1209 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1210 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1211 v.z = ((f & 0xFF) - 128) / 4;
1216 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1218 // NOTE: we'll always choose the SMALLER value...
1219 float healthdamage, armordamage, armorideal;
1220 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1223 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1224 armordamage = a + (h - 1); // damage we can take if we could use more armor
1225 armorideal = healthdamage * armorblock;
1227 if(armordamage < healthdamage)
1240 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1243 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1245 if (deathtype & HITTYPE_ARMORPIERCE)
1247 v.y = bound(0, damage * armorblock, a); // save
1248 v.x = bound(0, damage - v.y, damage); // take
1254 string getcurrentmod()
1258 m = cvar_string("fs_gamedir");
1259 n = tokenize_console(m);
1266 float matchacl(string acl, string str)
1273 t = car(acl); acl = cdr(acl);
1276 if(substring(t, 0, 1) == "-")
1279 t = substring(t, 1, strlen(t) - 1);
1281 else if(substring(t, 0, 1) == "+")
1282 t = substring(t, 1, strlen(t) - 1);
1284 if(substring(t, -1, 1) == "*")
1286 t = substring(t, 0, strlen(t) - 1);
1287 s = substring(str, 0, strlen(t));
1295 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1302 void write_String_To_File(int fh, string str, bool alsoprint)
1305 if (alsoprint) LOG_HELP(str);
1308 string get_model_datafilename(string m, float sk, string fil)
1313 m = "models/player/*_";
1315 m = strcat(m, ftos(sk));
1318 return strcat(m, ".", fil);
1321 float get_model_parameters(string m, float sk)
1323 get_model_parameters_modelname = string_null;
1324 get_model_parameters_modelskin = -1;
1325 get_model_parameters_name = string_null;
1326 get_model_parameters_species = -1;
1327 get_model_parameters_sex = string_null;
1328 get_model_parameters_weight = -1;
1329 get_model_parameters_age = -1;
1330 get_model_parameters_desc = string_null;
1331 get_model_parameters_bone_upperbody = string_null;
1332 get_model_parameters_bone_weapon = string_null;
1333 for(int i = 0; i < MAX_AIM_BONES; ++i)
1335 get_model_parameters_bone_aim[i] = string_null;
1336 get_model_parameters_bone_aimweight[i] = 0;
1338 get_model_parameters_fixbone = 0;
1339 get_model_parameters_hidden = false;
1342 MUTATOR_CALLHOOK(ClearModelParams);
1348 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1349 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1353 if(substring(m, -4, -1) != ".txt")
1355 if(substring(m, -6, 1) != "_")
1357 sk = stof(substring(m, -5, 1));
1358 m = substring(m, 0, -7);
1361 string fn = get_model_datafilename(m, sk, "txt");
1362 int fh = fopen(fn, FILE_READ);
1366 fn = get_model_datafilename(m, sk, "txt");
1367 fh = fopen(fn, FILE_READ);
1372 get_model_parameters_modelname = m;
1373 get_model_parameters_modelskin = sk;
1375 while((s = fgets(fh)))
1378 break; // next lines will be description
1382 get_model_parameters_name = s;
1386 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1387 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1388 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1389 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1390 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1391 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1392 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1396 if (s == "Male") s = _("Male");
1397 else if (s == "Female") s = _("Female");
1398 else if (s == "Undisclosed") s = _("Undisclosed");
1399 get_model_parameters_sex = s;
1402 get_model_parameters_weight = stof(s);
1404 get_model_parameters_age = stof(s);
1405 if(c == "description")
1406 get_model_parameters_description = s;
1407 if(c == "bone_upperbody")
1408 get_model_parameters_bone_upperbody = s;
1409 if(c == "bone_weapon")
1410 get_model_parameters_bone_weapon = s;
1412 MUTATOR_CALLHOOK(GetModelParams, c, s);
1414 for(int i = 0; i < MAX_AIM_BONES; ++i)
1415 if(c == strcat("bone_aim", ftos(i)))
1417 get_model_parameters_bone_aimweight[i] = stof(car(s));
1418 get_model_parameters_bone_aim[i] = cdr(s);
1421 get_model_parameters_fixbone = stof(s);
1423 get_model_parameters_hidden = stob(s);
1426 while((s = fgets(fh)))
1428 if(get_model_parameters_desc)
1429 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1431 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1439 string translate_key(string key)
1441 if (prvm_language == "en") return key;
1443 if (substring(key, 0, 1) == "<")
1445 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1446 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1451 case "TAB": return _("TAB");
1452 case "ENTER": return _("ENTER");
1453 case "ESCAPE": return _("ESCAPE");
1454 case "SPACE": return _("SPACE");
1456 case "BACKSPACE": return _("BACKSPACE");
1457 case "UPARROW": return _("UPARROW");
1458 case "DOWNARROW": return _("DOWNARROW");
1459 case "LEFTARROW": return _("LEFTARROW");
1460 case "RIGHTARROW": return _("RIGHTARROW");
1462 case "ALT": return _("ALT");
1463 case "CTRL": return _("CTRL");
1464 case "SHIFT": return _("SHIFT");
1466 case "INS": return _("INS");
1467 case "DEL": return _("DEL");
1468 case "PGDN": return _("PGDN");
1469 case "PGUP": return _("PGUP");
1470 case "HOME": return _("HOME");
1471 case "END": return _("END");
1473 case "PAUSE": return _("PAUSE");
1475 case "NUMLOCK": return _("NUMLOCK");
1476 case "CAPSLOCK": return _("CAPSLOCK");
1477 case "SCROLLOCK": return _("SCROLLOCK");
1479 case "SEMICOLON": return _("SEMICOLON");
1480 case "TILDE": return _("TILDE");
1481 case "BACKQUOTE": return _("BACKQUOTE");
1482 case "QUOTE": return _("QUOTE");
1483 case "APOSTROPHE": return _("APOSTROPHE");
1484 case "BACKSLASH": return _("BACKSLASH");
1487 if (substring(key, 0, 1) == "F")
1489 string subkey = substring(key, 1, -1);
1490 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1492 return sprintf(_("F%d"), stof(subkey));
1494 // continue in case there is another key name starting with F
1497 if (substring(key, 0, 3) == "KP_")
1499 string subkey = substring(key, 3, -1);
1500 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1502 return sprintf(_("KP_%d"), stof(subkey));
1507 case "INS": return sprintf(_("KP_%s"), _("INS"));
1508 case "END": return sprintf(_("KP_%s"), _("END"));
1509 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1510 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1511 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1512 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1513 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1514 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1515 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1516 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1517 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1518 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1519 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1520 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1521 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1522 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1523 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1524 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1525 default: return key;
1529 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1531 if (substring(key, 0, 5) == "MOUSE")
1532 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1534 if (key == "MWHEELUP") return _("MWHEELUP");
1535 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1537 if (substring(key, 0,3) == "JOY")
1538 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1540 if (substring(key, 0,3) == "AUX")
1541 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1543 if (substring(key, 0, 4) == "X360_")
1545 string subkey = substring(key, 4, -1);
1548 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1549 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1550 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1551 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1552 case "START": return sprintf(_("X360_%s"), _("START"));
1553 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1554 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1555 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1556 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1557 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1558 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1559 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1560 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1561 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1562 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1563 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1564 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1565 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1566 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1567 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1568 default: return key;
1572 if (substring(key, 0, 4) == "JOY_")
1574 string subkey = substring(key, 4, -1);
1577 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1578 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1579 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1580 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1581 default: return key;
1585 if (substring(key, 0, 8) == "MIDINOTE")
1586 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1591 // x-encoding (encoding as zero length invisible string)
1592 const string XENCODE_2 = "xX";
1593 const string XENCODE_22 = "0123456789abcdefABCDEF";
1594 string xencode(int f)
1597 d = f % 22; f = floor(f / 22);
1598 c = f % 22; f = floor(f / 22);
1599 b = f % 22; f = floor(f / 22);
1600 a = f % 2; // f = floor(f / 2);
1603 substring(XENCODE_2, a, 1),
1604 substring(XENCODE_22, b, 1),
1605 substring(XENCODE_22, c, 1),
1606 substring(XENCODE_22, d, 1)
1609 float xdecode(string s)
1612 if(substring(s, 0, 1) != "^")
1616 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1617 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1618 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1619 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1620 if(a < 0 || b < 0 || c < 0 || d < 0)
1622 return ((a * 22 + b) * 22 + c) * 22 + d;
1626 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1628 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1631 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1634 float shutdown_running;
1639 void CSQC_Shutdown()
1645 if(shutdown_running)
1647 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1651 shutdown_running = 1;
1655 cvar_settemp_restore(); // this must be done LAST, but in any case
1659 .float skeleton_bones_index;
1660 void Skeleton_SetBones(entity e)
1662 // set skeleton_bones to the total number of bones on the model
1663 if(e.skeleton_bones_index == e.modelindex)
1664 return; // same model, nothing to update
1667 skelindex = skel_create(e.modelindex);
1668 e.skeleton_bones = skel_get_numbones(skelindex);
1669 skel_delete(skelindex);
1670 e.skeleton_bones_index = e.modelindex;
1674 string to_execute_next_frame;
1675 void execute_next_frame()
1677 if(to_execute_next_frame)
1679 localcmd("\n", to_execute_next_frame, "\n");
1680 strfree(to_execute_next_frame);
1683 void queue_to_execute_next_frame(string s)
1685 if(to_execute_next_frame)
1687 s = strcat(s, "\n", to_execute_next_frame);
1689 strcpy(to_execute_next_frame, s);
1692 .float FindConnectedComponent_processing;
1693 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1695 entity queue_start, queue_end;
1697 // we build a queue of to-be-processed entities.
1698 // queue_start is the next entity to be checked for neighbors
1699 // queue_end is the last entity added
1701 if(e.FindConnectedComponent_processing)
1702 error("recursion or broken cleanup");
1704 // start with a 1-element queue
1705 queue_start = queue_end = e;
1706 queue_end.(fld) = NULL;
1707 queue_end.FindConnectedComponent_processing = 1;
1709 // for each queued item:
1710 for (; queue_start; queue_start = queue_start.(fld))
1712 // find all neighbors of queue_start
1714 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1716 if(t.FindConnectedComponent_processing)
1718 if(iscon(t, queue_start, pass))
1720 // it is connected? ADD IT. It will look for neighbors soon too.
1721 queue_end.(fld) = t;
1723 queue_end.(fld) = NULL;
1724 queue_end.FindConnectedComponent_processing = 1;
1730 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1731 queue_start.FindConnectedComponent_processing = 0;
1735 vector animfixfps(entity e, vector a, vector b)
1737 // multi-frame anim: keep as-is
1740 float dur = frameduration(e.modelindex, a.x);
1741 if (dur <= 0 && b.y)
1744 dur = frameduration(e.modelindex, a.x);
1754 Notification Announcer_PickNumber(int type, int num)
1763 case 10: return ANNCE_NUM_GAMESTART_10;
1764 case 9: return ANNCE_NUM_GAMESTART_9;
1765 case 8: return ANNCE_NUM_GAMESTART_8;
1766 case 7: return ANNCE_NUM_GAMESTART_7;
1767 case 6: return ANNCE_NUM_GAMESTART_6;
1768 case 5: return ANNCE_NUM_GAMESTART_5;
1769 case 4: return ANNCE_NUM_GAMESTART_4;
1770 case 3: return ANNCE_NUM_GAMESTART_3;
1771 case 2: return ANNCE_NUM_GAMESTART_2;
1772 case 1: return ANNCE_NUM_GAMESTART_1;
1780 case 10: return ANNCE_NUM_KILL_10;
1781 case 9: return ANNCE_NUM_KILL_9;
1782 case 8: return ANNCE_NUM_KILL_8;
1783 case 7: return ANNCE_NUM_KILL_7;
1784 case 6: return ANNCE_NUM_KILL_6;
1785 case 5: return ANNCE_NUM_KILL_5;
1786 case 4: return ANNCE_NUM_KILL_4;
1787 case 3: return ANNCE_NUM_KILL_3;
1788 case 2: return ANNCE_NUM_KILL_2;
1789 case 1: return ANNCE_NUM_KILL_1;
1797 case 10: return ANNCE_NUM_RESPAWN_10;
1798 case 9: return ANNCE_NUM_RESPAWN_9;
1799 case 8: return ANNCE_NUM_RESPAWN_8;
1800 case 7: return ANNCE_NUM_RESPAWN_7;
1801 case 6: return ANNCE_NUM_RESPAWN_6;
1802 case 5: return ANNCE_NUM_RESPAWN_5;
1803 case 4: return ANNCE_NUM_RESPAWN_4;
1804 case 3: return ANNCE_NUM_RESPAWN_3;
1805 case 2: return ANNCE_NUM_RESPAWN_2;
1806 case 1: return ANNCE_NUM_RESPAWN_1;
1810 case CNT_ROUNDSTART:
1814 case 10: return ANNCE_NUM_ROUNDSTART_10;
1815 case 9: return ANNCE_NUM_ROUNDSTART_9;
1816 case 8: return ANNCE_NUM_ROUNDSTART_8;
1817 case 7: return ANNCE_NUM_ROUNDSTART_7;
1818 case 6: return ANNCE_NUM_ROUNDSTART_6;
1819 case 5: return ANNCE_NUM_ROUNDSTART_5;
1820 case 4: return ANNCE_NUM_ROUNDSTART_4;
1821 case 3: return ANNCE_NUM_ROUNDSTART_3;
1822 case 2: return ANNCE_NUM_ROUNDSTART_2;
1823 case 1: return ANNCE_NUM_ROUNDSTART_1;
1832 case 10: return ANNCE_NUM_10;
1833 case 9: return ANNCE_NUM_9;
1834 case 8: return ANNCE_NUM_8;
1835 case 7: return ANNCE_NUM_7;
1836 case 6: return ANNCE_NUM_6;
1837 case 5: return ANNCE_NUM_5;
1838 case 4: return ANNCE_NUM_4;
1839 case 3: return ANNCE_NUM_3;
1840 case 2: return ANNCE_NUM_2;
1841 case 1: return ANNCE_NUM_1;
1850 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1852 switch(nativecontents)
1857 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1859 return DPCONTENTS_WATER;
1861 return DPCONTENTS_SLIME;
1863 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1865 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1870 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1872 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1873 return CONTENT_SOLID;
1874 if(supercontents & DPCONTENTS_SKY)
1876 if(supercontents & DPCONTENTS_LAVA)
1877 return CONTENT_LAVA;
1878 if(supercontents & DPCONTENTS_SLIME)
1879 return CONTENT_SLIME;
1880 if(supercontents & DPCONTENTS_WATER)
1881 return CONTENT_WATER;
1882 return CONTENT_EMPTY;
1887 void attach_sameorigin(entity e, entity to, string tag)
1889 vector org, t_forward, t_left, t_up, e_forward, e_up;
1892 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1893 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1894 t_forward = v_forward * tagscale;
1895 t_left = v_right * -tagscale;
1896 t_up = v_up * tagscale;
1898 e.origin_x = org * t_forward;
1899 e.origin_y = org * t_left;
1900 e.origin_z = org * t_up;
1902 // current forward and up directions
1903 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1904 e.angles = AnglesTransform_FromVAngles(e.angles);
1906 e.angles = AnglesTransform_FromAngles(e.angles);
1907 fixedmakevectors(e.angles);
1909 // untransform forward, up!
1910 e_forward.x = v_forward * t_forward;
1911 e_forward.y = v_forward * t_left;
1912 e_forward.z = v_forward * t_up;
1913 e_up.x = v_up * t_forward;
1914 e_up.y = v_up * t_left;
1915 e_up.z = v_up * t_up;
1917 e.angles = fixedvectoangles2(e_forward, e_up);
1918 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1919 e.angles = AnglesTransform_ToVAngles(e.angles);
1921 e.angles = AnglesTransform_ToAngles(e.angles);
1923 setattachment(e, to, tag);
1924 setorigin(e, e.origin);
1927 void detach_sameorigin(entity e)
1930 org = gettaginfo(e, 0);
1931 e.angles = fixedvectoangles2(v_forward, v_up);
1932 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1933 e.angles = AnglesTransform_ToVAngles(e.angles);
1935 e.angles = AnglesTransform_ToAngles(e.angles);
1937 setattachment(e, NULL, "");
1938 setorigin(e, e.origin);
1941 void follow_sameorigin(entity e, entity to)
1943 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1944 e.aiment = to; // make the hole follow bmodel
1945 e.punchangle = to.angles; // the original angles of bmodel
1946 e.view_ofs = e.origin - to.origin; // relative origin
1947 e.v_angle = e.angles - to.angles; // relative angles
1951 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1952 void unfollow_sameorigin(entity e)
1954 set_movetype(e, MOVETYPE_NONE);
1958 .string aiment_classname;
1959 .float aiment_deadflag;
1960 void SetMovetypeFollow(entity ent, entity e)
1962 // FIXME this may not be warpzone aware
1963 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1964 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.
1965 ent.aiment = e; // make the hole follow bmodel
1966 ent.punchangle = e.angles; // the original angles of bmodel
1967 ent.view_ofs = ent.origin - e.origin; // relative origin
1968 ent.v_angle = ent.angles - e.angles; // relative angles
1969 ent.aiment_classname = strzone(e.classname);
1970 ent.aiment_deadflag = e.deadflag;
1972 if(IS_PLAYER(ent.aiment))
1974 entity pl = ent.aiment;
1975 ent.view_ofs.x = bound(pl.mins.x + 4, ent.view_ofs.x, pl.maxs.x - 4);
1976 ent.view_ofs.y = bound(pl.mins.y + 4, ent.view_ofs.y, pl.maxs.y - 4);
1977 ent.view_ofs.z = bound(pl.mins.z + 4, ent.view_ofs.z, pl.maxs.z - 4);
1981 void UnsetMovetypeFollow(entity ent)
1983 set_movetype(ent, MOVETYPE_FLY);
1984 PROJECTILE_MAKETRIGGER(ent);
1985 if (ent.aiment_classname)
1986 strunzone(ent.classname);
1987 // FIXME: engine bug?
1988 // resetting aiment the engine will set orb's origin close to world's origin
1989 //ent.aiment = NULL;
1992 int LostMovetypeFollow(entity ent)
1995 if(ent.move_movetype != MOVETYPE_FOLLOW)
1999 // FIXME: engine bug?
2000 // when aiment disconnects the engine will set orb's origin close to world's origin
2003 if(ent.aiment.classname != ent.aiment_classname || ent.aiment.deadflag != ent.aiment_deadflag)
2010 // decolorizes and team colors the player name when needed
2011 string playername(string thename, int teamid, bool team_colorize)
2014 bool do_colorize = (teamplay && team_colorize);
2016 if(do_colorize && !intermission_running)
2021 string t = Team_ColorCode(teamid);
2022 return strcat(t, strdecolorize(thename));
2028 float trace_hits_box_a0, trace_hits_box_a1;
2030 float trace_hits_box_1d(float end, float thmi, float thma)
2034 // just check if x is in range
2042 // do the trace with respect to x
2043 // 0 -> end has to stay in thmi -> thma
2044 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2045 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2046 if (trace_hits_box_a0 > trace_hits_box_a1)
2052 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2057 // now it is a trace from 0 to end
2059 trace_hits_box_a0 = 0;
2060 trace_hits_box_a1 = 1;
2062 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2064 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2066 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2072 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2074 return trace_hits_box(start, end, thmi - ma, thma - mi);
2079 float cvar_or(string cv, float v)
2081 string s = cvar_string(cv);
2088 // NOTE base is the central value
2089 // freq: circle frequency, = 2*pi*frequency in hertz
2091 // -1 start from the lower value
2092 // 0 start from the base value
2093 // 1 start from the higher value
2095 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2098 // RMS = sqrt(base^2 + 0.5 * range^2)
2100 // base = sqrt(RMS^2 - 0.5 * range^2)
2103 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2107 float blink(float base, float range, float freq)
2109 return blink_synced(base, range, freq, 0, 0);