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.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;
304 MUTATOR_CALLHOOK(FormatMessage, escape, replacement, msg);
305 escape = format_escape;
306 replacement = format_replacement;
311 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
312 p = p + strlen(replacement);
323 >0: receives a cvar from name=argv(f) value=argv(f+1)
325 void GetCvars_handleString(string thisname, float f, .string field, string name)
330 strunzone(self.(field));
331 self.(field) = string_null;
335 if (thisname == name)
338 strunzone(self.(field));
339 self.(field) = strzone(argv(f + 1));
343 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
345 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
347 GetCvars_handleString(thisname, f, field, name);
348 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
349 if (thisname == name)
351 string s = func(strcat1(self.(field)));
352 if (s != self.(field))
354 strunzone(self.(field));
355 self.(field) = strzone(s);
359 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
366 if (thisname == name)
367 self.(field) = stof(argv(f + 1));
370 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
372 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
379 if (thisname == name)
383 self.(field) = stof(argv(f + 1));
392 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
395 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
398 o = W_FixWeaponOrder_ForceComplete(wo);
399 if(self.weaponorder_byimpulse)
401 strunzone(self.weaponorder_byimpulse);
402 self.weaponorder_byimpulse = string_null;
404 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
407 void GetCvars(float f)
409 string s = string_null;
412 s = strcat1(argv(f));
416 MUTATOR_CALLHOOK(GetCvars);
418 Notification_GetCvars();
420 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
421 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
422 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
423 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
424 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
425 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
426 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
427 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
428 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
429 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
430 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
431 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
432 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
437 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
438 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
439 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
440 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
441 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
442 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
443 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
444 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
446 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
447 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
449 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
450 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
451 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
452 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
453 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
455 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
458 if (s == "cl_weaponpriority")
459 self.switchweapon = w_getbestweapon(self);
460 if (s == "cl_allow_uidtracking")
461 PlayerStats_GameReport_AddPlayer(self);
465 // decolorizes and team colors the player name when needed
466 string playername(entity p)
469 if (teamplay && !intermission_running && IS_PLAYER(p))
471 t = Team_ColorCode(p.team);
472 return strcat(t, strdecolorize(p.netname));
478 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
480 int i = weaponinfo.weapon;
486 if (g_lms || g_ca || allguns)
488 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
494 d = (i == WEP_SHOTGUN.m_id);
496 d = 0; // weapon is set a few lines later
498 d = !(!weaponinfo.weaponstart);
500 if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
501 d |= (i == WEP_HOOK.m_id);
502 if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
505 float t = weaponinfo.weaponstartoverride;
507 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
512 // 4: is set by default?
521 void readplayerstartcvars()
527 // initialize starting values for players
528 start_weapons = '0 0 0';
529 start_weapons_default = '0 0 0';
530 start_weapons_defaultmask = '0 0 0';
532 start_ammo_shells = 0;
533 start_ammo_nails = 0;
534 start_ammo_rockets = 0;
535 start_ammo_cells = 0;
536 start_ammo_plasma = 0;
537 start_health = cvar("g_balance_health_start");
538 start_armorvalue = cvar("g_balance_armor_start");
541 g_weaponarena_weapons = '0 0 0';
543 s = cvar_string("g_weaponarena");
544 if (s == "0" || s == "")
546 if(g_ca || g_freezetag)
550 if (s == "0" || s == "")
556 // forcibly turn off weaponarena
558 else if (s == "all" || s == "1")
561 g_weaponarena_list = "All Weapons";
562 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
564 e = get_weaponinfo(j);
565 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
566 g_weaponarena_weapons |= WepSet_FromWeapon(j);
569 else if (s == "most")
572 g_weaponarena_list = "Most Weapons";
573 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
575 e = get_weaponinfo(j);
576 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
577 if (e.spawnflags & WEP_FLAG_NORMAL)
578 g_weaponarena_weapons |= WepSet_FromWeapon(j);
581 else if (s == "none")
584 g_weaponarena_list = "No Weapons";
589 t = tokenize_console(s);
590 g_weaponarena_list = "";
591 for (i = 0; i < t; ++i)
594 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
596 e = get_weaponinfo(j);
599 g_weaponarena_weapons |= WepSet_FromWeapon(j);
600 g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
606 LOG_INFO("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
609 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
613 g_weaponarena_random = cvar("g_weaponarena_random");
615 g_weaponarena_random = 0;
616 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
620 g_weapon_stay = 0; // incompatible
621 start_weapons = g_weaponarena_weapons;
622 start_items |= IT_UNLIMITED_AMMO;
626 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
628 e = get_weaponinfo(i);
629 int w = want_weapon(e, false);
631 start_weapons |= WepSet_FromWeapon(i);
633 start_weapons_default |= WepSet_FromWeapon(i);
635 start_weapons_defaultmask |= WepSet_FromWeapon(i);
639 if(!cvar("g_use_ammunition"))
640 start_items |= IT_UNLIMITED_AMMO;
642 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
644 start_ammo_shells = 999;
645 start_ammo_nails = 999;
646 start_ammo_rockets = 999;
647 start_ammo_cells = 999;
648 start_ammo_plasma = 999;
649 start_ammo_fuel = 999;
653 start_ammo_shells = cvar("g_start_ammo_shells");
654 start_ammo_nails = cvar("g_start_ammo_nails");
655 start_ammo_rockets = cvar("g_start_ammo_rockets");
656 start_ammo_cells = cvar("g_start_ammo_cells");
657 start_ammo_plasma = cvar("g_start_ammo_plasma");
658 start_ammo_fuel = cvar("g_start_ammo_fuel");
663 warmup_start_ammo_shells = start_ammo_shells;
664 warmup_start_ammo_nails = start_ammo_nails;
665 warmup_start_ammo_rockets = start_ammo_rockets;
666 warmup_start_ammo_cells = start_ammo_cells;
667 warmup_start_ammo_plasma = start_ammo_plasma;
668 warmup_start_ammo_fuel = start_ammo_fuel;
669 warmup_start_health = start_health;
670 warmup_start_armorvalue = start_armorvalue;
671 warmup_start_weapons = start_weapons;
672 warmup_start_weapons_default = start_weapons_default;
673 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
675 if (!g_weaponarena && !g_ca && !g_freezetag)
677 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
678 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
679 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
680 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
681 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
682 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
683 warmup_start_health = cvar("g_warmup_start_health");
684 warmup_start_armorvalue = cvar("g_warmup_start_armor");
685 warmup_start_weapons = '0 0 0';
686 warmup_start_weapons_default = '0 0 0';
687 warmup_start_weapons_defaultmask = '0 0 0';
688 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
690 e = get_weaponinfo(i);
691 int w = want_weapon(e, g_warmup_allguns);
693 warmup_start_weapons |= WepSet_FromWeapon(i);
695 warmup_start_weapons_default |= WepSet_FromWeapon(i);
697 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
703 start_items |= ITEM_Jetpack.m_itemid;
705 MUTATOR_CALLHOOK(SetStartItems);
707 if ((start_items & ITEM_Jetpack.m_itemid) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
709 start_items |= ITEM_JetpackRegen.m_itemid;
710 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
711 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
714 WepSet precache_weapons = start_weapons;
715 if (g_warmup_allguns != 1)
716 precache_weapons |= warmup_start_weapons;
717 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
719 e = get_weaponinfo(i);
720 if(precache_weapons & WepSet_FromWeapon(i))
721 WEP_ACTION(i, WR_INIT);
724 start_ammo_shells = max(0, start_ammo_shells);
725 start_ammo_nails = max(0, start_ammo_nails);
726 start_ammo_rockets = max(0, start_ammo_rockets);
727 start_ammo_cells = max(0, start_ammo_cells);
728 start_ammo_plasma = max(0, start_ammo_plasma);
729 start_ammo_fuel = max(0, start_ammo_fuel);
731 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
732 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
733 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
734 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
735 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
736 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
739 float sound_allowed(float destin, entity e)
741 // sounds from world may always pass
744 if (e.classname == "body")
746 else if (e.realowner && e.realowner != e)
748 else if (e.owner && e.owner != e)
753 // sounds to self may always pass
754 if (destin == MSG_ONE)
757 // sounds by players can be removed
758 if (autocvar_bot_sound_monopoly)
759 if (IS_REAL_CLIENT(e))
761 // anything else may pass
766 void sound(entity e, float chan, string samp, float vol, float attenu)
768 if (!sound_allowed(MSG_BROADCAST, e))
770 sound7(e, chan, samp, vol, attenu, 0, 0);
773 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
777 if (!sound_allowed(_dest, e))
780 entno = num_for_edict(e);
781 idx = precache_sound_index(samp);
786 attenu = floor(attenu * 64);
787 vol = floor(vol * 255);
790 sflags |= SND_VOLUME;
792 sflags |= SND_ATTENUATION;
793 if (entno >= 8192 || chan < 0 || chan > 7)
794 sflags |= SND_LARGEENTITY;
796 sflags |= SND_LARGESOUND;
798 WriteByte(_dest, SVC_SOUND);
799 WriteByte(_dest, sflags);
800 if (sflags & SND_VOLUME)
801 WriteByte(_dest, vol);
802 if (sflags & SND_ATTENUATION)
803 WriteByte(_dest, attenu);
804 if (sflags & SND_LARGEENTITY)
806 WriteShort(_dest, entno);
807 WriteByte(_dest, chan);
811 WriteShort(_dest, entno * 8 + chan);
813 if (sflags & SND_LARGESOUND)
814 WriteShort(_dest, idx);
816 WriteByte(_dest, idx);
818 WriteCoord(_dest, o.x);
819 WriteCoord(_dest, o.y);
820 WriteCoord(_dest, o.z);
822 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
826 if (!sound_allowed(_dest, e))
829 o = e.origin + 0.5 * (e.mins + e.maxs);
830 soundtoat(_dest, e, o, chan, samp, vol, _atten);
832 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
834 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
836 void stopsoundto(float _dest, entity e, float chan)
840 if (!sound_allowed(_dest, e))
843 entno = num_for_edict(e);
845 if (entno >= 8192 || chan < 0 || chan > 7)
848 idx = precache_sound_index("misc/null.wav");
849 sflags = SND_LARGEENTITY;
851 sflags |= SND_LARGESOUND;
852 WriteByte(_dest, SVC_SOUND);
853 WriteByte(_dest, sflags);
854 WriteShort(_dest, entno);
855 WriteByte(_dest, chan);
856 if (sflags & SND_LARGESOUND)
857 WriteShort(_dest, idx);
859 WriteByte(_dest, idx);
860 WriteCoord(_dest, e.origin.x);
861 WriteCoord(_dest, e.origin.y);
862 WriteCoord(_dest, e.origin.z);
866 WriteByte(_dest, SVC_STOPSOUND);
867 WriteShort(_dest, entno * 8 + chan);
870 void stopsound(entity e, float chan)
872 if (!sound_allowed(MSG_BROADCAST, e))
875 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
876 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
879 void play2(entity e, string filename)
881 //stuffcmd(e, strcat("play2 ", filename, "\n"));
883 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
886 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
888 float spamsound(entity e, float chan, string samp, float vol, float _atten)
890 if (!sound_allowed(MSG_BROADCAST, e))
893 if (time > e.spamtime)
896 sound(e, chan, samp, vol, _atten);
902 void play2team(float t, string filename)
906 if (autocvar_bot_sound_monopoly)
909 FOR_EACH_REALPLAYER(head)
912 play2(head, filename);
916 void play2all(string samp)
918 if (autocvar_bot_sound_monopoly)
921 sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
924 void PrecachePlayerSounds(string f);
925 void precache_playermodel(string m)
927 float globhandle, i, n;
930 if(substring(m, -9,5) == "_lod1")
932 if(substring(m, -9,5) == "_lod2")
935 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
938 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
942 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
945 n = search_getsize(globhandle);
946 for (i = 0; i < n; ++i)
948 //print(search_getfilename(globhandle, i), "\n");
949 f = search_getfilename(globhandle, i);
950 PrecachePlayerSounds(f);
952 search_end(globhandle);
954 void precache_all_playermodels(string pattern)
956 float globhandle, i, n;
959 globhandle = search_begin(pattern, true, false);
962 n = search_getsize(globhandle);
963 for (i = 0; i < n; ++i)
965 //print(search_getfilename(globhandle, i), "\n");
966 f = search_getfilename(globhandle, i);
967 precache_playermodel(f);
969 search_end(globhandle);
974 // gamemode related things
976 // Precache all player models if desired
977 if (autocvar_sv_precacheplayermodels)
979 PrecachePlayerSounds("sound/player/default.sounds");
980 precache_all_playermodels("models/player/*.zym");
981 precache_all_playermodels("models/player/*.dpm");
982 precache_all_playermodels("models/player/*.md3");
983 precache_all_playermodels("models/player/*.psk");
984 precache_all_playermodels("models/player/*.iqm");
987 if (autocvar_sv_defaultcharacter)
990 s = autocvar_sv_defaultplayermodel_red;
992 precache_playermodel(s);
993 s = autocvar_sv_defaultplayermodel_blue;
995 precache_playermodel(s);
996 s = autocvar_sv_defaultplayermodel_yellow;
998 precache_playermodel(s);
999 s = autocvar_sv_defaultplayermodel_pink;
1001 precache_playermodel(s);
1002 s = autocvar_sv_defaultplayermodel;
1004 precache_playermodel(s);
1009 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1010 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1013 // gore and miscellaneous sounds
1014 //precache_sound ("misc/h2ohit.wav");
1015 precache_sound ("misc/armorimpact.wav");
1016 precache_sound ("misc/bodyimpact1.wav");
1017 precache_sound ("misc/bodyimpact2.wav");
1018 precache_sound ("misc/gib.wav");
1019 precache_sound ("misc/gib_splat01.wav");
1020 precache_sound ("misc/gib_splat02.wav");
1021 precache_sound ("misc/gib_splat03.wav");
1022 precache_sound ("misc/gib_splat04.wav");
1023 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1024 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1025 precache_sound ("misc/null.wav");
1026 precache_sound ("misc/spawn.wav");
1027 precache_sound ("misc/talk.wav");
1028 precache_sound ("misc/teleport.wav");
1029 precache_sound ("misc/poweroff.wav");
1030 precache_sound ("player/lava.wav");
1031 precache_sound ("player/slime.wav");
1033 // common weapon precaches
1034 precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here
1035 precache_sound (W_Sound("weapon_switch"));
1036 precache_sound (W_Sound("weaponpickup"));
1037 precache_sound (W_Sound("unavailable"));
1038 precache_sound (W_Sound("dryfire"));
1039 if (g_grappling_hook)
1041 precache_sound (W_Sound("hook_fire")); // hook
1042 precache_sound (W_Sound("hook_impact")); // hook
1046 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1048 if (!self.noise && self.music) // quake 3 uses the music field
1049 self.noise = self.music;
1051 // plays music for the level if there is any
1054 precache_sound (self.noise);
1055 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1059 #include "precache-for-csqc.inc"
1063 void make_safe_for_remove(entity e)
1065 if (e.initialize_entity)
1067 entity ent, prev = world;
1068 for (ent = initialize_entity_first; ent; )
1070 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1072 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1073 // skip it in linked list
1076 prev.initialize_entity_next = ent.initialize_entity_next;
1077 ent = prev.initialize_entity_next;
1081 initialize_entity_first = ent.initialize_entity_next;
1082 ent = initialize_entity_first;
1088 ent = ent.initialize_entity_next;
1094 void objerror(string s)
1096 make_safe_for_remove(self);
1097 builtin_objerror(s);
1100 .float remove_except_protected_forbidden;
1101 void remove_except_protected(entity e)
1103 if(e.remove_except_protected_forbidden)
1104 error("not allowed to remove this at this point");
1108 void remove_unsafely(entity e)
1110 if(e.classname == "spike")
1111 error("Removing spikes is forbidden (crylink bug), please report");
1115 void remove_safely(entity e)
1117 make_safe_for_remove(e);
1121 void InitializeEntity(entity e, void(void) func, float order)
1125 if (!e || e.initialize_entity)
1127 // make a proxy initializer entity
1129 e = new(initialize_entity);
1133 e.initialize_entity = func;
1134 e.initialize_entity_order = order;
1136 cur = initialize_entity_first;
1140 if (!cur || cur.initialize_entity_order > order)
1142 // insert between prev and cur
1144 prev.initialize_entity_next = e;
1146 initialize_entity_first = e;
1147 e.initialize_entity_next = cur;
1151 cur = cur.initialize_entity_next;
1154 void InitializeEntitiesRun()
1156 entity startoflist = initialize_entity_first;
1157 initialize_entity_first = NULL;
1158 remove = remove_except_protected;
1159 for (entity e = startoflist; e; e = e.initialize_entity_next)
1161 e.remove_except_protected_forbidden = 1;
1163 for (entity e = startoflist; e; )
1165 e.remove_except_protected_forbidden = 0;
1166 e.initialize_entity_order = 0;
1167 entity next = e.initialize_entity_next;
1168 e.initialize_entity_next = NULL;
1169 var void() func = e.initialize_entity;
1170 e.initialize_entity = func_null;
1171 if (e.classname == "initialize_entity")
1173 entity wrappee = e.enemy;
1177 //dprint("Delayed initialization: ", e.classname, "\n");
1180 WITH(entity, self, e, func());
1185 backtrace(strcat("Null function in: ", e.classname, "\n"));
1189 remove = remove_unsafely;
1192 void UncustomizeEntitiesRun()
1194 for (entity e = NULL; (e = findfloat(e, uncustomizeentityforclient_set, 1)); )
1196 WITH(entity, self, e, e.uncustomizeentityforclient());
1199 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1201 e.customizeentityforclient = customizer;
1202 e.uncustomizeentityforclient = uncustomizer;
1203 e.uncustomizeentityforclient_set = !!uncustomizer;
1206 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1210 if (e.classname == "")
1211 e.classname = "net_linked";
1213 if (e.model == "" || self.modelindex == 0)
1217 setmodel(e, MDL_Null);
1221 e.SendEntity = sendfunc;
1222 e.SendFlags = 0xFFFFFF;
1225 e.effects |= EF_NODEPTHTEST;
1229 e.nextthink = time + dt;
1230 e.think = SUB_Remove;
1235 .float(entity) isEliminated;
1236 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1240 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1241 WriteByte(MSG_ENTITY, sendflags);
1245 for(i = 1; i <= maxclients; i += 8)
1247 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1249 if(eliminatedPlayers.isEliminated(e))
1252 WriteByte(MSG_ENTITY, f);
1259 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1261 if(eliminatedPlayers)
1263 backtrace("Can't spawn eliminatedPlayers again!");
1266 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1267 eliminatedPlayers.isEliminated = isEliminated_func;
1271 void adaptor_think2touch()
1280 void adaptor_think2use()
1292 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1294 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
1295 self.projectiledeathtype |= HITTYPE_SPLASH;
1296 adaptor_think2use();
1299 // deferred dropping
1300 void DropToFloor_Handler()
1302 builtin_droptofloor();
1303 self.dropped_origin = self.origin;
1308 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1313 float trace_hits_box_a0, trace_hits_box_a1;
1315 float trace_hits_box_1d(float end, float thmi, float thma)
1319 // just check if x is in range
1327 // do the trace with respect to x
1328 // 0 -> end has to stay in thmi -> thma
1329 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1330 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1331 if (trace_hits_box_a0 > trace_hits_box_a1)
1337 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1342 // now it is a trace from 0 to end
1344 trace_hits_box_a0 = 0;
1345 trace_hits_box_a1 = 1;
1347 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1349 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1351 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1357 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1359 return trace_hits_box(start, end, thmi - ma, thma - mi);
1362 float SUB_NoImpactCheck()
1364 // zero hitcontents = this is not the real impact, but either the
1365 // mirror-impact of something hitting the projectile instead of the
1366 // projectile hitting the something, or a touchareagrid one. Neither of
1367 // these stop the projectile from moving, so...
1368 if(trace_dphitcontents == 0)
1370 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1371 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));
1374 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1376 if (other == world && self.size != '0 0 0')
1379 tic = self.velocity * sys_frametime;
1380 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1381 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1382 if (trace_fraction >= 1)
1384 LOG_TRACE("Odd... did not hit...?\n");
1386 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1388 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1396 #define SUB_OwnerCheck() (other && (other == self.owner))
1398 void W_Crylink_Dequeue(entity e);
1399 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1401 if(SUB_OwnerCheck())
1403 if(SUB_NoImpactCheck())
1405 if(self.classname == "nade")
1406 return false; // no checks here
1407 else if(self.classname == "grapplinghook")
1408 RemoveGrapplingHook(self.realowner);
1409 else if(self.classname == "spike")
1411 W_Crylink_Dequeue(self);
1418 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1419 UpdateCSQCProjectile(self);
1424 void URI_Get_Callback(float id, float status, string data)
1426 if(url_URI_Get_Callback(id, status, data))
1430 else if (id == URI_GET_DISCARD)
1434 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1437 Curl_URI_Get_Callback(id, status, data);
1439 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1442 OnlineBanList_URI_Get_Callback(id, status, data);
1446 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1450 string uid2name(string myuid) {
1452 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1454 // FIXME remove this later after 0.6 release
1455 // convert old style broken records to correct style
1458 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1461 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1462 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1467 s = "^1Unregistered Player";
1471 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1474 vector start, org, delta, end, enddown, mstart;
1477 m = e.dphitcontentsmask;
1478 e.dphitcontentsmask = goodcontents | badcontents;
1481 delta = world.maxs - world.mins;
1485 for (i = 0; i < attempts; ++i)
1487 start.x = org.x + random() * delta.x;
1488 start.y = org.y + random() * delta.y;
1489 start.z = org.z + random() * delta.z;
1491 // rule 1: start inside world bounds, and outside
1492 // solid, and don't start from somewhere where you can
1493 // fall down to evil
1494 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1495 if (trace_fraction >= 1)
1497 if (trace_startsolid)
1499 if (trace_dphitcontents & badcontents)
1501 if (trace_dphitq3surfaceflags & badsurfaceflags)
1504 // rule 2: if we are too high, lower the point
1505 if (trace_fraction * delta.z > maxaboveground)
1506 start = trace_endpos + '0 0 1' * maxaboveground;
1507 enddown = trace_endpos;
1509 // rule 3: make sure we aren't outside the map. This only works
1510 // for somewhat well formed maps. A good rule of thumb is that
1511 // the map should have a convex outside hull.
1512 // these can be traceLINES as we already verified the starting box
1513 mstart = start + 0.5 * (e.mins + e.maxs);
1514 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1515 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1517 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1518 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1520 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1521 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1523 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1524 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1526 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1527 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1530 // rule 4: we must "see" some spawnpoint or item
1531 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1532 if(checkpvs(mstart, sp))
1533 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1537 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1538 if(checkpvs(mstart, sp))
1539 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1545 // find a random vector to "look at"
1546 end.x = org.x + random() * delta.x;
1547 end.y = org.y + random() * delta.y;
1548 end.z = org.z + random() * delta.z;
1549 end = start + normalize(end - start) * vlen(delta);
1551 // rule 4: start TO end must not be too short
1552 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1553 if (trace_startsolid)
1555 if (trace_fraction < minviewdistance / vlen(delta))
1558 // rule 5: don't want to look at sky
1559 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1562 // rule 6: we must not end up in trigger_hurt
1563 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1569 e.dphitcontentsmask = m;
1573 setorigin(e, start);
1574 e.angles = vectoangles(end - start);
1575 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1582 void write_recordmarker(entity pl, float tstart, float dt)
1584 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1586 // also write a marker into demo files for demotc-race-record-extractor to find
1589 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1590 " ", ftos(tstart), " ", ftos(dt), "\n"));
1593 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1606 if(allowcenter) // 2: allow center handedness
1619 if(allowcenter) // 2: allow center handedness
1635 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1640 if (autocvar_g_shootfromeye)
1644 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1645 else { vecs.y = 0; vecs.z -= 2; }
1653 else if (autocvar_g_shootfromcenter)
1658 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1668 else if (autocvar_g_shootfromclient)
1670 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1675 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1677 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1681 void attach_sameorigin(entity e, entity to, string tag)
1683 vector org, t_forward, t_left, t_up, e_forward, e_up;
1686 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1687 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1688 t_forward = v_forward * tagscale;
1689 t_left = v_right * -tagscale;
1690 t_up = v_up * tagscale;
1692 e.origin_x = org * t_forward;
1693 e.origin_y = org * t_left;
1694 e.origin_z = org * t_up;
1696 // current forward and up directions
1697 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1698 e.angles = AnglesTransform_FromVAngles(e.angles);
1700 e.angles = AnglesTransform_FromAngles(e.angles);
1701 fixedmakevectors(e.angles);
1703 // untransform forward, up!
1704 e_forward.x = v_forward * t_forward;
1705 e_forward.y = v_forward * t_left;
1706 e_forward.z = v_forward * t_up;
1707 e_up.x = v_up * t_forward;
1708 e_up.y = v_up * t_left;
1709 e_up.z = v_up * t_up;
1711 e.angles = fixedvectoangles2(e_forward, e_up);
1712 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1713 e.angles = AnglesTransform_ToVAngles(e.angles);
1715 e.angles = AnglesTransform_ToAngles(e.angles);
1717 setattachment(e, to, tag);
1718 setorigin(e, e.origin);
1721 void detach_sameorigin(entity e)
1724 org = gettaginfo(e, 0);
1725 e.angles = fixedvectoangles2(v_forward, v_up);
1726 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1727 e.angles = AnglesTransform_ToVAngles(e.angles);
1729 e.angles = AnglesTransform_ToAngles(e.angles);
1731 setattachment(e, world, "");
1732 setorigin(e, e.origin);
1735 void follow_sameorigin(entity e, entity to)
1737 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1738 e.aiment = to; // make the hole follow bmodel
1739 e.punchangle = to.angles; // the original angles of bmodel
1740 e.view_ofs = e.origin - to.origin; // relative origin
1741 e.v_angle = e.angles - to.angles; // relative angles
1744 void unfollow_sameorigin(entity e)
1746 e.movetype = MOVETYPE_NONE;
1749 entity gettaginfo_relative_ent;
1750 vector gettaginfo_relative(entity e, float tag)
1752 if (!gettaginfo_relative_ent)
1754 gettaginfo_relative_ent = spawn();
1755 gettaginfo_relative_ent.effects = EF_NODRAW;
1757 gettaginfo_relative_ent.model = e.model;
1758 gettaginfo_relative_ent.modelindex = e.modelindex;
1759 gettaginfo_relative_ent.frame = e.frame;
1760 return gettaginfo(gettaginfo_relative_ent, tag);
1765 float modeleffect_SendEntity(entity to, int sf)
1768 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1771 if(self.velocity != '0 0 0')
1773 if(self.angles != '0 0 0')
1775 if(self.avelocity != '0 0 0')
1778 WriteByte(MSG_ENTITY, f);
1779 WriteShort(MSG_ENTITY, self.modelindex);
1780 WriteByte(MSG_ENTITY, self.skin);
1781 WriteByte(MSG_ENTITY, self.frame);
1782 WriteCoord(MSG_ENTITY, self.origin.x);
1783 WriteCoord(MSG_ENTITY, self.origin.y);
1784 WriteCoord(MSG_ENTITY, self.origin.z);
1787 WriteCoord(MSG_ENTITY, self.velocity.x);
1788 WriteCoord(MSG_ENTITY, self.velocity.y);
1789 WriteCoord(MSG_ENTITY, self.velocity.z);
1793 WriteCoord(MSG_ENTITY, self.angles.x);
1794 WriteCoord(MSG_ENTITY, self.angles.y);
1795 WriteCoord(MSG_ENTITY, self.angles.z);
1799 WriteCoord(MSG_ENTITY, self.avelocity.x);
1800 WriteCoord(MSG_ENTITY, self.avelocity.y);
1801 WriteCoord(MSG_ENTITY, self.avelocity.z);
1803 WriteShort(MSG_ENTITY, self.scale * 256.0);
1804 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1805 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1806 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1807 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1812 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)
1817 e.classname = "modeleffect";
1825 e.teleport_time = t1;
1829 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1833 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1836 sz = max(e.scale, e.scale2);
1837 setsize(e, e.mins * sz, e.maxs * sz);
1838 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1841 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1843 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1846 float randombit(float bits)
1848 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1857 for(f = 1; f <= bits; f *= 2)
1866 r = (r - 1) / (n - 1);
1873 float randombits(float bits, float k, float error_return)
1877 while(k > 0 && bits != r)
1879 r += randombit(bits - r);
1888 void randombit_test(float bits, float iter)
1892 LOG_INFO(ftos(randombit(bits)), "\n");
1897 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1899 if(halflifedist > 0)
1900 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1901 else if(halflifedist < 0)
1902 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1908 .string aiment_classname;
1909 .float aiment_deadflag;
1910 void SetMovetypeFollow(entity ent, entity e)
1912 // FIXME this may not be warpzone aware
1913 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1914 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.
1915 ent.aiment = e; // make the hole follow bmodel
1916 ent.punchangle = e.angles; // the original angles of bmodel
1917 ent.view_ofs = ent.origin - e.origin; // relative origin
1918 ent.v_angle = ent.angles - e.angles; // relative angles
1919 ent.aiment_classname = strzone(e.classname);
1920 ent.aiment_deadflag = e.deadflag;
1922 void UnsetMovetypeFollow(entity ent)
1924 ent.movetype = MOVETYPE_FLY;
1925 PROJECTILE_MAKETRIGGER(ent);
1928 float LostMovetypeFollow(entity ent)
1931 if(ent.movetype != MOVETYPE_FOLLOW)
1937 if(ent.aiment.classname != ent.aiment_classname)
1939 if(ent.aiment.deadflag != ent.aiment_deadflag)
1945 float isPushable(entity e)
1956 case "droppedweapon":
1957 case "keepawayball":
1958 case "nexball_basketball":
1959 case "nexball_football":
1961 case "bullet": // antilagged bullets can't hit this either
1964 if (e.projectiledeathtype)