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)
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);
389 valstr = ftos(pValue);
395 // compressed vector format:
396 // like MD3, just even shorter
397 // 4 bit pitch (16 angles), 0 is -90, 8 is 0, 16 would be 90
398 // 5 bit yaw (32 angles), 0=0, 8=90, 16=180, 24=270
399 // 7 bit length (logarithmic encoding), 1/8 .. about 7844
400 // length = 2^(length_encoded/8) / 8
401 // if pitch is 90, yaw does nothing and therefore indicates the sign (yaw is then either 11111 or 11110); 11111 is pointing DOWN
402 // thus, valid values are from 0000.11110.0000000 to 1111.11111.1111111
403 // the special value 0 indicates the zero vector
405 float lengthLogTable[128];
407 float invertLengthLog(float dist)
411 if(dist >= lengthLogTable[127])
413 if(dist <= lengthLogTable[0])
421 m = floor((l + r) / 2);
422 if(lengthLogTable[m] < dist)
428 // now: r is >=, l is <
429 float lerr = (dist - lengthLogTable[l]);
430 float rerr = (lengthLogTable[r] - dist);
436 vector decompressShortVector(int data)
441 float p = (data & 0xF000) / 0x1000;
442 float q = (data & 0x0F80) / 0x80;
443 int len = (data & 0x007F);
445 //print("\ndecompress: p:", ftos(p)); print(" q:", ftos(q)); print(" len:", ftos(len), "\n");
458 q = .19634954084936207740 * q;
459 p = .19634954084936207740 * p - 1.57079632679489661922;
460 out.x = cos(q) * cos(p);
461 out.y = sin(q) * cos(p);
465 //print("decompressed: ", vtos(out), "\n");
467 return out * lengthLogTable[len];
470 float compressShortVector(vector vec)
476 //print("compress: ", vtos(vec), "\n");
477 ang = vectoangles(vec);
481 if(ang.x < -90 && ang.x > +90)
482 error("BOGUS vectoangles");
483 //print("angles: ", vtos(ang), "\n");
485 p = floor(0.5 + (ang.x + 90) * 16 / 180) & 15; // -90..90 to 0..14
494 y = floor(0.5 + ang.y * 32 / 360) & 31; // 0..360 to 0..32
495 len = invertLengthLog(vlen(vec));
497 //print("compressed: p:", ftos(p)); print(" y:", ftos(y)); print(" len:", ftos(len), "\n");
499 return (p * 0x1000) + (y * 0x80) + len;
502 STATIC_INIT(compressShortVector)
505 float f = (2 ** (1/8));
507 for(i = 0; i < 128; ++i)
509 lengthLogTable[i] = l;
513 if(cvar("developer") > 0)
515 LOG_TRACE("Verifying vector compression table...");
516 for(i = 0x0F00; i < 0xFFFF; ++i)
517 if(i != compressShortVector(decompressShortVector(i)))
520 "BROKEN vector compression: %s -> %s -> %s",
522 vtos(decompressShortVector(i)),
523 ftos(compressShortVector(decompressShortVector(i)))
531 float CheckWireframeBox(entity forent, vector v0, vector dvx, vector dvy, vector dvz)
533 traceline(v0, v0 + dvx, true, forent); if(trace_fraction < 1) return 0;
534 traceline(v0, v0 + dvy, true, forent); if(trace_fraction < 1) return 0;
535 traceline(v0, v0 + dvz, true, forent); if(trace_fraction < 1) return 0;
536 traceline(v0 + dvx, v0 + dvx + dvy, true, forent); if(trace_fraction < 1) return 0;
537 traceline(v0 + dvx, v0 + dvx + dvz, true, forent); if(trace_fraction < 1) return 0;
538 traceline(v0 + dvy, v0 + dvy + dvx, true, forent); if(trace_fraction < 1) return 0;
539 traceline(v0 + dvy, v0 + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
540 traceline(v0 + dvz, v0 + dvz + dvx, true, forent); if(trace_fraction < 1) return 0;
541 traceline(v0 + dvz, v0 + dvz + dvy, true, forent); if(trace_fraction < 1) return 0;
542 traceline(v0 + dvx + dvy, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
543 traceline(v0 + dvx + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
544 traceline(v0 + dvy + dvz, v0 + dvx + dvy + dvz, true, forent); if(trace_fraction < 1) return 0;
549 string fixPriorityList(string order, float from, float to, float subtract, float complete)
554 n = tokenize_console(order);
556 for(i = 0; i < n; ++i)
561 if(w >= from && w <= to)
562 neworder = strcat(neworder, ftos(w), " ");
566 if(w >= from && w <= to)
567 neworder = strcat(neworder, ftos(w), " ");
574 n = tokenize_console(neworder);
575 for(w = to; w >= from; --w)
577 int wflags = REGISTRY_GET(Weapons, w).spawnflags;
578 if(wflags & WEP_FLAG_SPECIALATTACK)
580 for(i = 0; i < n; ++i)
581 if(stof(argv(i)) == w)
583 if(i == n) // not found
584 neworder = strcat(neworder, ftos(w), " ");
588 return substring(neworder, 0, strlen(neworder) - 1);
591 string mapPriorityList(string order, string(string) mapfunc)
596 n = tokenize_console(order);
598 for(float i = 0; i < n; ++i)
599 neworder = strcat(neworder, mapfunc(argv(i)), " ");
601 return substring(neworder, 0, strlen(neworder) - 1);
604 string swapInPriorityList(string order, float i, float j)
606 float n = tokenize_console(order);
608 if(i >= 0 && i < n && j >= 0 && j < n && i != j)
611 for(float w = 0; w < n; ++w)
614 s = strcat(s, argv(j), " ");
616 s = strcat(s, argv(i), " ");
618 s = strcat(s, argv(w), " ");
620 return substring(s, 0, strlen(s) - 1);
627 void get_mi_min_max(float mode)
632 if(!strcasecmp(substring(s, 0, 5), "maps/"))
633 s = substring(s, 5, strlen(s) - 5);
634 if(!strcasecmp(substring(s, strlen(s) - 4, 4), ".bsp"))
635 s = substring(s, 0, strlen(s) - 4);
636 strcpy(mi_shortname, s);
648 MapInfo_Get_ByName(mi_shortname, 0, NULL);
649 if(MapInfo_Map_mins.x < MapInfo_Map_maxs.x)
651 mi_min = MapInfo_Map_mins;
652 mi_max = MapInfo_Map_maxs;
660 tracebox('1 0 0' * mi.x,
661 '0 1 0' * mi.y + '0 0 1' * mi.z,
662 '0 1 0' * ma.y + '0 0 1' * ma.z,
666 if(!trace_startsolid)
667 mi_min.x = trace_endpos.x;
669 tracebox('0 1 0' * mi.y,
670 '1 0 0' * mi.x + '0 0 1' * mi.z,
671 '1 0 0' * ma.x + '0 0 1' * ma.z,
675 if(!trace_startsolid)
676 mi_min.y = trace_endpos.y;
678 tracebox('0 0 1' * mi.z,
679 '1 0 0' * mi.x + '0 1 0' * mi.y,
680 '1 0 0' * ma.x + '0 1 0' * ma.y,
684 if(!trace_startsolid)
685 mi_min.z = trace_endpos.z;
687 tracebox('1 0 0' * ma.x,
688 '0 1 0' * mi.y + '0 0 1' * mi.z,
689 '0 1 0' * ma.y + '0 0 1' * ma.z,
693 if(!trace_startsolid)
694 mi_max.x = trace_endpos.x;
696 tracebox('0 1 0' * ma.y,
697 '1 0 0' * mi.x + '0 0 1' * mi.z,
698 '1 0 0' * ma.x + '0 0 1' * ma.z,
702 if(!trace_startsolid)
703 mi_max.y = trace_endpos.y;
705 tracebox('0 0 1' * ma.z,
706 '1 0 0' * mi.x + '0 1 0' * mi.y,
707 '1 0 0' * ma.x + '0 1 0' * ma.y,
711 if(!trace_startsolid)
712 mi_max.z = trace_endpos.z;
717 void get_mi_min_max_texcoords(float mode)
721 get_mi_min_max(mode);
726 // extend mi_picmax to get a square aspect ratio
727 // center the map in that area
728 extend = mi_picmax - mi_picmin;
729 if(extend.y > extend.x)
731 mi_picmin.x -= (extend.y - extend.x) * 0.5;
732 mi_picmax.x += (extend.y - extend.x) * 0.5;
736 mi_picmin.y -= (extend.x - extend.y) * 0.5;
737 mi_picmax.y += (extend.x - extend.y) * 0.5;
740 // add another some percent
741 extend = (mi_picmax - mi_picmin) * (1 / 64.0);
745 // calculate the texcoords
746 mi_pictexcoord0 = mi_pictexcoord1 = mi_pictexcoord2 = mi_pictexcoord3 = '0 0 0';
747 // first the two corners of the origin
748 mi_pictexcoord0_x = (mi_min.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
749 mi_pictexcoord0_y = (mi_min.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
750 mi_pictexcoord2_x = (mi_max.x - mi_picmin.x) / (mi_picmax.x - mi_picmin.x);
751 mi_pictexcoord2_y = (mi_max.y - mi_picmin.y) / (mi_picmax.y - mi_picmin.y);
752 // then the other corners
753 mi_pictexcoord1_x = mi_pictexcoord0_x;
754 mi_pictexcoord1_y = mi_pictexcoord2_y;
755 mi_pictexcoord3_x = mi_pictexcoord2_x;
756 mi_pictexcoord3_y = mi_pictexcoord0_y;
760 float cvar_settemp(string tmp_cvar, string tmp_value)
762 float created_saved_value;
764 created_saved_value = 0;
766 if (!(tmp_cvar || tmp_value))
768 LOG_TRACE("Error: Invalid usage of cvar_settemp(string, string); !");
772 if(!cvar_type(tmp_cvar))
774 LOG_INFOF("Error: cvar %s doesn't exist!", tmp_cvar);
778 IL_EACH(g_saved_cvars, it.netname == tmp_cvar,
780 created_saved_value = -1; // skip creation
781 break; // no need to continue
784 if(created_saved_value != -1)
786 // creating a new entity to keep track of this cvar
787 entity e = new_pure(saved_cvar_value);
788 IL_PUSH(g_saved_cvars, e);
789 e.netname = strzone(tmp_cvar);
790 e.message = strzone(cvar_string(tmp_cvar));
791 created_saved_value = 1;
794 // update the cvar to the value given
795 cvar_set(tmp_cvar, tmp_value);
797 return created_saved_value;
800 int cvar_settemp_restore()
803 // FIXME this new-style loop fails!
805 FOREACH_ENTITY_CLASS("saved_cvar_value", true,
807 if(cvar_type(it.netname))
809 cvar_set(it.netname, it.message);
810 strunzone(it.netname);
811 strunzone(it.message);
816 LOG_INFOF("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", it.netname);
821 while((e = find(e, classname, "saved_cvar_value")))
823 if(cvar_type(e.netname))
825 cvar_set(e.netname, e.message);
830 print(sprintf("Error: cvar %s doesn't exist anymore! It can still be restored once it's manually recreated.", e.netname));
837 float textLengthUpToWidth(string theText, float maxWidth, vector theSize, textLengthUpToWidth_widthFunction_t w)
840 // The following function is SLOW.
841 // For your safety and for the protection of those around you...
842 // DO NOT CALL THIS AT HOME.
844 if(w(theText, theSize) <= maxWidth)
845 return strlen(theText); // yeah!
847 bool colors = (w("^7", theSize) == 0);
849 // binary search for right place to cut string
850 int len, left, right, middle;
852 right = len = strlen(theText);
856 middle = floor((left + right) / 2);
859 vector res = checkColorCode(theText, len, middle, false);
860 ofs = (res.x) ? res.x - res.y : 0;
863 if(w(substring(theText, 0, middle + ofs), theSize) <= maxWidth)
868 while(left < right - 1);
873 float textLengthUpToLength(string theText, int maxLength, textLengthUpToLength_lenFunction_t w)
876 // The following function is SLOW.
877 // For your safety and for the protection of those around you...
878 // DO NOT CALL THIS AT HOME.
880 if(w(theText) <= maxLength)
881 return strlen(theText); // yeah!
883 bool colors = (w("^7") == 0);
885 // binary search for right place to cut string
886 int len, left, right, middle;
888 right = len = strlen(theText);
892 middle = floor((left + right) / 2);
895 vector res = checkColorCode(theText, len, middle, true);
896 ofs = (!res.x) ? 0 : res.x - res.y;
899 if(w(substring(theText, 0, middle + ofs)) <= maxLength)
904 while(left < right - 1);
909 string find_last_color_code(string s)
911 int start = strstrofs(s, "^", 0);
912 if (start == -1) // no caret found
914 int len = strlen(s)-1;
915 for(int i = len; i >= start; --i)
917 if(substring(s, i, 1) != "^")
921 while (i-carets >= start && substring(s, i-carets, 1) == "^")
924 // check if carets aren't all escaped
928 if(IS_DIGIT(substring(s, i+1, 1)))
929 return substring(s, i, 2);
932 if(substring(s, i+1, 1) == "x")
933 if(IS_HEXDIGIT(substring(s, i + 2, 1)))
934 if(IS_HEXDIGIT(substring(s, i + 3, 1)))
935 if(IS_HEXDIGIT(substring(s, i + 4, 1)))
936 return substring(s, i, 5);
938 i -= carets; // this also skips one char before the carets
944 string getWrappedLine(float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
946 string s = getWrappedLine_remaining;
950 getWrappedLine_remaining = string_null;
951 return s; // the line has no size ANYWAY, nothing would be displayed.
954 int take_until = textLengthUpToWidth(s, maxWidth, theFontSize, tw);
955 if(take_until > 0 && take_until < strlen(s))
957 int last_word = take_until - 1;
958 while(last_word > 0 && substring(s, last_word, 1) != " ")
964 take_until = last_word;
968 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
969 if(getWrappedLine_remaining == "")
970 getWrappedLine_remaining = string_null;
971 else if (tw("^7", theFontSize) == 0)
972 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
973 return substring(s, 0, take_until);
977 getWrappedLine_remaining = string_null;
982 string getWrappedLineLen(int maxLength, textLengthUpToLength_lenFunction_t tw)
984 string s = getWrappedLine_remaining;
988 getWrappedLine_remaining = string_null;
989 return s; // the line has no size ANYWAY, nothing would be displayed.
992 int take_until = textLengthUpToLength(s, maxLength, tw);
993 if(take_until > 0 && take_until < strlen(s))
995 int last_word = take_until - 1;
996 while(last_word > 0 && substring(s, last_word, 1) != " ")
1002 take_until = last_word;
1006 getWrappedLine_remaining = substring(s, take_until + skip, strlen(s) - take_until);
1007 if(getWrappedLine_remaining == "")
1008 getWrappedLine_remaining = string_null;
1009 else if (tw("^7") == 0)
1010 getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining);
1011 return substring(s, 0, take_until);
1015 getWrappedLine_remaining = string_null;
1020 string textShortenToWidth(string theText, float maxWidth, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
1022 if(tw(theText, theFontSize) <= maxWidth)
1025 return strcat(substring(theText, 0, textLengthUpToWidth(theText, maxWidth - tw("...", theFontSize), theFontSize, tw)), "...");
1028 string textShortenToLength(string theText, float maxWidth, textLengthUpToLength_lenFunction_t tw)
1030 if(tw(theText) <= maxWidth)
1033 return strcat(substring(theText, 0, textLengthUpToLength(theText, maxWidth - tw("..."), tw)), "...");
1036 float isGametypeInFilter(Gametype gt, float tp, float ts, string pattern)
1038 string subpattern, subpattern2, subpattern3, subpattern4;
1039 subpattern = strcat(",", MapInfo_Type_ToString(gt), ",");
1041 subpattern2 = ",teams,";
1043 subpattern2 = ",noteams,";
1045 subpattern3 = ",teamspawns,";
1047 subpattern3 = ",noteamspawns,";
1048 if(gt == MAPINFO_TYPE_RACE || gt == MAPINFO_TYPE_CTS)
1049 subpattern4 = ",race,";
1051 subpattern4 = string_null;
1053 if(substring(pattern, 0, 1) == "-")
1055 pattern = substring(pattern, 1, strlen(pattern) - 1);
1056 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) >= 0)
1058 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) >= 0)
1060 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) >= 0)
1062 if(subpattern4 && strstrofs(strcat(",", pattern, ","), subpattern4, 0) >= 0)
1067 if(substring(pattern, 0, 1) == "+")
1068 pattern = substring(pattern, 1, strlen(pattern) - 1);
1069 if(strstrofs(strcat(",", pattern, ","), subpattern, 0) < 0)
1070 if(strstrofs(strcat(",", pattern, ","), subpattern2, 0) < 0)
1071 if(strstrofs(strcat(",", pattern, ","), subpattern3, 0) < 0)
1075 if(strstrofs(strcat(",", pattern, ","), subpattern4, 0) < 0)
1082 vector solve_shotdirection(vector myorg, vector myvel, vector eorg, vector evel, float spd, float newton_style)
1086 // make origin and speed relative
1091 // now solve for ret, ret normalized:
1092 // eorg + t * evel == t * ret * spd
1093 // or, rather, solve for t:
1094 // |eorg + t * evel| == t * spd
1095 // eorg^2 + t^2 * evel^2 + 2 * t * (eorg * evel) == t^2 * spd^2
1096 // t^2 * (evel^2 - spd^2) + t * (2 * (eorg * evel)) + eorg^2 == 0
1097 vector solution = solve_quadratic(evel * evel - spd * spd, 2 * (eorg * evel), eorg * eorg);
1098 // p = 2 * (eorg * evel) / (evel * evel - spd * spd)
1099 // q = (eorg * eorg) / (evel * evel - spd * spd)
1100 if(!solution.z) // no real solution
1103 // (eorg * evel)^2 < (evel^2 - spd^2) * eorg^2
1104 // (eorg * evel)^2 / eorg^2 < evel^2 - spd^2
1105 // spd^2 < ((evel^2 * eorg^2) - (eorg * evel)^2) / eorg^2
1106 // spd^2 < evel^2 * (1 - cos^2 angle(evel, eorg))
1107 // spd^2 < evel^2 * sin^2 angle(evel, eorg)
1108 // spd < |evel| * sin angle(evel, eorg)
1111 else if(solution.x > 0)
1113 // both solutions > 0: take the smaller one
1114 // happens if p < 0 and q > 0
1115 ret = normalize(eorg + solution.x * evel);
1117 else if(solution.y > 0)
1119 // one solution > 0: take the larger one
1120 // happens if q < 0 or q == 0 and p < 0
1121 ret = normalize(eorg + solution.y * evel);
1125 // no solution > 0: reject
1126 // happens if p > 0 and q >= 0
1127 // 2 * (eorg * evel) / (evel * evel - spd * spd) > 0
1128 // (eorg * eorg) / (evel * evel - spd * spd) >= 0
1133 // "Enemy is moving away from me at more than spd"
1137 // NOTE: we always got a solution if spd > |evel|
1139 if(newton_style == 2)
1140 ret = normalize(ret * spd + myvel);
1145 vector get_shotvelocity(vector myvel, vector mydir, float spd, float newton_style, float mi, float ma)
1150 if(newton_style == 2)
1152 // true Newtonian projectiles with automatic aim adjustment
1154 // solve: |outspeed * mydir - myvel| = spd
1155 // outspeed^2 - 2 * outspeed * (mydir * myvel) + myvel^2 - spd^2 = 0
1156 // outspeed = (mydir * myvel) +- sqrt((mydir * myvel)^2 - myvel^2 + spd^2)
1160 // myvel^2 - (mydir * myvel)^2 > spd^2
1161 // velocity without mydir component > spd
1162 // fire at smallest possible spd that works?
1163 // |(mydir * myvel) * myvel - myvel| = spd
1165 vector solution = solve_quadratic(1, -2 * (mydir * myvel), myvel * myvel - spd * spd);
1169 outspeed = solution.y; // the larger one
1172 //outspeed = 0; // slowest possible shot
1173 outspeed = solution.x; // the real part (that is, the average!)
1174 //dprint("impossible shot, adjusting\n");
1177 outspeed = bound(spd * mi, outspeed, spd * ma);
1178 return mydir * outspeed;
1182 return myvel + spd * mydir;
1185 float compressShotOrigin(vector v)
1187 float rx = rint(v.x * 2);
1188 float ry = rint(v.y * 4) + 128;
1189 float rz = rint(v.z * 4) + 128;
1190 if(rx > 255 || rx < 0)
1192 LOG_DEBUG("shot origin ", vtos(v), " x out of bounds\n");
1193 rx = bound(0, rx, 255);
1195 if(ry > 255 || ry < 0)
1197 LOG_DEBUG("shot origin ", vtos(v), " y out of bounds\n");
1198 ry = bound(0, ry, 255);
1200 if(rz > 255 || rz < 0)
1202 LOG_DEBUG("shot origin ", vtos(v), " z out of bounds\n");
1203 rz = bound(0, rz, 255);
1205 return rx * 0x10000 + ry * 0x100 + rz;
1207 vector decompressShotOrigin(int f)
1210 v.x = ((f & 0xFF0000) / 0x10000) / 2;
1211 v.y = ((f & 0xFF00) / 0x100 - 128) / 4;
1212 v.z = ((f & 0xFF) - 128) / 4;
1217 vector healtharmor_maxdamage(float h, float a, float armorblock, int deathtype)
1219 // NOTE: we'll always choose the SMALLER value...
1220 float healthdamage, armordamage, armorideal;
1221 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1224 healthdamage = (h - 1) / (1 - armorblock); // damage we can take if we could use more health
1225 armordamage = a + (h - 1); // damage we can take if we could use more armor
1226 armorideal = healthdamage * armorblock;
1228 if(armordamage < healthdamage)
1241 vector healtharmor_applydamage(float a, float armorblock, int deathtype, float damage)
1244 if (DEATH_IS(deathtype, DEATH_DROWN)) // Why should armor help here...
1246 if (deathtype & HITTYPE_ARMORPIERCE)
1248 v.y = bound(0, damage * armorblock, a); // save
1249 v.x = bound(0, damage - v.y, damage); // take
1255 string getcurrentmod()
1259 m = cvar_string("fs_gamedir");
1260 n = tokenize_console(m);
1267 float matchacl(string acl, string str)
1274 t = car(acl); acl = cdr(acl);
1277 if(substring(t, 0, 1) == "-")
1280 t = substring(t, 1, strlen(t) - 1);
1282 else if(substring(t, 0, 1) == "+")
1283 t = substring(t, 1, strlen(t) - 1);
1285 if(substring(t, -1, 1) == "*")
1287 t = substring(t, 0, strlen(t) - 1);
1288 s = substring(str, 0, strlen(t));
1296 break; // if we found a killing case, apply it! other settings may be allowed in the future, but this one is caught
1303 void write_String_To_File(int fh, string str, bool alsoprint)
1306 if (alsoprint) LOG_HELP(str);
1309 string get_model_datafilename(string m, float sk, string fil)
1314 m = "models/player/*_";
1316 m = strcat(m, ftos(sk));
1319 return strcat(m, ".", fil);
1322 float get_model_parameters(string m, float sk)
1324 get_model_parameters_modelname = string_null;
1325 get_model_parameters_modelskin = -1;
1326 get_model_parameters_name = string_null;
1327 get_model_parameters_species = -1;
1328 get_model_parameters_sex = string_null;
1329 get_model_parameters_weight = -1;
1330 get_model_parameters_age = -1;
1331 get_model_parameters_desc = string_null;
1332 get_model_parameters_bone_upperbody = string_null;
1333 get_model_parameters_bone_weapon = string_null;
1334 for(int i = 0; i < MAX_AIM_BONES; ++i)
1336 get_model_parameters_bone_aim[i] = string_null;
1337 get_model_parameters_bone_aimweight[i] = 0;
1339 get_model_parameters_fixbone = 0;
1340 get_model_parameters_hidden = false;
1343 MUTATOR_CALLHOOK(ClearModelParams);
1349 if(substring(m, -9, 5) == "_lod1" || substring(m, -9, 5) == "_lod2")
1350 m = strcat(substring(m, 0, -10), substring(m, -4, -1));
1354 if(substring(m, -4, -1) != ".txt")
1356 if(substring(m, -6, 1) != "_")
1358 sk = stof(substring(m, -5, 1));
1359 m = substring(m, 0, -7);
1362 string fn = get_model_datafilename(m, sk, "txt");
1363 int fh = fopen(fn, FILE_READ);
1367 fn = get_model_datafilename(m, sk, "txt");
1368 fh = fopen(fn, FILE_READ);
1373 get_model_parameters_modelname = m;
1374 get_model_parameters_modelskin = sk;
1376 while((s = fgets(fh)))
1379 break; // next lines will be description
1383 get_model_parameters_name = s;
1387 case "human": get_model_parameters_species = SPECIES_HUMAN; break;
1388 case "alien": get_model_parameters_species = SPECIES_ALIEN; break;
1389 case "robot_shiny": get_model_parameters_species = SPECIES_ROBOT_SHINY; break;
1390 case "robot_rusty": get_model_parameters_species = SPECIES_ROBOT_RUSTY; break;
1391 case "robot_solid": get_model_parameters_species = SPECIES_ROBOT_SOLID; break;
1392 case "animal": get_model_parameters_species = SPECIES_ANIMAL; break;
1393 case "reserved": get_model_parameters_species = SPECIES_RESERVED; break;
1397 if (s == "Male") s = _("Male");
1398 else if (s == "Female") s = _("Female");
1399 else if (s == "Undisclosed") s = _("Undisclosed");
1400 get_model_parameters_sex = s;
1403 get_model_parameters_weight = stof(s);
1405 get_model_parameters_age = stof(s);
1406 if(c == "description")
1407 get_model_parameters_description = s;
1408 if(c == "bone_upperbody")
1409 get_model_parameters_bone_upperbody = s;
1410 if(c == "bone_weapon")
1411 get_model_parameters_bone_weapon = s;
1413 MUTATOR_CALLHOOK(GetModelParams, c, s);
1415 for(int i = 0; i < MAX_AIM_BONES; ++i)
1416 if(c == strcat("bone_aim", ftos(i)))
1418 get_model_parameters_bone_aimweight[i] = stof(car(s));
1419 get_model_parameters_bone_aim[i] = cdr(s);
1422 get_model_parameters_fixbone = stof(s);
1424 get_model_parameters_hidden = stob(s);
1427 while((s = fgets(fh)))
1429 if(get_model_parameters_desc)
1430 get_model_parameters_desc = strcat(get_model_parameters_desc, "\n");
1432 get_model_parameters_desc = strcat(get_model_parameters_desc, s);
1440 string translate_key(string key)
1442 if (prvm_language == "en") return key;
1444 if (substring(key, 0, 1) == "<")
1446 if (key == "<KEY NOT FOUND>") return _("<KEY NOT FOUND>");
1447 if (key == "<UNKNOWN KEYNUM>") return _("<UNKNOWN KEYNUM>");
1452 case "TAB": return _("TAB");
1453 case "ENTER": return _("ENTER");
1454 case "ESCAPE": return _("ESCAPE");
1455 case "SPACE": return _("SPACE");
1457 case "BACKSPACE": return _("BACKSPACE");
1458 case "UPARROW": return _("UPARROW");
1459 case "DOWNARROW": return _("DOWNARROW");
1460 case "LEFTARROW": return _("LEFTARROW");
1461 case "RIGHTARROW": return _("RIGHTARROW");
1463 case "ALT": return _("ALT");
1464 case "CTRL": return _("CTRL");
1465 case "SHIFT": return _("SHIFT");
1467 case "INS": return _("INS");
1468 case "DEL": return _("DEL");
1469 case "PGDN": return _("PGDN");
1470 case "PGUP": return _("PGUP");
1471 case "HOME": return _("HOME");
1472 case "END": return _("END");
1474 case "PAUSE": return _("PAUSE");
1476 case "NUMLOCK": return _("NUMLOCK");
1477 case "CAPSLOCK": return _("CAPSLOCK");
1478 case "SCROLLOCK": return _("SCROLLOCK");
1480 case "SEMICOLON": return _("SEMICOLON");
1481 case "TILDE": return _("TILDE");
1482 case "BACKQUOTE": return _("BACKQUOTE");
1483 case "QUOTE": return _("QUOTE");
1484 case "APOSTROPHE": return _("APOSTROPHE");
1485 case "BACKSLASH": return _("BACKSLASH");
1488 if (substring(key, 0, 1) == "F")
1490 string subkey = substring(key, 1, -1);
1491 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1493 return sprintf(_("F%d"), stof(subkey));
1495 // continue in case there is another key name starting with F
1498 if (substring(key, 0, 3) == "KP_")
1500 string subkey = substring(key, 3, -1);
1501 if (IS_DIGIT(substring(key, 3, 1))) // check only first digit
1503 return sprintf(_("KP_%d"), stof(subkey));
1508 case "INS": return sprintf(_("KP_%s"), _("INS"));
1509 case "END": return sprintf(_("KP_%s"), _("END"));
1510 case "DOWNARROW": return sprintf(_("KP_%s"), _("DOWNARROW"));
1511 case "PGDN": return sprintf(_("KP_%s"), _("PGDN"));
1512 case "LEFTARROW": return sprintf(_("KP_%s"), _("LEFTARROW"));
1513 case "RIGHTARROW": return sprintf(_("KP_%s"), _("RIGHTARROW"));
1514 case "HOME": return sprintf(_("KP_%s"), _("HOME"));
1515 case "UPARROW": return sprintf(_("KP_%s"), _("UPARROW"));
1516 case "PGUP": return sprintf(_("KP_%s"), _("PGUP"));
1517 case "PERIOD": return sprintf(_("KP_%s"), _("PERIOD"));
1518 case "DEL": return sprintf(_("KP_%s"), _("DEL"));
1519 case "DIVIDE": return sprintf(_("KP_%s"), _("DIVIDE"));
1520 case "SLASH": return sprintf(_("KP_%s"), _("SLASH"));
1521 case "MULTIPLY": return sprintf(_("KP_%s"), _("MULTIPLY"));
1522 case "MINUS": return sprintf(_("KP_%s"), _("MINUS"));
1523 case "PLUS": return sprintf(_("KP_%s"), _("PLUS"));
1524 case "ENTER": return sprintf(_("KP_%s"), _("ENTER"));
1525 case "EQUALS": return sprintf(_("KP_%s"), _("EQUALS"));
1526 default: return key;
1530 if (key == "PRINTSCREEN") return _("PRINTSCREEN");
1532 if (substring(key, 0, 5) == "MOUSE")
1533 return sprintf(_("MOUSE%d"), stof(substring(key, 5, -1)));
1535 if (key == "MWHEELUP") return _("MWHEELUP");
1536 if (key == "MWHEELDOWN") return _("MWHEELDOWN");
1538 if (substring(key, 0,3) == "JOY")
1539 return sprintf(_("JOY%d"), stof(substring(key, 3, -1)));
1541 if (substring(key, 0,3) == "AUX")
1542 return sprintf(_("AUX%d"), stof(substring(key, 3, -1)));
1544 if (substring(key, 0, 4) == "X360_")
1546 string subkey = substring(key, 4, -1);
1549 case "DPAD_UP": return sprintf(_("X360_%s"), _("DPAD_UP"));
1550 case "DPAD_DOWN": return sprintf(_("X360_%s"), _("DPAD_DOWN"));
1551 case "DPAD_LEFT": return sprintf(_("X360_%s"), _("DPAD_LEFT"));
1552 case "DPAD_RIGHT": return sprintf(_("X360_%s"), _("DPAD_RIGHT"));
1553 case "START": return sprintf(_("X360_%s"), _("START"));
1554 case "BACK": return sprintf(_("X360_%s"), _("BACK"));
1555 case "LEFT_THUMB": return sprintf(_("X360_%s"), _("LEFT_THUMB"));
1556 case "RIGHT_THUMB": return sprintf(_("X360_%s"), _("RIGHT_THUMB"));
1557 case "LEFT_SHOULDER": return sprintf(_("X360_%s"), _("LEFT_SHOULDER"));
1558 case "RIGHT_SHOULDER": return sprintf(_("X360_%s"), _("RIGHT_SHOULDER"));
1559 case "LEFT_TRIGGER": return sprintf(_("X360_%s"), _("LEFT_TRIGGER"));
1560 case "RIGHT_TRIGGER": return sprintf(_("X360_%s"), _("RIGHT_TRIGGER"));
1561 case "LEFT_THUMB_UP": return sprintf(_("X360_%s"), _("LEFT_THUMB_UP"));
1562 case "LEFT_THUMB_DOWN": return sprintf(_("X360_%s"), _("LEFT_THUMB_DOWN"));
1563 case "LEFT_THUMB_LEFT": return sprintf(_("X360_%s"), _("LEFT_THUMB_LEFT"));
1564 case "LEFT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("LEFT_THUMB_RIGHT"));
1565 case "RIGHT_THUMB_UP": return sprintf(_("X360_%s"), _("RIGHT_THUMB_UP"));
1566 case "RIGHT_THUMB_DOWN": return sprintf(_("X360_%s"), _("RIGHT_THUMB_DOWN"));
1567 case "RIGHT_THUMB_LEFT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_LEFT"));
1568 case "RIGHT_THUMB_RIGHT": return sprintf(_("X360_%s"), _("RIGHT_THUMB_RIGHT"));
1569 default: return key;
1573 if (substring(key, 0, 4) == "JOY_")
1575 string subkey = substring(key, 4, -1);
1578 case "UP": return sprintf(_("JOY_%s"), _("UP"));
1579 case "DOWN": return sprintf(_("JOY_%s"), _("DOWN"));
1580 case "LEFT": return sprintf(_("JOY_%s"), _("LEFT"));
1581 case "RIGHT": return sprintf(_("JOY_%s"), _("RIGHT"));
1582 default: return key;
1586 if (substring(key, 0, 8) == "MIDINOTE")
1587 return sprintf(_("MIDINOTE%d"), stof(substring(key, 8, -1)));
1592 // x-encoding (encoding as zero length invisible string)
1593 const string XENCODE_2 = "xX";
1594 const string XENCODE_22 = "0123456789abcdefABCDEF";
1595 string xencode(int f)
1598 d = f % 22; f = floor(f / 22);
1599 c = f % 22; f = floor(f / 22);
1600 b = f % 22; f = floor(f / 22);
1601 a = f % 2; // f = floor(f / 2);
1604 substring(XENCODE_2, a, 1),
1605 substring(XENCODE_22, b, 1),
1606 substring(XENCODE_22, c, 1),
1607 substring(XENCODE_22, d, 1)
1610 float xdecode(string s)
1613 if(substring(s, 0, 1) != "^")
1617 a = strstrofs(XENCODE_2, substring(s, 1, 1), 0);
1618 b = strstrofs(XENCODE_22, substring(s, 2, 1), 0);
1619 c = strstrofs(XENCODE_22, substring(s, 3, 1), 0);
1620 d = strstrofs(XENCODE_22, substring(s, 4, 1), 0);
1621 if(a < 0 || b < 0 || c < 0 || d < 0)
1623 return ((a * 22 + b) * 22 + c) * 22 + d;
1627 string strlimitedlen(string input, string truncation, float strip_colors, float limit)
1629 if(strlen((strip_colors ? strdecolorize(input) : input)) <= limit)
1632 return strcat(substring(input, 0, (strlen(input) - strlen(truncation))), truncation);
1635 float shutdown_running;
1640 void CSQC_Shutdown()
1646 if(shutdown_running)
1648 LOG_INFO("Recursive shutdown detected! Only restoring cvars...");
1652 shutdown_running = 1;
1656 cvar_settemp_restore(); // this must be done LAST, but in any case
1660 .float skeleton_bones_index;
1661 void Skeleton_SetBones(entity e)
1663 // set skeleton_bones to the total number of bones on the model
1664 if(e.skeleton_bones_index == e.modelindex)
1665 return; // same model, nothing to update
1668 skelindex = skel_create(e.modelindex);
1669 e.skeleton_bones = skel_get_numbones(skelindex);
1670 skel_delete(skelindex);
1671 e.skeleton_bones_index = e.modelindex;
1675 string to_execute_next_frame;
1676 void execute_next_frame()
1678 if(to_execute_next_frame)
1680 localcmd("\n", to_execute_next_frame, "\n");
1681 strfree(to_execute_next_frame);
1684 void queue_to_execute_next_frame(string s)
1686 if(to_execute_next_frame)
1688 s = strcat(s, "\n", to_execute_next_frame);
1690 strcpy(to_execute_next_frame, s);
1693 .float FindConnectedComponent_processing;
1694 void FindConnectedComponent(entity e, .entity fld, findNextEntityNearFunction_t nxt, isConnectedFunction_t iscon, entity pass)
1696 entity queue_start, queue_end;
1698 // we build a queue of to-be-processed entities.
1699 // queue_start is the next entity to be checked for neighbors
1700 // queue_end is the last entity added
1702 if(e.FindConnectedComponent_processing)
1703 error("recursion or broken cleanup");
1705 // start with a 1-element queue
1706 queue_start = queue_end = e;
1707 queue_end.(fld) = NULL;
1708 queue_end.FindConnectedComponent_processing = 1;
1710 // for each queued item:
1711 for (; queue_start; queue_start = queue_start.(fld))
1713 // find all neighbors of queue_start
1715 for(t = NULL; (t = nxt(t, queue_start, pass)); )
1717 if(t.FindConnectedComponent_processing)
1719 if(iscon(t, queue_start, pass))
1721 // it is connected? ADD IT. It will look for neighbors soon too.
1722 queue_end.(fld) = t;
1724 queue_end.(fld) = NULL;
1725 queue_end.FindConnectedComponent_processing = 1;
1731 for (queue_start = e; queue_start; queue_start = queue_start.(fld))
1732 queue_start.FindConnectedComponent_processing = 0;
1736 vector animfixfps(entity e, vector a, vector b)
1738 // multi-frame anim: keep as-is
1741 float dur = frameduration(e.modelindex, a.x);
1742 if (dur <= 0 && b.y)
1745 dur = frameduration(e.modelindex, a.x);
1755 Notification Announcer_PickNumber(int type, int num)
1764 case 10: return ANNCE_NUM_GAMESTART_10;
1765 case 9: return ANNCE_NUM_GAMESTART_9;
1766 case 8: return ANNCE_NUM_GAMESTART_8;
1767 case 7: return ANNCE_NUM_GAMESTART_7;
1768 case 6: return ANNCE_NUM_GAMESTART_6;
1769 case 5: return ANNCE_NUM_GAMESTART_5;
1770 case 4: return ANNCE_NUM_GAMESTART_4;
1771 case 3: return ANNCE_NUM_GAMESTART_3;
1772 case 2: return ANNCE_NUM_GAMESTART_2;
1773 case 1: return ANNCE_NUM_GAMESTART_1;
1781 case 10: return ANNCE_NUM_KILL_10;
1782 case 9: return ANNCE_NUM_KILL_9;
1783 case 8: return ANNCE_NUM_KILL_8;
1784 case 7: return ANNCE_NUM_KILL_7;
1785 case 6: return ANNCE_NUM_KILL_6;
1786 case 5: return ANNCE_NUM_KILL_5;
1787 case 4: return ANNCE_NUM_KILL_4;
1788 case 3: return ANNCE_NUM_KILL_3;
1789 case 2: return ANNCE_NUM_KILL_2;
1790 case 1: return ANNCE_NUM_KILL_1;
1798 case 10: return ANNCE_NUM_RESPAWN_10;
1799 case 9: return ANNCE_NUM_RESPAWN_9;
1800 case 8: return ANNCE_NUM_RESPAWN_8;
1801 case 7: return ANNCE_NUM_RESPAWN_7;
1802 case 6: return ANNCE_NUM_RESPAWN_6;
1803 case 5: return ANNCE_NUM_RESPAWN_5;
1804 case 4: return ANNCE_NUM_RESPAWN_4;
1805 case 3: return ANNCE_NUM_RESPAWN_3;
1806 case 2: return ANNCE_NUM_RESPAWN_2;
1807 case 1: return ANNCE_NUM_RESPAWN_1;
1811 case CNT_ROUNDSTART:
1815 case 10: return ANNCE_NUM_ROUNDSTART_10;
1816 case 9: return ANNCE_NUM_ROUNDSTART_9;
1817 case 8: return ANNCE_NUM_ROUNDSTART_8;
1818 case 7: return ANNCE_NUM_ROUNDSTART_7;
1819 case 6: return ANNCE_NUM_ROUNDSTART_6;
1820 case 5: return ANNCE_NUM_ROUNDSTART_5;
1821 case 4: return ANNCE_NUM_ROUNDSTART_4;
1822 case 3: return ANNCE_NUM_ROUNDSTART_3;
1823 case 2: return ANNCE_NUM_ROUNDSTART_2;
1824 case 1: return ANNCE_NUM_ROUNDSTART_1;
1833 case 10: return ANNCE_NUM_10;
1834 case 9: return ANNCE_NUM_9;
1835 case 8: return ANNCE_NUM_8;
1836 case 7: return ANNCE_NUM_7;
1837 case 6: return ANNCE_NUM_6;
1838 case 5: return ANNCE_NUM_5;
1839 case 4: return ANNCE_NUM_4;
1840 case 3: return ANNCE_NUM_3;
1841 case 2: return ANNCE_NUM_2;
1842 case 1: return ANNCE_NUM_1;
1851 int Mod_Q1BSP_SuperContentsFromNativeContents(int nativecontents)
1853 switch(nativecontents)
1858 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
1860 return DPCONTENTS_WATER;
1862 return DPCONTENTS_SLIME;
1864 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
1866 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
1871 int Mod_Q1BSP_NativeContentsFromSuperContents(int supercontents)
1873 if(supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
1874 return CONTENT_SOLID;
1875 if(supercontents & DPCONTENTS_SKY)
1877 if(supercontents & DPCONTENTS_LAVA)
1878 return CONTENT_LAVA;
1879 if(supercontents & DPCONTENTS_SLIME)
1880 return CONTENT_SLIME;
1881 if(supercontents & DPCONTENTS_WATER)
1882 return CONTENT_WATER;
1883 return CONTENT_EMPTY;
1888 void attach_sameorigin(entity e, entity to, string tag)
1890 vector org, t_forward, t_left, t_up, e_forward, e_up;
1893 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1894 tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1895 t_forward = v_forward * tagscale;
1896 t_left = v_right * -tagscale;
1897 t_up = v_up * tagscale;
1899 e.origin_x = org * t_forward;
1900 e.origin_y = org * t_left;
1901 e.origin_z = org * t_up;
1903 // current forward and up directions
1904 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1905 e.angles = AnglesTransform_FromVAngles(e.angles);
1907 e.angles = AnglesTransform_FromAngles(e.angles);
1908 fixedmakevectors(e.angles);
1910 // untransform forward, up!
1911 e_forward.x = v_forward * t_forward;
1912 e_forward.y = v_forward * t_left;
1913 e_forward.z = v_forward * t_up;
1914 e_up.x = v_up * t_forward;
1915 e_up.y = v_up * t_left;
1916 e_up.z = v_up * t_up;
1918 e.angles = fixedvectoangles2(e_forward, e_up);
1919 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1920 e.angles = AnglesTransform_ToVAngles(e.angles);
1922 e.angles = AnglesTransform_ToAngles(e.angles);
1924 setattachment(e, to, tag);
1925 setorigin(e, e.origin);
1928 void detach_sameorigin(entity e)
1931 org = gettaginfo(e, 0);
1932 e.angles = fixedvectoangles2(v_forward, v_up);
1933 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1934 e.angles = AnglesTransform_ToVAngles(e.angles);
1936 e.angles = AnglesTransform_ToAngles(e.angles);
1938 setattachment(e, NULL, "");
1939 setorigin(e, e.origin);
1942 void follow_sameorigin(entity e, entity to)
1944 set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1945 e.aiment = to; // make the hole follow bmodel
1946 e.punchangle = to.angles; // the original angles of bmodel
1947 e.view_ofs = e.origin - to.origin; // relative origin
1948 e.v_angle = e.angles - to.angles; // relative angles
1952 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1953 void unfollow_sameorigin(entity e)
1955 set_movetype(e, MOVETYPE_NONE);
1959 .string aiment_classname;
1960 .float aiment_deadflag;
1961 void SetMovetypeFollow(entity ent, entity e)
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 = 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 ent.aiment_classname = string_null;
1986 // FIXME: engine bug?
1987 // resetting aiment the engine will set orb's origin close to world's origin
1988 //ent.aiment = NULL;
1991 int LostMovetypeFollow(entity ent)
1994 if(ent.move_movetype != MOVETYPE_FOLLOW)
1998 // FIXME: engine bug?
1999 // when aiment disconnects the engine will set orb's origin close to world's origin
2002 if(ent.aiment.classname != ent.aiment_classname || ent.aiment.deadflag != ent.aiment_deadflag)
2009 // decolorizes and team colors the player name when needed
2010 string playername(string thename, int teamid, bool team_colorize)
2013 bool do_colorize = (teamplay && team_colorize);
2015 if(do_colorize && !intermission_running)
2020 string t = Team_ColorCode(teamid);
2021 return strcat(t, strdecolorize(thename));
2027 float trace_hits_box_a0, trace_hits_box_a1;
2029 float trace_hits_box_1d(float end, float thmi, float thma)
2033 // just check if x is in range
2041 // do the trace with respect to x
2042 // 0 -> end has to stay in thmi -> thma
2043 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
2044 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
2045 if (trace_hits_box_a0 > trace_hits_box_a1)
2051 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
2056 // now it is a trace from 0 to end
2058 trace_hits_box_a0 = 0;
2059 trace_hits_box_a1 = 1;
2061 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
2063 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
2065 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
2071 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
2073 return trace_hits_box(start, end, thmi - ma, thma - mi);
2078 float cvar_or(string cv, float v)
2080 string s = cvar_string(cv);
2087 // NOTE base is the central value
2088 // freq: circle frequency, = 2*pi*frequency in hertz
2090 // -1 start from the lower value
2091 // 0 start from the base value
2092 // 1 start from the higher value
2094 float blink_synced(float base, float range, float freq, float start_time, int start_pos)
2097 // RMS = sqrt(base^2 + 0.5 * range^2)
2099 // base = sqrt(RMS^2 - 0.5 * range^2)
2102 return base + range * sin((time - start_time - (M_PI / 2) * start_pos) * freq);
2106 float blink(float base, float range, float freq)
2108 return blink_synced(base, range, freq, 0, 0);