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 ReplicateVars(this, s, f);
422 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
423 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
424 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
425 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
426 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
427 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
428 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
429 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
430 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
431 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
432 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
437 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
438 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
439 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
440 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
441 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
442 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
443 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
444 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
445 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
446 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
448 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
449 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
451 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
452 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
453 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
454 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
455 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
457 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
460 if (s == "cl_weaponpriority")
461 self.switchweapon = w_getbestweapon(self);
462 if (s == "cl_allow_uidtracking")
463 PlayerStats_GameReport_AddPlayer(self);
467 // decolorizes and team colors the player name when needed
468 string playername(entity p)
471 if (teamplay && !intermission_running && IS_PLAYER(p))
473 t = Team_ColorCode(p.team);
474 return strcat(t, strdecolorize(p.netname));
480 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
482 int i = weaponinfo.weapon;
488 if (g_lms || g_ca || allguns)
490 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
496 d = (i == WEP_SHOTGUN.m_id);
498 d = 0; // weapon is set a few lines later
500 d = !(!weaponinfo.weaponstart);
502 if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
503 d |= (i == WEP_HOOK.m_id);
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) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
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 WEP_ACTION(i, WR_INIT);
726 start_ammo_shells = max(0, start_ammo_shells);
727 start_ammo_nails = max(0, start_ammo_nails);
728 start_ammo_rockets = max(0, start_ammo_rockets);
729 start_ammo_cells = max(0, start_ammo_cells);
730 start_ammo_plasma = max(0, start_ammo_plasma);
731 start_ammo_fuel = max(0, start_ammo_fuel);
733 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
734 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
735 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
736 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
737 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
738 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
741 float sound_allowed(float destin, entity e)
743 // sounds from world may always pass
746 if (e.classname == "body")
748 else if (e.realowner && e.realowner != e)
750 else if (e.owner && e.owner != e)
755 // sounds to self may always pass
756 if (destin == MSG_ONE)
759 // sounds by players can be removed
760 if (autocvar_bot_sound_monopoly)
761 if (IS_REAL_CLIENT(e))
763 // anything else may pass
768 void sound(entity e, float chan, string samp, float vol, float attenu)
770 if (!sound_allowed(MSG_BROADCAST, e))
772 sound7(e, chan, samp, vol, attenu, 0, 0);
775 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
779 if (!sound_allowed(_dest, e))
782 entno = num_for_edict(e);
783 idx = precache_sound_index(samp);
788 attenu = floor(attenu * 64);
789 vol = floor(vol * 255);
792 sflags |= SND_VOLUME;
794 sflags |= SND_ATTENUATION;
795 if (entno >= 8192 || chan < 0 || chan > 7)
796 sflags |= SND_LARGEENTITY;
798 sflags |= SND_LARGESOUND;
800 WriteByte(_dest, SVC_SOUND);
801 WriteByte(_dest, sflags);
802 if (sflags & SND_VOLUME)
803 WriteByte(_dest, vol);
804 if (sflags & SND_ATTENUATION)
805 WriteByte(_dest, attenu);
806 if (sflags & SND_LARGEENTITY)
808 WriteShort(_dest, entno);
809 WriteByte(_dest, chan);
813 WriteShort(_dest, entno * 8 + chan);
815 if (sflags & SND_LARGESOUND)
816 WriteShort(_dest, idx);
818 WriteByte(_dest, idx);
820 WriteCoord(_dest, o.x);
821 WriteCoord(_dest, o.y);
822 WriteCoord(_dest, o.z);
824 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
828 if (!sound_allowed(_dest, e))
831 o = e.origin + 0.5 * (e.mins + e.maxs);
832 soundtoat(_dest, e, o, chan, samp, vol, _atten);
834 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
836 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
838 void stopsoundto(float _dest, entity e, float chan)
842 if (!sound_allowed(_dest, e))
845 entno = num_for_edict(e);
847 if (entno >= 8192 || chan < 0 || chan > 7)
850 idx = precache_sound_index("misc/null.wav");
851 sflags = SND_LARGEENTITY;
853 sflags |= SND_LARGESOUND;
854 WriteByte(_dest, SVC_SOUND);
855 WriteByte(_dest, sflags);
856 WriteShort(_dest, entno);
857 WriteByte(_dest, chan);
858 if (sflags & SND_LARGESOUND)
859 WriteShort(_dest, idx);
861 WriteByte(_dest, idx);
862 WriteCoord(_dest, e.origin.x);
863 WriteCoord(_dest, e.origin.y);
864 WriteCoord(_dest, e.origin.z);
868 WriteByte(_dest, SVC_STOPSOUND);
869 WriteShort(_dest, entno * 8 + chan);
872 void stopsound(entity e, float chan)
874 if (!sound_allowed(MSG_BROADCAST, e))
877 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
878 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
881 void play2(entity e, string filename)
883 //stuffcmd(e, strcat("play2 ", filename, "\n"));
885 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
888 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
890 float spamsound(entity e, float chan, string samp, float vol, float _atten)
892 if (!sound_allowed(MSG_BROADCAST, e))
895 if (time > e.spamtime)
898 sound(e, chan, samp, vol, _atten);
904 void play2team(float t, string filename)
908 if (autocvar_bot_sound_monopoly)
911 FOR_EACH_REALPLAYER(head)
914 play2(head, filename);
918 void play2all(string samp)
920 if (autocvar_bot_sound_monopoly)
923 sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
926 void PrecachePlayerSounds(string f);
927 void precache_playermodel(string m)
929 float globhandle, i, n;
932 if(substring(m, -9,5) == "_lod1")
934 if(substring(m, -9,5) == "_lod2")
937 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
940 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
944 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
947 n = search_getsize(globhandle);
948 for (i = 0; i < n; ++i)
950 //print(search_getfilename(globhandle, i), "\n");
951 f = search_getfilename(globhandle, i);
952 PrecachePlayerSounds(f);
954 search_end(globhandle);
956 void precache_all_playermodels(string pattern)
958 float globhandle, i, n;
961 globhandle = search_begin(pattern, true, false);
964 n = search_getsize(globhandle);
965 for (i = 0; i < n; ++i)
967 //print(search_getfilename(globhandle, i), "\n");
968 f = search_getfilename(globhandle, i);
969 precache_playermodel(f);
971 search_end(globhandle);
976 // gamemode related things
978 // Precache all player models if desired
979 if (autocvar_sv_precacheplayermodels)
981 PrecachePlayerSounds("sound/player/default.sounds");
982 precache_all_playermodels("models/player/*.zym");
983 precache_all_playermodels("models/player/*.dpm");
984 precache_all_playermodels("models/player/*.md3");
985 precache_all_playermodels("models/player/*.psk");
986 precache_all_playermodels("models/player/*.iqm");
989 if (autocvar_sv_defaultcharacter)
992 s = autocvar_sv_defaultplayermodel_red;
994 precache_playermodel(s);
995 s = autocvar_sv_defaultplayermodel_blue;
997 precache_playermodel(s);
998 s = autocvar_sv_defaultplayermodel_yellow;
1000 precache_playermodel(s);
1001 s = autocvar_sv_defaultplayermodel_pink;
1003 precache_playermodel(s);
1004 s = autocvar_sv_defaultplayermodel;
1006 precache_playermodel(s);
1011 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1012 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1015 // gore and miscellaneous sounds
1016 //precache_sound ("misc/h2ohit.wav");
1017 precache_sound ("misc/armorimpact.wav");
1018 precache_sound ("misc/bodyimpact1.wav");
1019 precache_sound ("misc/bodyimpact2.wav");
1020 precache_sound ("misc/gib.wav");
1021 precache_sound ("misc/gib_splat01.wav");
1022 precache_sound ("misc/gib_splat02.wav");
1023 precache_sound ("misc/gib_splat03.wav");
1024 precache_sound ("misc/gib_splat04.wav");
1025 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1026 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1027 precache_sound ("misc/null.wav");
1028 precache_sound ("misc/spawn.wav");
1029 precache_sound ("misc/talk.wav");
1030 precache_sound ("misc/teleport.wav");
1031 precache_sound ("misc/poweroff.wav");
1032 precache_sound ("player/lava.wav");
1033 precache_sound ("player/slime.wav");
1035 // common weapon precaches
1036 precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here
1037 precache_sound (W_Sound("weapon_switch"));
1038 precache_sound (W_Sound("weaponpickup"));
1039 precache_sound (W_Sound("unavailable"));
1040 precache_sound (W_Sound("dryfire"));
1041 if (g_grappling_hook)
1043 precache_sound (W_Sound("hook_fire")); // hook
1044 precache_sound (W_Sound("hook_impact")); // hook
1048 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1050 if (!self.noise && self.music) // quake 3 uses the music field
1051 self.noise = self.music;
1053 // plays music for the level if there is any
1056 precache_sound (self.noise);
1057 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1061 #include "precache-for-csqc.inc"
1065 void make_safe_for_remove(entity e)
1067 if (e.initialize_entity)
1069 entity ent, prev = world;
1070 for (ent = initialize_entity_first; ent; )
1072 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1074 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1075 // skip it in linked list
1078 prev.initialize_entity_next = ent.initialize_entity_next;
1079 ent = prev.initialize_entity_next;
1083 initialize_entity_first = ent.initialize_entity_next;
1084 ent = initialize_entity_first;
1090 ent = ent.initialize_entity_next;
1096 void objerror(string s)
1098 make_safe_for_remove(self);
1099 builtin_objerror(s);
1102 .float remove_except_protected_forbidden;
1103 void remove_except_protected(entity e)
1105 if(e.remove_except_protected_forbidden)
1106 error("not allowed to remove this at this point");
1110 void remove_unsafely(entity e)
1112 if(e.classname == "spike")
1113 error("Removing spikes is forbidden (crylink bug), please report");
1117 void remove_safely(entity e)
1119 make_safe_for_remove(e);
1123 void InitializeEntity(entity e, void(void) func, float order)
1127 if (!e || e.initialize_entity)
1129 // make a proxy initializer entity
1131 e = new(initialize_entity);
1135 e.initialize_entity = func;
1136 e.initialize_entity_order = order;
1138 cur = initialize_entity_first;
1142 if (!cur || cur.initialize_entity_order > order)
1144 // insert between prev and cur
1146 prev.initialize_entity_next = e;
1148 initialize_entity_first = e;
1149 e.initialize_entity_next = cur;
1153 cur = cur.initialize_entity_next;
1156 void InitializeEntitiesRun()
1158 entity startoflist = initialize_entity_first;
1159 initialize_entity_first = NULL;
1160 remove = remove_except_protected;
1161 for (entity e = startoflist; e; e = e.initialize_entity_next)
1163 e.remove_except_protected_forbidden = 1;
1165 for (entity e = startoflist; e; )
1167 e.remove_except_protected_forbidden = 0;
1168 e.initialize_entity_order = 0;
1169 entity next = e.initialize_entity_next;
1170 e.initialize_entity_next = NULL;
1171 var void() func = e.initialize_entity;
1172 e.initialize_entity = func_null;
1173 if (e.classname == "initialize_entity")
1175 entity wrappee = e.enemy;
1179 //dprint("Delayed initialization: ", e.classname, "\n");
1182 WITH(entity, self, e, func());
1187 backtrace(strcat("Null function in: ", e.classname, "\n"));
1191 remove = remove_unsafely;
1194 void UncustomizeEntitiesRun()
1196 for (entity e = NULL; (e = findfloat(e, uncustomizeentityforclient_set, 1)); )
1198 WITH(entity, self, e, e.uncustomizeentityforclient());
1201 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1203 e.customizeentityforclient = customizer;
1204 e.uncustomizeentityforclient = uncustomizer;
1205 e.uncustomizeentityforclient_set = !!uncustomizer;
1208 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1212 if (e.classname == "")
1213 e.classname = "net_linked";
1215 if (e.model == "" || self.modelindex == 0)
1219 setmodel(e, MDL_Null);
1223 e.SendEntity = sendfunc;
1224 e.SendFlags = 0xFFFFFF;
1227 e.effects |= EF_NODEPTHTEST;
1231 e.nextthink = time + dt;
1232 e.think = SUB_Remove;
1237 .float(entity) isEliminated;
1238 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1242 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1243 WriteByte(MSG_ENTITY, sendflags);
1247 for(i = 1; i <= maxclients; i += 8)
1249 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1251 if(eliminatedPlayers.isEliminated(e))
1254 WriteByte(MSG_ENTITY, f);
1261 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1263 if(eliminatedPlayers)
1265 backtrace("Can't spawn eliminatedPlayers again!");
1268 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1269 eliminatedPlayers.isEliminated = isEliminated_func;
1273 void adaptor_think2touch()
1282 void adaptor_think2use()
1294 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1296 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
1297 self.projectiledeathtype |= HITTYPE_SPLASH;
1298 adaptor_think2use();
1301 // deferred dropping
1302 void DropToFloor_Handler()
1304 builtin_droptofloor();
1305 self.dropped_origin = self.origin;
1310 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1315 float trace_hits_box_a0, trace_hits_box_a1;
1317 float trace_hits_box_1d(float end, float thmi, float thma)
1321 // just check if x is in range
1329 // do the trace with respect to x
1330 // 0 -> end has to stay in thmi -> thma
1331 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1332 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1333 if (trace_hits_box_a0 > trace_hits_box_a1)
1339 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1344 // now it is a trace from 0 to end
1346 trace_hits_box_a0 = 0;
1347 trace_hits_box_a1 = 1;
1349 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1351 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1353 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1359 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1361 return trace_hits_box(start, end, thmi - ma, thma - mi);
1364 float SUB_NoImpactCheck()
1366 // zero hitcontents = this is not the real impact, but either the
1367 // mirror-impact of something hitting the projectile instead of the
1368 // projectile hitting the something, or a touchareagrid one. Neither of
1369 // these stop the projectile from moving, so...
1370 if(trace_dphitcontents == 0)
1372 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1373 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));
1376 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1378 if (other == world && self.size != '0 0 0')
1381 tic = self.velocity * sys_frametime;
1382 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1383 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1384 if (trace_fraction >= 1)
1386 LOG_TRACE("Odd... did not hit...?\n");
1388 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1390 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1398 #define SUB_OwnerCheck() (other && (other == self.owner))
1400 void W_Crylink_Dequeue(entity e);
1401 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1403 if(SUB_OwnerCheck())
1405 if(SUB_NoImpactCheck())
1407 if(self.classname == "nade")
1408 return false; // no checks here
1409 else if(self.classname == "grapplinghook")
1410 RemoveGrapplingHook(self.realowner);
1411 else if(self.classname == "spike")
1413 W_Crylink_Dequeue(self);
1420 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1421 UpdateCSQCProjectile(self);
1426 void URI_Get_Callback(float id, float status, string data)
1428 if(url_URI_Get_Callback(id, status, data))
1432 else if (id == URI_GET_DISCARD)
1436 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1439 Curl_URI_Get_Callback(id, status, data);
1441 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1444 OnlineBanList_URI_Get_Callback(id, status, data);
1448 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1452 string uid2name(string myuid) {
1454 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1456 // FIXME remove this later after 0.6 release
1457 // convert old style broken records to correct style
1460 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1463 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1464 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1469 s = "^1Unregistered Player";
1473 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1476 vector start, org, delta, end, enddown, mstart;
1479 m = e.dphitcontentsmask;
1480 e.dphitcontentsmask = goodcontents | badcontents;
1483 delta = world.maxs - world.mins;
1487 for (i = 0; i < attempts; ++i)
1489 start.x = org.x + random() * delta.x;
1490 start.y = org.y + random() * delta.y;
1491 start.z = org.z + random() * delta.z;
1493 // rule 1: start inside world bounds, and outside
1494 // solid, and don't start from somewhere where you can
1495 // fall down to evil
1496 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1497 if (trace_fraction >= 1)
1499 if (trace_startsolid)
1501 if (trace_dphitcontents & badcontents)
1503 if (trace_dphitq3surfaceflags & badsurfaceflags)
1506 // rule 2: if we are too high, lower the point
1507 if (trace_fraction * delta.z > maxaboveground)
1508 start = trace_endpos + '0 0 1' * maxaboveground;
1509 enddown = trace_endpos;
1511 // rule 3: make sure we aren't outside the map. This only works
1512 // for somewhat well formed maps. A good rule of thumb is that
1513 // the map should have a convex outside hull.
1514 // these can be traceLINES as we already verified the starting box
1515 mstart = start + 0.5 * (e.mins + e.maxs);
1516 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1517 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1519 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1520 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1522 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1523 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1525 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1526 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1528 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1529 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1532 // rule 4: we must "see" some spawnpoint or item
1533 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1534 if(checkpvs(mstart, sp))
1535 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1539 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1540 if(checkpvs(mstart, sp))
1541 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1547 // find a random vector to "look at"
1548 end.x = org.x + random() * delta.x;
1549 end.y = org.y + random() * delta.y;
1550 end.z = org.z + random() * delta.z;
1551 end = start + normalize(end - start) * vlen(delta);
1553 // rule 4: start TO end must not be too short
1554 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1555 if (trace_startsolid)
1557 if (trace_fraction < minviewdistance / vlen(delta))
1560 // rule 5: don't want to look at sky
1561 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1564 // rule 6: we must not end up in trigger_hurt
1565 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1571 e.dphitcontentsmask = m;
1575 setorigin(e, start);
1576 e.angles = vectoangles(end - start);
1577 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1584 void write_recordmarker(entity pl, float tstart, float dt)
1586 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1588 // also write a marker into demo files for demotc-race-record-extractor to find
1591 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1592 " ", ftos(tstart), " ", ftos(dt), "\n"));
1595 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1608 if(allowcenter) // 2: allow center handedness
1621 if(allowcenter) // 2: allow center handedness
1637 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1642 if (autocvar_g_shootfromeye)
1646 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1647 else { vecs.y = 0; vecs.z -= 2; }
1655 else if (autocvar_g_shootfromcenter)
1660 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1670 else if (autocvar_g_shootfromclient)
1672 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1677 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1679 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1683 void attach_sameorigin(entity e, entity to, string tag)
1685 vector org, t_forward, t_left, t_up, e_forward, e_up;
1688 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1689 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1690 t_forward = v_forward * tagscale;
1691 t_left = v_right * -tagscale;
1692 t_up = v_up * tagscale;
1694 e.origin_x = org * t_forward;
1695 e.origin_y = org * t_left;
1696 e.origin_z = org * t_up;
1698 // current forward and up directions
1699 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1700 e.angles = AnglesTransform_FromVAngles(e.angles);
1702 e.angles = AnglesTransform_FromAngles(e.angles);
1703 fixedmakevectors(e.angles);
1705 // untransform forward, up!
1706 e_forward.x = v_forward * t_forward;
1707 e_forward.y = v_forward * t_left;
1708 e_forward.z = v_forward * t_up;
1709 e_up.x = v_up * t_forward;
1710 e_up.y = v_up * t_left;
1711 e_up.z = v_up * t_up;
1713 e.angles = fixedvectoangles2(e_forward, e_up);
1714 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1715 e.angles = AnglesTransform_ToVAngles(e.angles);
1717 e.angles = AnglesTransform_ToAngles(e.angles);
1719 setattachment(e, to, tag);
1720 setorigin(e, e.origin);
1723 void detach_sameorigin(entity e)
1726 org = gettaginfo(e, 0);
1727 e.angles = fixedvectoangles2(v_forward, v_up);
1728 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1729 e.angles = AnglesTransform_ToVAngles(e.angles);
1731 e.angles = AnglesTransform_ToAngles(e.angles);
1733 setattachment(e, world, "");
1734 setorigin(e, e.origin);
1737 void follow_sameorigin(entity e, entity to)
1739 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1740 e.aiment = to; // make the hole follow bmodel
1741 e.punchangle = to.angles; // the original angles of bmodel
1742 e.view_ofs = e.origin - to.origin; // relative origin
1743 e.v_angle = e.angles - to.angles; // relative angles
1746 void unfollow_sameorigin(entity e)
1748 e.movetype = MOVETYPE_NONE;
1751 entity gettaginfo_relative_ent;
1752 vector gettaginfo_relative(entity e, float tag)
1754 if (!gettaginfo_relative_ent)
1756 gettaginfo_relative_ent = spawn();
1757 gettaginfo_relative_ent.effects = EF_NODRAW;
1759 gettaginfo_relative_ent.model = e.model;
1760 gettaginfo_relative_ent.modelindex = e.modelindex;
1761 gettaginfo_relative_ent.frame = e.frame;
1762 return gettaginfo(gettaginfo_relative_ent, tag);
1767 float modeleffect_SendEntity(entity to, int sf)
1770 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1773 if(self.velocity != '0 0 0')
1775 if(self.angles != '0 0 0')
1777 if(self.avelocity != '0 0 0')
1780 WriteByte(MSG_ENTITY, f);
1781 WriteShort(MSG_ENTITY, self.modelindex);
1782 WriteByte(MSG_ENTITY, self.skin);
1783 WriteByte(MSG_ENTITY, self.frame);
1784 WriteCoord(MSG_ENTITY, self.origin.x);
1785 WriteCoord(MSG_ENTITY, self.origin.y);
1786 WriteCoord(MSG_ENTITY, self.origin.z);
1789 WriteCoord(MSG_ENTITY, self.velocity.x);
1790 WriteCoord(MSG_ENTITY, self.velocity.y);
1791 WriteCoord(MSG_ENTITY, self.velocity.z);
1795 WriteCoord(MSG_ENTITY, self.angles.x);
1796 WriteCoord(MSG_ENTITY, self.angles.y);
1797 WriteCoord(MSG_ENTITY, self.angles.z);
1801 WriteCoord(MSG_ENTITY, self.avelocity.x);
1802 WriteCoord(MSG_ENTITY, self.avelocity.y);
1803 WriteCoord(MSG_ENTITY, self.avelocity.z);
1805 WriteShort(MSG_ENTITY, self.scale * 256.0);
1806 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1807 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1808 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1809 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1814 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)
1819 e.classname = "modeleffect";
1827 e.teleport_time = t1;
1831 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1835 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1838 sz = max(e.scale, e.scale2);
1839 setsize(e, e.mins * sz, e.maxs * sz);
1840 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1843 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1845 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1848 float randombit(float bits)
1850 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1859 for(f = 1; f <= bits; f *= 2)
1868 r = (r - 1) / (n - 1);
1875 float randombits(float bits, float k, float error_return)
1879 while(k > 0 && bits != r)
1881 r += randombit(bits - r);
1890 void randombit_test(float bits, float iter)
1894 LOG_INFO(ftos(randombit(bits)), "\n");
1899 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1901 if(halflifedist > 0)
1902 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1903 else if(halflifedist < 0)
1904 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1910 .string aiment_classname;
1911 .float aiment_deadflag;
1912 void SetMovetypeFollow(entity ent, entity e)
1914 // FIXME this may not be warpzone aware
1915 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1916 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.
1917 ent.aiment = e; // make the hole follow bmodel
1918 ent.punchangle = e.angles; // the original angles of bmodel
1919 ent.view_ofs = ent.origin - e.origin; // relative origin
1920 ent.v_angle = ent.angles - e.angles; // relative angles
1921 ent.aiment_classname = strzone(e.classname);
1922 ent.aiment_deadflag = e.deadflag;
1924 void UnsetMovetypeFollow(entity ent)
1926 ent.movetype = MOVETYPE_FLY;
1927 PROJECTILE_MAKETRIGGER(ent);
1930 float LostMovetypeFollow(entity ent)
1933 if(ent.movetype != MOVETYPE_FOLLOW)
1939 if(ent.aiment.classname != ent.aiment_classname)
1941 if(ent.aiment.deadflag != ent.aiment_deadflag)
1947 float isPushable(entity e)
1958 case "droppedweapon":
1959 case "keepawayball":
1960 case "nexball_basketball":
1961 case "nexball_football":
1963 case "bullet": // antilagged bullets can't hit this either
1966 if (e.projectiledeathtype)