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;
1187 float compressShotOrigin(vector v)
1189 float rx = rint(v.x * 2);
1190 float ry = rint(v.y * 4) + 128;
1191 float rz = rint(v.z * 4) + 128;
1192 if(rx > 255 || rx < 0)
1194 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1195 rx = bound(0, rx, 255);
1197 if(ry > 255 || ry < 0)
1199 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1200 ry = bound(0, ry, 255);
1202 if(rz > 255 || rz < 0)
1204 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1205 rz = bound(0, rz, 255);
1207 return rx * 0x10000 + ry * 0x100 + rz;
1209 vector decompressShotOrigin(int f)
1212 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1213 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1214 v.z = ((f & 0xFF) - 128) / 4;
1219 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1221 // NOTE: we'll always choose the SMALLER value...
1222 float healthdamage, armordamage, armorideal;
1223 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1226 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1227 armordamage = a + (h - 1); // damage we can take if we could use more armor
1228 armorideal = healthdamage * armorblock;
1230 if(armordamage < healthdamage)
1243 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1246 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1248 if (deathtype & HITTYPE_ARMORPIERCE)
1250 v.y = bound(0, damage * armorblock, a); // save
1251 v.x = bound(0, damage - v.y, damage); // take
1257 string getcurrentmod()
1261 m = cvar_string("fs_gamedir");
1262 n = tokenize_console(m);
1269 float matchacl(string acl, string str)
1276 t = car(acl); acl = cdr(acl);
1279 if(substring(t, 0, 1) == "-")
1282 t = substring(t, 1, strlen(t) - 1);
1284 else if(substring(t, 0, 1) == "+")
1285 t = substring(t, 1, strlen(t) - 1);
1287 if(substring(t, -1, 1) == "*")
1289 t = substring(t, 0, strlen(t) - 1);
1290 s = substring(str, 0, strlen(t));
1298 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1305 void write_String_To_File(int fh, string str, bool alsoprint)
1308 if (alsoprint) LOG_HELP(str);
1311 string get_model_datafilename(string m, float sk, string fil)
1316 m = "models/player/*_";
1318 m = strcat(m, ftos(sk));
1321 return strcat(m, ".", fil);
1324 float get_model_parameters(string m, float sk)
1326 get_model_parameters_modelname = string_null;
1327 get_model_parameters_modelskin = -1;
1328 get_model_parameters_name = string_null;
1329 get_model_parameters_species = -1;
1330 get_model_parameters_sex = string_null;
1331 get_model_parameters_weight = -1;
1332 get_model_parameters_age = -1;
1333 get_model_parameters_desc = string_null;
1334 get_model_parameters_bone_upperbody = string_null;
1335 get_model_parameters_bone_weapon = string_null;
1336 for(int i = 0; i < MAX_AIM_BONES; ++i)
1338 get_model_parameters_bone_aim[i] = string_null;
1339 get_model_parameters_bone_aimweight[i] = 0;
1341 get_model_parameters_fixbone = 0;
1342 get_model_parameters_hidden = false;
1345 MUTATOR_CALLHOOK(ClearModelParams);
1351 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1352 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1356 if(substring(m, -4, -1) != ".txt")
1358 if(substring(m, -6, 1) != "_")
1360 sk = stof(substring(m, -5, 1));
1361 m = substring(m, 0, -7);
1364 string fn = get_model_datafilename(m, sk, "txt");
1365 int fh = fopen(fn, FILE_READ);
1369 fn = get_model_datafilename(m, sk, "txt");
1370 fh = fopen(fn, FILE_READ);
1375 get_model_parameters_modelname = m;
1376 get_model_parameters_modelskin = sk;
1378 while((s = fgets(fh)))
1381 break; // next lines will be description
1385 get_model_parameters_name = s;
1389 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1390 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1391 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1392 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1393 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1394 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1395 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1399 if (s == "Male") s = _("Male");
1400 else if (s == "Female") s = _("Female");
1401 else if (s == "Undisclosed") s = _("Undisclosed");
1402 get_model_parameters_sex = s;
1405 get_model_parameters_weight = stof(s);
1407 get_model_parameters_age = stof(s);
1408 if(c == "description")
1409 get_model_parameters_description = s;
1410 if(c == "bone_upperbody")
1411 get_model_parameters_bone_upperbody = s;
1412 if(c == "bone_weapon")
1413 get_model_parameters_bone_weapon = s;
1415 MUTATOR_CALLHOOK(GetModelParams, c, s);
1417 for(int i = 0; i < MAX_AIM_BONES; ++i)
1418 if(c == strcat("bone_aim", ftos(i)))
1420 get_model_parameters_bone_aimweight[i] = stof(car(s));
1421 get_model_parameters_bone_aim[i] = cdr(s);
1424 get_model_parameters_fixbone = stof(s);
1426 get_model_parameters_hidden = stob(s);
1429 while((s = fgets(fh)))
1431 if(get_model_parameters_desc)
1432 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1434 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1442 string translate_key(string key)
1444 if (prvm_language == "en") return key;
1446 if (substring(key, 0, 1) == "<")
1448 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1449 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1454 case "TAB": return _("TAB");
1455 case "ENTER": return _("ENTER");
1456 case "ESCAPE": return _("ESCAPE");
1457 case "SPACE": return _("SPACE");
1459 case "BACKSPACE": return _("BACKSPACE");
1460 case "UPARROW": return _("UPARROW");
1461 case "DOWNARROW": return _("DOWNARROW");
1462 case "LEFTARROW": return _("LEFTARROW");
1463 case "RIGHTARROW": return _("RIGHTARROW");
1465 case "ALT": return _("ALT");
1466 case "CTRL": return _("CTRL");
1467 case "SHIFT": return _("SHIFT");
1469 case "INS": return _("INS");
1470 case "DEL": return _("DEL");
1471 case "PGDN": return _("PGDN");
1472 case "PGUP": return _("PGUP");
1473 case "HOME": return _("HOME");
1474 case "END": return _("END");
1476 case "PAUSE": return _("PAUSE");
1478 case "NUMLOCK": return _("NUMLOCK");
1479 case "CAPSLOCK": return _("CAPSLOCK");
1480 case "SCROLLOCK": return _("SCROLLOCK");
1482 case "SEMICOLON": return _("SEMICOLON");
1483 case "TILDE": return _("TILDE");
1484 case "BACKQUOTE": return _("BACKQUOTE");
1485 case "QUOTE": return _("QUOTE");
1486 case "APOSTROPHE": return _("APOSTROPHE");
1487 case "BACKSLASH": return _("BACKSLASH");
1490 if (substring(key, 0, 1) == "F")
1492 string subkey = substring(key, 1, -1);
1493 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1495 return sprintf(_("F%d"), stof(subkey));
1497 // continue in case there is another key name starting with F
1500 if (substring(key, 0, 3) == "KP_")
1502 string subkey = substring(key, 3, -1);
1503 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1505 return sprintf(_("KP_%d"), stof(subkey));
1510 case "INS": return sprintf(_("KP_%s"), _("INS"));
1511 case "END": return sprintf(_("KP_%s"), _("END"));
1512 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1513 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1514 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1515 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1516 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1517 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1518 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1519 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1520 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1521 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1522 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1523 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1524 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1525 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1526 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1527 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1528 default: return key;
1532 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1534 if (substring(key, 0, 5) == "MOUSE")
1535 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1537 if (key == "MWHEELUP") return _("MWHEELUP");
1538 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1540 if (substring(key, 0,3) == "JOY")
1541 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1543 if (substring(key, 0,3) == "AUX")
1544 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1546 if (substring(key, 0, 4) == "X360_")
1548 string subkey = substring(key, 4, -1);
1551 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1552 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1553 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1554 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1555 case "START": return sprintf(_("X360_%s"), _("START"));
1556 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1557 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1558 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1559 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1560 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1561 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1562 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1563 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1564 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1565 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1566 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1567 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1568 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1569 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1570 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1571 default: return key;
1575 if (substring(key, 0, 4) == "JOY_")
1577 string subkey = substring(key, 4, -1);
1580 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1581 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1582 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1583 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1584 default: return key;
1588 if (substring(key, 0, 8) == "MIDINOTE")
1589 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1594 // x-encoding (encoding as zero length invisible string)
1595 const string XENCODE_2 = "xX";
1596 const string XENCODE_22 = "0123456789abcdefABCDEF";
1597 string xencode(int f)
1600 d = f % 22; f = floor(f / 22);
1601 c = f % 22; f = floor(f / 22);
1602 b = f % 22; f = floor(f / 22);
1603 a = f % 2; // f = floor(f / 2);
1606 substring(XENCODE_2, a, 1),
1607 substring(XENCODE_22, b, 1),
1608 substring(XENCODE_22, c, 1),
1609 substring(XENCODE_22, d, 1)
1612 float xdecode(string s)
1615 if(substring(s, 0, 1) != "^")
1619 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1620 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1621 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1622 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1623 if(a < 0 || b < 0 || c < 0 || d < 0)
1625 return ((a * 22 + b) * 22 + c) * 22 + d;
1629 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1631 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1634 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1637 float shutdown_running;
1642 void CSQC_Shutdown()
1648 if(shutdown_running)
1650 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1654 shutdown_running = 1;
1658 cvar_settemp_restore(); // this must be done LAST, but in any case
1662 .float skeleton_bones_index;
1663 void Skeleton_SetBones(entity e)
1665 // set skeleton_bones to the total number of bones on the model
1666 if(e.skeleton_bones_index == e.modelindex)
1667 return; // same model, nothing to update
1670 skelindex = skel_create(e.modelindex);
1671 e.skeleton_bones = skel_get_numbones(skelindex);
1672 skel_delete(skelindex);
1673 e.skeleton_bones_index = e.modelindex;
1677 string to_execute_next_frame;
1678 void execute_next_frame()
1680 if(to_execute_next_frame)
1682 localcmd("\n", to_execute_next_frame, "\n");
1683 strfree(to_execute_next_frame);
1686 void queue_to_execute_next_frame(string s)
1688 if(to_execute_next_frame)
1690 s = strcat(s, "\n", to_execute_next_frame);
1692 strcpy(to_execute_next_frame, s);
1695 .float FindConnectedComponent_processing;
1696 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1698 entity queue_start, queue_end;
1700 // we build a queue of to-be-processed entities.
1701 // queue_start is the next entity to be checked for neighbors
1702 // queue_end is the last entity added
1704 if(e.FindConnectedComponent_processing)
1705 error("recursion or broken cleanup");
1707 // start with a 1-element queue
1708 queue_start = queue_end = e;
1709 queue_end.(fld) = NULL;
1710 queue_end.FindConnectedComponent_processing = 1;
1712 // for each queued item:
1713 for (; queue_start; queue_start = queue_start.(fld))
1715 // find all neighbors of queue_start
1717 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1719 if(t.FindConnectedComponent_processing)
1721 if(iscon(t, queue_start, pass))
1723 // it is connected? ADD IT. It will look for neighbors soon too.
1724 queue_end.(fld) = t;
1726 queue_end.(fld) = NULL;
1727 queue_end.FindConnectedComponent_processing = 1;
1733 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1734 queue_start.FindConnectedComponent_processing = 0;
1738 vector animfixfps(entity e, vector a, vector b)
1740 // multi-frame anim: keep as-is
1743 float dur = frameduration(e.modelindex, a.x);
1744 if (dur <= 0 && b.y)
1747 dur = frameduration(e.modelindex, a.x);
1757 Notification Announcer_PickNumber(int type, int num)
1766 case 10: return ANNCE_NUM_GAMESTART_10;
1767 case 9: return ANNCE_NUM_GAMESTART_9;
1768 case 8: return ANNCE_NUM_GAMESTART_8;
1769 case 7: return ANNCE_NUM_GAMESTART_7;
1770 case 6: return ANNCE_NUM_GAMESTART_6;
1771 case 5: return ANNCE_NUM_GAMESTART_5;
1772 case 4: return ANNCE_NUM_GAMESTART_4;
1773 case 3: return ANNCE_NUM_GAMESTART_3;
1774 case 2: return ANNCE_NUM_GAMESTART_2;
1775 case 1: return ANNCE_NUM_GAMESTART_1;
1783 case 10: return ANNCE_NUM_KILL_10;
1784 case 9: return ANNCE_NUM_KILL_9;
1785 case 8: return ANNCE_NUM_KILL_8;
1786 case 7: return ANNCE_NUM_KILL_7;
1787 case 6: return ANNCE_NUM_KILL_6;
1788 case 5: return ANNCE_NUM_KILL_5;
1789 case 4: return ANNCE_NUM_KILL_4;
1790 case 3: return ANNCE_NUM_KILL_3;
1791 case 2: return ANNCE_NUM_KILL_2;
1792 case 1: return ANNCE_NUM_KILL_1;
1800 case 10: return ANNCE_NUM_RESPAWN_10;
1801 case 9: return ANNCE_NUM_RESPAWN_9;
1802 case 8: return ANNCE_NUM_RESPAWN_8;
1803 case 7: return ANNCE_NUM_RESPAWN_7;
1804 case 6: return ANNCE_NUM_RESPAWN_6;
1805 case 5: return ANNCE_NUM_RESPAWN_5;
1806 case 4: return ANNCE_NUM_RESPAWN_4;
1807 case 3: return ANNCE_NUM_RESPAWN_3;
1808 case 2: return ANNCE_NUM_RESPAWN_2;
1809 case 1: return ANNCE_NUM_RESPAWN_1;
1813 case CNT_ROUNDSTART:
1817 case 10: return ANNCE_NUM_ROUNDSTART_10;
1818 case 9: return ANNCE_NUM_ROUNDSTART_9;
1819 case 8: return ANNCE_NUM_ROUNDSTART_8;
1820 case 7: return ANNCE_NUM_ROUNDSTART_7;
1821 case 6: return ANNCE_NUM_ROUNDSTART_6;
1822 case 5: return ANNCE_NUM_ROUNDSTART_5;
1823 case 4: return ANNCE_NUM_ROUNDSTART_4;
1824 case 3: return ANNCE_NUM_ROUNDSTART_3;
1825 case 2: return ANNCE_NUM_ROUNDSTART_2;
1826 case 1: return ANNCE_NUM_ROUNDSTART_1;
1835 case 10: return ANNCE_NUM_10;
1836 case 9: return ANNCE_NUM_9;
1837 case 8: return ANNCE_NUM_8;
1838 case 7: return ANNCE_NUM_7;
1839 case 6: return ANNCE_NUM_6;
1840 case 5: return ANNCE_NUM_5;
1841 case 4: return ANNCE_NUM_4;
1842 case 3: return ANNCE_NUM_3;
1843 case 2: return ANNCE_NUM_2;
1844 case 1: return ANNCE_NUM_1;
1853 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1855 switch(nativecontents)
1860 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1862 return DPCONTENTS_WATER;
1864 return DPCONTENTS_SLIME;
1866 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1868 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1873 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1875 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1876 return CONTENT_SOLID;
1877 if(supercontents & DPCONTENTS_SKY)
1879 if(supercontents & DPCONTENTS_LAVA)
1880 return CONTENT_LAVA;
1881 if(supercontents & DPCONTENTS_SLIME)
1882 return CONTENT_SLIME;
1883 if(supercontents & DPCONTENTS_WATER)
1884 return CONTENT_WATER;
1885 return CONTENT_EMPTY;
1890 void attach_sameorigin(entity e, entity to, string tag)
1892 vector org, t_forward, t_left, t_up, e_forward, e_up;
1895 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1896 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1897 t_forward = v_forward * tagscale;
1898 t_left = v_right * -tagscale;
1899 t_up = v_up * tagscale;
1901 e.origin_x = org * t_forward;
1902 e.origin_y = org * t_left;
1903 e.origin_z = org * t_up;
1905 // current forward and up directions
1906 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1907 e.angles = AnglesTransform_FromVAngles(e.angles);
1909 e.angles = AnglesTransform_FromAngles(e.angles);
1910 fixedmakevectors(e.angles);
1912 // untransform forward, up!
1913 e_forward.x = v_forward * t_forward;
1914 e_forward.y = v_forward * t_left;
1915 e_forward.z = v_forward * t_up;
1916 e_up.x = v_up * t_forward;
1917 e_up.y = v_up * t_left;
1918 e_up.z = v_up * t_up;
1920 e.angles = fixedvectoangles2(e_forward, e_up);
1921 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1922 e.angles = AnglesTransform_ToVAngles(e.angles);
1924 e.angles = AnglesTransform_ToAngles(e.angles);
1926 setattachment(e, to, tag);
1927 setorigin(e, e.origin);
1930 void detach_sameorigin(entity e)
1933 org = gettaginfo(e, 0);
1934 e.angles = fixedvectoangles2(v_forward, v_up);
1935 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1936 e.angles = AnglesTransform_ToVAngles(e.angles);
1938 e.angles = AnglesTransform_ToAngles(e.angles);
1940 setattachment(e, NULL, "");
1941 setorigin(e, e.origin);
1944 void follow_sameorigin(entity e, entity to)
1946 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1947 e.aiment = to; // make the hole follow bmodel
1948 e.punchangle = to.angles; // the original angles of bmodel
1949 e.view_ofs = e.origin - to.origin; // relative origin
1950 e.v_angle = e.angles - to.angles; // relative angles
1954 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1955 void unfollow_sameorigin(entity e)
1957 set_movetype(e, MOVETYPE_NONE);
1961 .string aiment_classname;
1962 .float aiment_deadflag;
1963 void SetMovetypeFollow(entity ent, entity e)
1965 set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1966 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.
1967 ent.aiment = e; // make the hole follow bmodel
1968 ent.punchangle = e.angles; // the original angles of bmodel
1969 ent.view_ofs = ent.origin - e.origin; // relative origin
1970 ent.v_angle = ent.angles - e.angles; // relative angles
1971 ent.aiment_classname = e.classname;
1972 ent.aiment_deadflag = e.deadflag;
1974 if(IS_PLAYER(ent.aiment))
1976 entity pl = ent.aiment;
1977 ent.view_ofs.x = bound(pl.mins.x + 4, ent.view_ofs.x, pl.maxs.x - 4);
1978 ent.view_ofs.y = bound(pl.mins.y + 4, ent.view_ofs.y, pl.maxs.y - 4);
1979 ent.view_ofs.z = bound(pl.mins.z + 4, ent.view_ofs.z, pl.maxs.z - 4);
1983 void UnsetMovetypeFollow(entity ent)
1985 set_movetype(ent, MOVETYPE_FLY);
1986 PROJECTILE_MAKETRIGGER(ent);
1987 ent.aiment_classname = string_null;
1988 // FIXME: engine bug?
1989 // resetting aiment the engine will set orb's origin close to world's origin
1990 //ent.aiment = NULL;
1993 int LostMovetypeFollow(entity ent)
1996 if(ent.move_movetype != MOVETYPE_FOLLOW)
2000 // FIXME: engine bug?
2001 // when aiment disconnects the engine will set orb's origin close to world's origin
2004 if(ent.aiment.classname != ent.aiment_classname || ent.aiment.deadflag != ent.aiment_deadflag)
2011 // decolorizes and team colors the player name when needed
2012 string playername(string thename, int teamid, bool team_colorize)
2015 bool do_colorize = (teamplay && team_colorize);
2017 if(do_colorize && !intermission_running)
2022 string t = Team_ColorCode(teamid);
2023 return strcat(t, strdecolorize(thename));
2029 float trace_hits_box_a0, trace_hits_box_a1;
2031 float trace_hits_box_1d(float end, float thmi, float thma)
2035 // just check if x is in range
2043 // do the trace with respect to x
2044 // 0 -> end has to stay in thmi -> thma
2045 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2046 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2047 if (trace_hits_box_a0 > trace_hits_box_a1)
2053 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2058 // now it is a trace from 0 to end
2060 trace_hits_box_a0 = 0;
2061 trace_hits_box_a1 = 1;
2063 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2065 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2067 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2073 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2075 return trace_hits_box(start, end, thmi - ma, thma - mi);
2080 float cvar_or(string cv, float v)
2082 string s = cvar_string(cv);
2089 // NOTE base is the central value
2090 // freq: circle frequency, = 2*pi*frequency in hertz
2092 // -1 start from the lower value
2093 // 0 start from the base value
2094 // 1 start from the higher value
2096 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2099 // RMS = sqrt(base^2 + 0.5 * range^2)
2101 // base = sqrt(RMS^2 - 0.5 * range^2)
2104 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2108 float blink(float base, float range, float freq)
2110 return blink_synced(base, range, freq, 0, 0);