1 #include "miscfunctions.qh"
4 #include "command/common.qh"
5 #include "constants.qh"
8 #include "mutators/mutators_include.qh"
10 #include "weapons/accuracy.qh"
11 #include "weapons/csqcprojectile.qh"
12 #include "weapons/selection.qh"
13 #include "../common/command/generic.qh"
14 #include "../common/constants.qh"
15 #include "../common/deathtypes/all.qh"
16 #include "../common/mapinfo.qh"
17 #include "../common/notifications.qh"
18 #include "../common/playerstats.qh"
19 #include "../common/teams.qh"
20 #include "../common/triggers/subs.qh"
21 #include "../common/util.qh"
22 #include "../common/turrets/sv_turrets.qh"
23 #include "../common/weapons/all.qh"
24 #include "../csqcmodellib/sv_model.qh"
25 #include "../warpzonelib/anglestransform.qh"
26 #include "../warpzonelib/server.qh"
28 void crosshair_trace(entity pl)
30 traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
32 void crosshair_trace_plusvisibletriggers(entity pl)
36 first = findchainfloat(solid, SOLID_TRIGGER);
38 for (e = first; e; e = e.chain)
44 for (e = first; e; e = e.chain)
45 e.solid = SOLID_TRIGGER;
47 void WarpZone_crosshair_trace(entity pl)
49 WarpZone_traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
53 string admin_name(void)
55 if(autocvar_sv_adminnick != "")
56 return autocvar_sv_adminnick;
58 return "SERVER ADMIN";
61 void DistributeEvenly_Init(float amount, float totalweight)
63 if (DistributeEvenly_amount)
65 LOG_TRACE("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
66 LOG_TRACE(ftos(DistributeEvenly_totalweight), " left!)\n");
69 DistributeEvenly_amount = 0;
71 DistributeEvenly_amount = amount;
72 DistributeEvenly_totalweight = totalweight;
74 float DistributeEvenly_Get(float weight)
79 f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
80 DistributeEvenly_totalweight -= weight;
81 DistributeEvenly_amount -= f;
84 float DistributeEvenly_GetRandomized(float weight)
89 f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
90 DistributeEvenly_totalweight -= weight;
91 DistributeEvenly_amount -= f;
96 void GameLogEcho(string s)
101 if (autocvar_sv_eventlog_files)
106 matches = autocvar_sv_eventlog_files_counter + 1;
107 cvar_set("sv_eventlog_files_counter", itos(matches));
110 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
111 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
112 logfile = fopen(fn, FILE_APPEND);
113 fputs(logfile, ":logversion:3\n");
117 if (autocvar_sv_eventlog_files_timestamps)
118 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
120 fputs(logfile, strcat(s, "\n"));
123 if (autocvar_sv_eventlog_console)
132 // will be opened later
137 if (logfile_open && logfile >= 0)
144 entity findnearest(vector point, .string field, string value, vector axismod)
155 localhead = find(world, field, value);
158 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
159 dist = localhead.oldorigin;
161 dist = localhead.origin;
163 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
166 for (i = 0; i < num_nearest; ++i)
168 if (len < nearest_length[i])
172 // now i tells us where to insert at
173 // INSERTION SORT! YOU'VE SEEN IT! RUN!
174 if (i < NUM_NEAREST_ENTITIES)
176 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
178 nearest_length[j + 1] = nearest_length[j];
179 nearest_entity[j + 1] = nearest_entity[j];
181 nearest_length[i] = len;
182 nearest_entity[i] = localhead;
183 if (num_nearest < NUM_NEAREST_ENTITIES)
184 num_nearest = num_nearest + 1;
187 localhead = find(localhead, field, value);
190 // now use the first one from our list that we can see
191 for (i = 0; i < num_nearest; ++i)
193 traceline(point, nearest_entity[i].origin, true, world);
194 if (trace_fraction == 1)
198 LOG_TRACE("Nearest point (");
199 LOG_TRACE(nearest_entity[0].netname);
200 LOG_TRACE(") is not visible, using a visible one.\n");
202 return nearest_entity[i];
206 if (num_nearest == 0)
209 LOG_TRACE("Not seeing any location point, using nearest as fallback.\n");
211 dprint("Candidates were: ");
212 for(j = 0; j < num_nearest; ++j)
216 dprint(nearest_entity[j].netname);
221 return nearest_entity[0];
224 string NearestLocation(vector p)
229 loc = findnearest(p, classname, "target_location", '1 1 1');
236 loc = findnearest(p, target, "###item###", '1 1 4');
243 string formatmessage(string msg)
255 ammoitems = "batteries";
256 if(self.items & ITEM_Plasma.m_itemid) ammoitems = ITEM_Plasma.m_name;
257 if(self.items & ITEM_Cells.m_itemid) ammoitems = ITEM_Cells.m_name;
258 if(self.items & ITEM_Rockets.m_itemid) ammoitems = ITEM_Rockets.m_name;
259 if(self.items & ITEM_Shells.m_itemid) ammoitems = ITEM_Shells.m_name;
261 WarpZone_crosshair_trace(self);
262 cursor = trace_endpos;
263 cursor_ent = trace_ent;
267 break; // too many replacements
270 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
271 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
284 replacement = substring(msg, p, 2);
285 escape = substring(msg, p + 1, 1);
289 case "%": replacement = "%"; break;
290 case "\\":replacement = "\\"; break;
291 case "n": replacement = "\n"; break;
292 case "a": replacement = ftos(floor(self.armorvalue)); break;
293 case "h": replacement = ftos(floor(self.health)); break;
294 case "l": replacement = NearestLocation(self.origin); break;
295 case "y": replacement = NearestLocation(cursor); break;
296 case "d": replacement = NearestLocation(self.death_origin); break;
297 case "w": replacement = WEP_NAME((!self.weapon) ? (!self.switchweapon ? self.cnt : self.switchweapon) : self.weapon); break;
298 case "W": replacement = ammoitems; break;
299 case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
300 case "s": replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); break;
301 case "S": replacement = ftos(vlen(self.velocity)); break;
302 case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break;
303 case "T": replacement = seconds_tostring(floor(time - game_starttime)); break;
306 MUTATOR_CALLHOOK(FormatMessage, escape, replacement, msg);
307 escape = format_escape;
308 replacement = format_replacement;
313 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
314 p = p + strlen(replacement);
325 >0: receives a cvar from name=argv(f) value=argv(f+1)
327 void GetCvars_handleString(string thisname, float f, .string field, string name)
332 strunzone(self.(field));
333 self.(field) = string_null;
337 if (thisname == name)
340 strunzone(self.(field));
341 self.(field) = strzone(argv(f + 1));
345 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
347 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
349 GetCvars_handleString(thisname, f, field, name);
350 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
351 if (thisname == name)
353 string s = func(strcat1(self.(field)));
354 if (s != self.(field))
356 strunzone(self.(field));
357 self.(field) = strzone(s);
361 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
368 if (thisname == name)
369 self.(field) = stof(argv(f + 1));
372 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
374 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
381 if (thisname == name)
385 self.(field) = stof(argv(f + 1));
394 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
397 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
400 o = W_FixWeaponOrder_ForceComplete(wo);
401 if(self.weaponorder_byimpulse)
403 strunzone(self.weaponorder_byimpulse);
404 self.weaponorder_byimpulse = string_null;
406 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
409 void GetCvars(float f)
411 string s = string_null;
414 s = strcat1(argv(f));
418 MUTATOR_CALLHOOK(GetCvars);
420 Notification_GetCvars();
422 ReplicateVars(this, s, f);
424 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
425 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
426 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
427 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
428 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
429 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
430 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
431 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
432 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
437 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
438 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
439 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
440 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
441 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
442 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
443 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
444 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
445 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
446 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
447 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
448 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
450 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
451 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
453 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
454 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
455 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
456 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
457 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
459 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
462 if (s == "cl_weaponpriority")
463 self.switchweapon = w_getbestweapon(self);
464 if (s == "cl_allow_uidtracking")
465 PlayerStats_GameReport_AddPlayer(self);
469 // decolorizes and team colors the player name when needed
470 string playername(entity p)
473 if (teamplay && !intermission_running && IS_PLAYER(p))
475 t = Team_ColorCode(p.team);
476 return strcat(t, strdecolorize(p.netname));
482 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
484 int i = weaponinfo.weapon;
490 if (g_lms || g_ca || allguns)
492 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
498 d = (i == WEP_SHOTGUN.m_id);
500 d = 0; // weapon is set a few lines later
502 d = !(!weaponinfo.weaponstart);
504 if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
507 float t = weaponinfo.weaponstartoverride;
509 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
514 // 4: is set by default?
523 void readplayerstartcvars()
529 // initialize starting values for players
530 start_weapons = '0 0 0';
531 start_weapons_default = '0 0 0';
532 start_weapons_defaultmask = '0 0 0';
534 start_ammo_shells = 0;
535 start_ammo_nails = 0;
536 start_ammo_rockets = 0;
537 start_ammo_cells = 0;
538 start_ammo_plasma = 0;
539 start_health = cvar("g_balance_health_start");
540 start_armorvalue = cvar("g_balance_armor_start");
543 g_weaponarena_weapons = '0 0 0';
545 s = cvar_string("g_weaponarena");
546 if (s == "0" || s == "")
548 if(g_ca || g_freezetag)
552 if (s == "0" || s == "")
558 // forcibly turn off weaponarena
560 else if (s == "all" || s == "1")
563 g_weaponarena_list = "All Weapons";
564 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
566 e = get_weaponinfo(j);
567 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
568 g_weaponarena_weapons |= WepSet_FromWeapon(j);
571 else if (s == "most")
574 g_weaponarena_list = "Most Weapons";
575 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
577 e = get_weaponinfo(j);
578 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
579 if (e.spawnflags & WEP_FLAG_NORMAL)
580 g_weaponarena_weapons |= WepSet_FromWeapon(j);
583 else if (s == "none")
586 g_weaponarena_list = "No Weapons";
591 t = tokenize_console(s);
592 g_weaponarena_list = "";
593 for (i = 0; i < t; ++i)
596 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
598 e = get_weaponinfo(j);
601 g_weaponarena_weapons |= WepSet_FromWeapon(j);
602 g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
608 LOG_INFO("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
611 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
615 g_weaponarena_random = cvar("g_weaponarena_random");
617 g_weaponarena_random = 0;
618 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
622 g_weapon_stay = 0; // incompatible
623 start_weapons = g_weaponarena_weapons;
624 start_items |= IT_UNLIMITED_AMMO;
628 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
630 e = get_weaponinfo(i);
631 int w = want_weapon(e, false);
633 start_weapons |= WepSet_FromWeapon(i);
635 start_weapons_default |= WepSet_FromWeapon(i);
637 start_weapons_defaultmask |= WepSet_FromWeapon(i);
641 if(!cvar("g_use_ammunition"))
642 start_items |= IT_UNLIMITED_AMMO;
644 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
646 start_ammo_shells = 999;
647 start_ammo_nails = 999;
648 start_ammo_rockets = 999;
649 start_ammo_cells = 999;
650 start_ammo_plasma = 999;
651 start_ammo_fuel = 999;
655 start_ammo_shells = cvar("g_start_ammo_shells");
656 start_ammo_nails = cvar("g_start_ammo_nails");
657 start_ammo_rockets = cvar("g_start_ammo_rockets");
658 start_ammo_cells = cvar("g_start_ammo_cells");
659 start_ammo_plasma = cvar("g_start_ammo_plasma");
660 start_ammo_fuel = cvar("g_start_ammo_fuel");
665 warmup_start_ammo_shells = start_ammo_shells;
666 warmup_start_ammo_nails = start_ammo_nails;
667 warmup_start_ammo_rockets = start_ammo_rockets;
668 warmup_start_ammo_cells = start_ammo_cells;
669 warmup_start_ammo_plasma = start_ammo_plasma;
670 warmup_start_ammo_fuel = start_ammo_fuel;
671 warmup_start_health = start_health;
672 warmup_start_armorvalue = start_armorvalue;
673 warmup_start_weapons = start_weapons;
674 warmup_start_weapons_default = start_weapons_default;
675 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
677 if (!g_weaponarena && !g_ca && !g_freezetag)
679 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
680 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
681 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
682 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
683 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
684 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
685 warmup_start_health = cvar("g_warmup_start_health");
686 warmup_start_armorvalue = cvar("g_warmup_start_armor");
687 warmup_start_weapons = '0 0 0';
688 warmup_start_weapons_default = '0 0 0';
689 warmup_start_weapons_defaultmask = '0 0 0';
690 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
692 e = get_weaponinfo(i);
693 int w = want_weapon(e, g_warmup_allguns);
695 warmup_start_weapons |= WepSet_FromWeapon(i);
697 warmup_start_weapons_default |= WepSet_FromWeapon(i);
699 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
705 start_items |= ITEM_Jetpack.m_itemid;
707 MUTATOR_CALLHOOK(SetStartItems);
709 if (start_items & ITEM_Jetpack.m_itemid)
711 start_items |= ITEM_JetpackRegen.m_itemid;
712 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
713 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
716 WepSet precache_weapons = start_weapons;
717 if (g_warmup_allguns != 1)
718 precache_weapons |= warmup_start_weapons;
719 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
721 e = get_weaponinfo(i);
722 if(precache_weapons & WepSet_FromWeapon(i)) {
723 Weapon w = get_weaponinfo(i);
728 start_ammo_shells = max(0, start_ammo_shells);
729 start_ammo_nails = max(0, start_ammo_nails);
730 start_ammo_rockets = max(0, start_ammo_rockets);
731 start_ammo_cells = max(0, start_ammo_cells);
732 start_ammo_plasma = max(0, start_ammo_plasma);
733 start_ammo_fuel = max(0, start_ammo_fuel);
735 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
736 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
737 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
738 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
739 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
740 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
743 float sound_allowed(float destin, entity e)
745 // sounds from world may always pass
748 if (e.classname == "body")
750 else if (e.realowner && e.realowner != e)
752 else if (e.owner && e.owner != e)
757 // sounds to self may always pass
758 if (destin == MSG_ONE)
761 // sounds by players can be removed
762 if (autocvar_bot_sound_monopoly)
763 if (IS_REAL_CLIENT(e))
765 // anything else may pass
769 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
773 if (!sound_allowed(_dest, e))
776 entno = num_for_edict(e);
777 idx = precache_sound_index(samp);
782 attenu = floor(attenu * 64);
783 vol = floor(vol * 255);
786 sflags |= SND_VOLUME;
788 sflags |= SND_ATTENUATION;
789 if (entno >= 8192 || chan < 0 || chan > 7)
790 sflags |= SND_LARGEENTITY;
792 sflags |= SND_LARGESOUND;
794 WriteByte(_dest, SVC_SOUND);
795 WriteByte(_dest, sflags);
796 if (sflags & SND_VOLUME)
797 WriteByte(_dest, vol);
798 if (sflags & SND_ATTENUATION)
799 WriteByte(_dest, attenu);
800 if (sflags & SND_LARGEENTITY)
802 WriteShort(_dest, entno);
803 WriteByte(_dest, chan);
807 WriteShort(_dest, entno * 8 + chan);
809 if (sflags & SND_LARGESOUND)
810 WriteShort(_dest, idx);
812 WriteByte(_dest, idx);
814 WriteCoord(_dest, o.x);
815 WriteCoord(_dest, o.y);
816 WriteCoord(_dest, o.z);
818 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
822 if (!sound_allowed(_dest, e))
825 o = e.origin + 0.5 * (e.mins + e.maxs);
826 soundtoat(_dest, e, o, chan, samp, vol, _atten);
828 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
830 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
832 void stopsoundto(float _dest, entity e, float chan)
836 if (!sound_allowed(_dest, e))
839 entno = num_for_edict(e);
841 if (entno >= 8192 || chan < 0 || chan > 7)
844 idx = precache_sound_index(SND(Null));
845 sflags = SND_LARGEENTITY;
847 sflags |= SND_LARGESOUND;
848 WriteByte(_dest, SVC_SOUND);
849 WriteByte(_dest, sflags);
850 WriteShort(_dest, entno);
851 WriteByte(_dest, chan);
852 if (sflags & SND_LARGESOUND)
853 WriteShort(_dest, idx);
855 WriteByte(_dest, idx);
856 WriteCoord(_dest, e.origin.x);
857 WriteCoord(_dest, e.origin.y);
858 WriteCoord(_dest, e.origin.z);
862 WriteByte(_dest, SVC_STOPSOUND);
863 WriteShort(_dest, entno * 8 + chan);
866 void stopsound(entity e, float chan)
868 if (!sound_allowed(MSG_BROADCAST, e))
871 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
872 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
875 void play2(entity e, string filename)
877 //stuffcmd(e, strcat("play2 ", filename, "\n"));
879 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
882 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
884 float spamsound(entity e, float chan, string samp, float vol, float _atten)
886 if (!sound_allowed(MSG_BROADCAST, e))
889 if (time > e.spamtime)
892 _sound(e, chan, samp, vol, _atten);
898 void play2team(float t, string filename)
902 if (autocvar_bot_sound_monopoly)
905 FOR_EACH_REALPLAYER(head)
908 play2(head, filename);
912 void play2all(string samp)
914 if (autocvar_bot_sound_monopoly)
917 _sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
920 void PrecachePlayerSounds(string f);
921 void precache_playermodel(string m)
923 float globhandle, i, n;
926 if(substring(m, -9,5) == "_lod1")
928 if(substring(m, -9,5) == "_lod2")
931 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
934 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
938 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
941 n = search_getsize(globhandle);
942 for (i = 0; i < n; ++i)
944 //print(search_getfilename(globhandle, i), "\n");
945 f = search_getfilename(globhandle, i);
946 PrecachePlayerSounds(f);
948 search_end(globhandle);
950 void precache_all_playermodels(string pattern)
952 float globhandle, i, n;
955 globhandle = search_begin(pattern, true, false);
958 n = search_getsize(globhandle);
959 for (i = 0; i < n; ++i)
961 //print(search_getfilename(globhandle, i), "\n");
962 f = search_getfilename(globhandle, i);
963 precache_playermodel(f);
965 search_end(globhandle);
970 // gamemode related things
972 // Precache all player models if desired
973 if (autocvar_sv_precacheplayermodels)
975 PrecachePlayerSounds("sound/player/default.sounds");
976 precache_all_playermodels("models/player/*.zym");
977 precache_all_playermodels("models/player/*.dpm");
978 precache_all_playermodels("models/player/*.md3");
979 precache_all_playermodels("models/player/*.psk");
980 precache_all_playermodels("models/player/*.iqm");
983 if (autocvar_sv_defaultcharacter)
986 s = autocvar_sv_defaultplayermodel_red;
988 precache_playermodel(s);
989 s = autocvar_sv_defaultplayermodel_blue;
991 precache_playermodel(s);
992 s = autocvar_sv_defaultplayermodel_yellow;
994 precache_playermodel(s);
995 s = autocvar_sv_defaultplayermodel_pink;
997 precache_playermodel(s);
998 s = autocvar_sv_defaultplayermodel;
1000 precache_playermodel(s);
1005 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1006 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1009 // gore and miscellaneous sounds
1010 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1011 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1014 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1016 if (!self.noise && self.music) // quake 3 uses the music field
1017 self.noise = self.music;
1019 // plays music for the level if there is any
1022 precache_sound (self.noise);
1023 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1029 void make_safe_for_remove(entity e)
1031 if (e.initialize_entity)
1033 entity ent, prev = world;
1034 for (ent = initialize_entity_first; ent; )
1036 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1038 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1039 // skip it in linked list
1042 prev.initialize_entity_next = ent.initialize_entity_next;
1043 ent = prev.initialize_entity_next;
1047 initialize_entity_first = ent.initialize_entity_next;
1048 ent = initialize_entity_first;
1054 ent = ent.initialize_entity_next;
1060 void objerror(string s)
1062 make_safe_for_remove(self);
1063 builtin_objerror(s);
1066 .float remove_except_protected_forbidden;
1067 void remove_except_protected(entity e)
1069 if(e.remove_except_protected_forbidden)
1070 error("not allowed to remove this at this point");
1074 void remove_unsafely(entity e)
1076 if(e.classname == "spike")
1077 error("Removing spikes is forbidden (crylink bug), please report");
1081 void remove_safely(entity e)
1083 make_safe_for_remove(e);
1087 void InitializeEntity(entity e, void(void) func, float order)
1091 if (!e || e.initialize_entity)
1093 // make a proxy initializer entity
1095 e = new(initialize_entity);
1099 e.initialize_entity = func;
1100 e.initialize_entity_order = order;
1102 cur = initialize_entity_first;
1106 if (!cur || cur.initialize_entity_order > order)
1108 // insert between prev and cur
1110 prev.initialize_entity_next = e;
1112 initialize_entity_first = e;
1113 e.initialize_entity_next = cur;
1117 cur = cur.initialize_entity_next;
1120 void InitializeEntitiesRun()
1122 entity startoflist = initialize_entity_first;
1123 initialize_entity_first = NULL;
1124 remove = remove_except_protected;
1125 for (entity e = startoflist; e; e = e.initialize_entity_next)
1127 e.remove_except_protected_forbidden = 1;
1129 for (entity e = startoflist; e; )
1131 e.remove_except_protected_forbidden = 0;
1132 e.initialize_entity_order = 0;
1133 entity next = e.initialize_entity_next;
1134 e.initialize_entity_next = NULL;
1135 var void() func = e.initialize_entity;
1136 e.initialize_entity = func_null;
1137 if (e.classname == "initialize_entity")
1139 entity wrappee = e.enemy;
1143 //dprint("Delayed initialization: ", e.classname, "\n");
1146 WITH(entity, self, e, func());
1151 backtrace(strcat("Null function in: ", e.classname, "\n"));
1155 remove = remove_unsafely;
1158 .float(entity) isEliminated;
1159 bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags)
1163 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1164 WriteByte(MSG_ENTITY, sendflags);
1168 for(i = 1; i <= maxclients; i += 8)
1170 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1172 if(eliminatedPlayers.isEliminated(e))
1175 WriteByte(MSG_ENTITY, f);
1182 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1184 if(eliminatedPlayers)
1186 backtrace("Can't spawn eliminatedPlayers again!");
1189 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1190 eliminatedPlayers.isEliminated = isEliminated_func;
1194 void adaptor_think2touch()
1203 void adaptor_think2use()
1215 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1217 if(!(self.flags & FL_ONGROUND)) // if onground, we ARE touching something, but HITTYPE_SPLASH is to be networked if the damage causing projectile is not touching ANYTHING
1218 self.projectiledeathtype |= HITTYPE_SPLASH;
1219 adaptor_think2use();
1222 // deferred dropping
1223 void DropToFloor_Handler()
1225 builtin_droptofloor();
1226 self.dropped_origin = self.origin;
1231 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1236 float trace_hits_box_a0, trace_hits_box_a1;
1238 float trace_hits_box_1d(float end, float thmi, float thma)
1242 // just check if x is in range
1250 // do the trace with respect to x
1251 // 0 -> end has to stay in thmi -> thma
1252 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1253 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1254 if (trace_hits_box_a0 > trace_hits_box_a1)
1260 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1265 // now it is a trace from 0 to end
1267 trace_hits_box_a0 = 0;
1268 trace_hits_box_a1 = 1;
1270 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1272 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1274 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1280 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1282 return trace_hits_box(start, end, thmi - ma, thma - mi);
1285 float SUB_NoImpactCheck()
1287 // zero hitcontents = this is not the real impact, but either the
1288 // mirror-impact of something hitting the projectile instead of the
1289 // projectile hitting the something, or a touchareagrid one. Neither of
1290 // these stop the projectile from moving, so...
1291 if(trace_dphitcontents == 0)
1293 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1294 LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Profectile will self-destruct. (edict: %d, classname: %s, origin: %s)\n", num_for_edict(self), self.classname, vtos(self.origin));
1297 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1299 if (other == world && self.size != '0 0 0')
1302 tic = self.velocity * sys_frametime;
1303 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1304 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1305 if (trace_fraction >= 1)
1307 LOG_TRACE("Odd... did not hit...?\n");
1309 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1311 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1319 #define SUB_OwnerCheck() (other && (other == self.owner))
1321 void W_Crylink_Dequeue(entity e);
1322 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1324 if(SUB_OwnerCheck())
1326 if(SUB_NoImpactCheck())
1328 if(self.classname == "nade")
1329 return false; // no checks here
1330 else if(self.classname == "grapplinghook")
1331 RemoveGrapplingHook(self.realowner);
1332 else if(self.classname == "spike")
1334 W_Crylink_Dequeue(self);
1341 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1342 UpdateCSQCProjectile(self);
1347 void URI_Get_Callback(float id, float status, string data)
1349 if(url_URI_Get_Callback(id, status, data))
1353 else if (id == URI_GET_DISCARD)
1357 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1360 Curl_URI_Get_Callback(id, status, data);
1362 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1365 OnlineBanList_URI_Get_Callback(id, status, data);
1369 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1373 string uid2name(string myuid) {
1375 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1377 // FIXME remove this later after 0.6 release
1378 // convert old style broken records to correct style
1381 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1384 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1385 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1390 s = "^1Unregistered Player";
1394 float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1397 vector start, org, delta, end, enddown, mstart;
1400 m = e.dphitcontentsmask;
1401 e.dphitcontentsmask = goodcontents | badcontents;
1404 delta = boundmax - boundmin;
1408 for (i = 0; i < attempts; ++i)
1410 start.x = org.x + random() * delta.x;
1411 start.y = org.y + random() * delta.y;
1412 start.z = org.z + random() * delta.z;
1414 // rule 1: start inside world bounds, and outside
1415 // solid, and don't start from somewhere where you can
1416 // fall down to evil
1417 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1418 if (trace_fraction >= 1)
1420 if (trace_startsolid)
1422 if (trace_dphitcontents & badcontents)
1424 if (trace_dphitq3surfaceflags & badsurfaceflags)
1427 // rule 2: if we are too high, lower the point
1428 if (trace_fraction * delta.z > maxaboveground)
1429 start = trace_endpos + '0 0 1' * maxaboveground;
1430 enddown = trace_endpos;
1432 // rule 3: make sure we aren't outside the map. This only works
1433 // for somewhat well formed maps. A good rule of thumb is that
1434 // the map should have a convex outside hull.
1435 // these can be traceLINES as we already verified the starting box
1436 mstart = start + 0.5 * (e.mins + e.maxs);
1437 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1438 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1440 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1441 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1443 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1444 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1446 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1447 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1449 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1450 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1453 // rule 4: we must "see" some spawnpoint or item
1454 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1455 if(checkpvs(mstart, sp))
1456 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1460 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1461 if(checkpvs(mstart, sp))
1462 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1468 // find a random vector to "look at"
1469 end.x = org.x + random() * delta.x;
1470 end.y = org.y + random() * delta.y;
1471 end.z = org.z + random() * delta.z;
1472 end = start + normalize(end - start) * vlen(delta);
1474 // rule 4: start TO end must not be too short
1475 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1476 if (trace_startsolid)
1478 if (trace_fraction < minviewdistance / vlen(delta))
1481 // rule 5: don't want to look at sky
1482 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1485 // rule 6: we must not end up in trigger_hurt
1486 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1492 e.dphitcontentsmask = m;
1496 setorigin(e, start);
1497 e.angles = vectoangles(end - start);
1498 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1505 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1507 return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
1510 void write_recordmarker(entity pl, float tstart, float dt)
1512 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1514 // also write a marker into demo files for demotc-race-record-extractor to find
1517 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1518 " ", ftos(tstart), " ", ftos(dt), "\n"));
1521 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1534 if(allowcenter) // 2: allow center handedness
1547 if(allowcenter) // 2: allow center handedness
1563 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1568 if (autocvar_g_shootfromeye)
1572 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1573 else { vecs.y = 0; vecs.z -= 2; }
1581 else if (autocvar_g_shootfromcenter)
1586 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1596 else if (autocvar_g_shootfromclient)
1598 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1603 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1605 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1609 void attach_sameorigin(entity e, entity to, string tag)
1611 vector org, t_forward, t_left, t_up, e_forward, e_up;
1614 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1615 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1616 t_forward = v_forward * tagscale;
1617 t_left = v_right * -tagscale;
1618 t_up = v_up * tagscale;
1620 e.origin_x = org * t_forward;
1621 e.origin_y = org * t_left;
1622 e.origin_z = org * t_up;
1624 // current forward and up directions
1625 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1626 e.angles = AnglesTransform_FromVAngles(e.angles);
1628 e.angles = AnglesTransform_FromAngles(e.angles);
1629 fixedmakevectors(e.angles);
1631 // untransform forward, up!
1632 e_forward.x = v_forward * t_forward;
1633 e_forward.y = v_forward * t_left;
1634 e_forward.z = v_forward * t_up;
1635 e_up.x = v_up * t_forward;
1636 e_up.y = v_up * t_left;
1637 e_up.z = v_up * t_up;
1639 e.angles = fixedvectoangles2(e_forward, e_up);
1640 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1641 e.angles = AnglesTransform_ToVAngles(e.angles);
1643 e.angles = AnglesTransform_ToAngles(e.angles);
1645 setattachment(e, to, tag);
1646 setorigin(e, e.origin);
1649 void detach_sameorigin(entity e)
1652 org = gettaginfo(e, 0);
1653 e.angles = fixedvectoangles2(v_forward, v_up);
1654 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1655 e.angles = AnglesTransform_ToVAngles(e.angles);
1657 e.angles = AnglesTransform_ToAngles(e.angles);
1659 setattachment(e, world, "");
1660 setorigin(e, e.origin);
1663 void follow_sameorigin(entity e, entity to)
1665 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1666 e.aiment = to; // make the hole follow bmodel
1667 e.punchangle = to.angles; // the original angles of bmodel
1668 e.view_ofs = e.origin - to.origin; // relative origin
1669 e.v_angle = e.angles - to.angles; // relative angles
1672 void unfollow_sameorigin(entity e)
1674 e.movetype = MOVETYPE_NONE;
1677 entity gettaginfo_relative_ent;
1678 vector gettaginfo_relative(entity e, float tag)
1680 if (!gettaginfo_relative_ent)
1682 gettaginfo_relative_ent = spawn();
1683 gettaginfo_relative_ent.effects = EF_NODRAW;
1685 gettaginfo_relative_ent.model = e.model;
1686 gettaginfo_relative_ent.modelindex = e.modelindex;
1687 gettaginfo_relative_ent.frame = e.frame;
1688 return gettaginfo(gettaginfo_relative_ent, tag);
1693 bool modeleffect_SendEntity(entity this, entity to, int sf)
1696 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1699 if(self.velocity != '0 0 0')
1701 if(self.angles != '0 0 0')
1703 if(self.avelocity != '0 0 0')
1706 WriteByte(MSG_ENTITY, f);
1707 WriteShort(MSG_ENTITY, self.modelindex);
1708 WriteByte(MSG_ENTITY, self.skin);
1709 WriteByte(MSG_ENTITY, self.frame);
1710 WriteCoord(MSG_ENTITY, self.origin.x);
1711 WriteCoord(MSG_ENTITY, self.origin.y);
1712 WriteCoord(MSG_ENTITY, self.origin.z);
1715 WriteCoord(MSG_ENTITY, self.velocity.x);
1716 WriteCoord(MSG_ENTITY, self.velocity.y);
1717 WriteCoord(MSG_ENTITY, self.velocity.z);
1721 WriteCoord(MSG_ENTITY, self.angles.x);
1722 WriteCoord(MSG_ENTITY, self.angles.y);
1723 WriteCoord(MSG_ENTITY, self.angles.z);
1727 WriteCoord(MSG_ENTITY, self.avelocity.x);
1728 WriteCoord(MSG_ENTITY, self.avelocity.y);
1729 WriteCoord(MSG_ENTITY, self.avelocity.z);
1731 WriteShort(MSG_ENTITY, self.scale * 256.0);
1732 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1733 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1734 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1735 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1740 void modeleffect_spawn(string m, float s, float f, vector o, vector v, vector ang, vector angv, float s0, float s2, float a, float t1, float t2)
1745 e.classname = "modeleffect";
1753 e.teleport_time = t1;
1757 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1761 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1764 sz = max(e.scale, e.scale2);
1765 setsize(e, e.mins * sz, e.maxs * sz);
1766 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1769 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1771 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1774 float randombit(float bits)
1776 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1785 for(f = 1; f <= bits; f *= 2)
1794 r = (r - 1) / (n - 1);
1801 float randombits(float bits, float k, float error_return)
1805 while(k > 0 && bits != r)
1807 r += randombit(bits - r);
1816 void randombit_test(float bits, float iter)
1820 LOG_INFO(ftos(randombit(bits)), "\n");
1825 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1827 if(halflifedist > 0)
1828 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1829 else if(halflifedist < 0)
1830 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1836 .string aiment_classname;
1837 .float aiment_deadflag;
1838 void SetMovetypeFollow(entity ent, entity e)
1840 // FIXME this may not be warpzone aware
1841 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1842 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.
1843 ent.aiment = e; // make the hole follow bmodel
1844 ent.punchangle = e.angles; // the original angles of bmodel
1845 ent.view_ofs = ent.origin - e.origin; // relative origin
1846 ent.v_angle = ent.angles - e.angles; // relative angles
1847 ent.aiment_classname = strzone(e.classname);
1848 ent.aiment_deadflag = e.deadflag;
1850 void UnsetMovetypeFollow(entity ent)
1852 ent.movetype = MOVETYPE_FLY;
1853 PROJECTILE_MAKETRIGGER(ent);
1856 float LostMovetypeFollow(entity ent)
1859 if(ent.movetype != MOVETYPE_FOLLOW)
1865 if(ent.aiment.classname != ent.aiment_classname)
1867 if(ent.aiment.deadflag != ent.aiment_deadflag)
1873 float isPushable(entity e)
1884 case "droppedweapon":
1885 case "keepawayball":
1886 case "nexball_basketball":
1887 case "nexball_football":
1889 case "bullet": // antilagged bullets can't hit this either
1892 if (e.projectiledeathtype)