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);
309 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
310 p = p + strlen(replacement);
315 float boolean(float value) { // if value is 0 return false (0), otherwise return true (1)
316 return (value == 0) ? false : true;
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 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
977 precache_model ("models/misc/chatbubble.spr");
978 precache_model("models/ice/ice.md3");
980 // Precache all player models if desired
981 if (autocvar_sv_precacheplayermodels)
983 PrecachePlayerSounds("sound/player/default.sounds");
984 precache_all_playermodels("models/player/*.zym");
985 precache_all_playermodels("models/player/*.dpm");
986 precache_all_playermodels("models/player/*.md3");
987 precache_all_playermodels("models/player/*.psk");
988 precache_all_playermodels("models/player/*.iqm");
991 if (autocvar_sv_defaultcharacter)
994 s = autocvar_sv_defaultplayermodel_red;
996 precache_playermodel(s);
997 s = autocvar_sv_defaultplayermodel_blue;
999 precache_playermodel(s);
1000 s = autocvar_sv_defaultplayermodel_yellow;
1002 precache_playermodel(s);
1003 s = autocvar_sv_defaultplayermodel_pink;
1005 precache_playermodel(s);
1006 s = autocvar_sv_defaultplayermodel;
1008 precache_playermodel(s);
1013 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1014 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1017 // gore and miscellaneous sounds
1018 //precache_sound ("misc/h2ohit.wav");
1019 precache_model ("models/hook.md3");
1020 precache_sound ("misc/armorimpact.wav");
1021 precache_sound ("misc/bodyimpact1.wav");
1022 precache_sound ("misc/bodyimpact2.wav");
1023 precache_sound ("misc/gib.wav");
1024 precache_sound ("misc/gib_splat01.wav");
1025 precache_sound ("misc/gib_splat02.wav");
1026 precache_sound ("misc/gib_splat03.wav");
1027 precache_sound ("misc/gib_splat04.wav");
1028 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1029 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1030 precache_sound ("misc/null.wav");
1031 precache_sound ("misc/spawn.wav");
1032 precache_sound ("misc/talk.wav");
1033 precache_sound ("misc/teleport.wav");
1034 precache_sound ("misc/poweroff.wav");
1035 precache_sound ("player/lava.wav");
1036 precache_sound ("player/slime.wav");
1038 precache_model ("models/sprites/0.spr32");
1039 precache_model ("models/sprites/1.spr32");
1040 precache_model ("models/sprites/2.spr32");
1041 precache_model ("models/sprites/3.spr32");
1042 precache_model ("models/sprites/4.spr32");
1043 precache_model ("models/sprites/5.spr32");
1044 precache_model ("models/sprites/6.spr32");
1045 precache_model ("models/sprites/7.spr32");
1046 precache_model ("models/sprites/8.spr32");
1047 precache_model ("models/sprites/9.spr32");
1048 precache_model ("models/sprites/10.spr32");
1050 // common weapon precaches
1051 precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here
1052 precache_sound (W_Sound("weapon_switch"));
1053 precache_sound (W_Sound("weaponpickup"));
1054 precache_sound (W_Sound("unavailable"));
1055 precache_sound (W_Sound("dryfire"));
1056 if (g_grappling_hook)
1058 precache_sound (W_Sound("hook_fire")); // hook
1059 precache_sound (W_Sound("hook_impact")); // hook
1062 precache_model("models/elaser.mdl");
1063 precache_model("models/laser.mdl");
1064 precache_model("models/ebomb.mdl");
1067 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1069 if (!self.noise && self.music) // quake 3 uses the music field
1070 self.noise = self.music;
1072 // plays music for the level if there is any
1075 precache_sound (self.noise);
1076 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1080 #include "precache-for-csqc.inc"
1084 void make_safe_for_remove(entity e)
1086 if (e.initialize_entity)
1088 entity ent, prev = world;
1089 for (ent = initialize_entity_first; ent; )
1091 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1093 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1094 // skip it in linked list
1097 prev.initialize_entity_next = ent.initialize_entity_next;
1098 ent = prev.initialize_entity_next;
1102 initialize_entity_first = ent.initialize_entity_next;
1103 ent = initialize_entity_first;
1109 ent = ent.initialize_entity_next;
1115 void objerror(string s)
1117 make_safe_for_remove(self);
1118 builtin_objerror(s);
1121 .float remove_except_protected_forbidden;
1122 void remove_except_protected(entity e)
1124 if(e.remove_except_protected_forbidden)
1125 error("not allowed to remove this at this point");
1129 void remove_unsafely(entity e)
1131 if(e.classname == "spike")
1132 error("Removing spikes is forbidden (crylink bug), please report");
1136 void remove_safely(entity e)
1138 make_safe_for_remove(e);
1142 void InitializeEntity(entity e, void(void) func, float order)
1146 if (!e || e.initialize_entity)
1148 // make a proxy initializer entity
1152 e.classname = "initialize_entity";
1156 e.initialize_entity = func;
1157 e.initialize_entity_order = order;
1159 cur = initialize_entity_first;
1163 if (!cur || cur.initialize_entity_order > order)
1165 // insert between prev and cur
1167 prev.initialize_entity_next = e;
1169 initialize_entity_first = e;
1170 e.initialize_entity_next = cur;
1174 cur = cur.initialize_entity_next;
1177 void InitializeEntitiesRun()
1180 startoflist = initialize_entity_first;
1181 initialize_entity_first = world;
1182 remove = remove_except_protected;
1183 for (self = startoflist; self; self = self.initialize_entity_next)
1185 self.remove_except_protected_forbidden = 1;
1187 for (self = startoflist; self; )
1190 var void(void) func;
1191 e = self.initialize_entity_next;
1192 func = self.initialize_entity;
1193 self.initialize_entity_order = 0;
1194 self.initialize_entity = func_null;
1195 self.initialize_entity_next = world;
1196 self.remove_except_protected_forbidden = 0;
1197 if (self.classname == "initialize_entity")
1201 builtin_remove(self);
1204 //dprint("Delayed initialization: ", self.classname, "\n");
1210 backtrace(strcat("Null function in: ", self.classname, "\n"));
1214 remove = remove_unsafely;
1217 void UncustomizeEntitiesRun()
1221 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1222 self.uncustomizeentityforclient();
1225 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1227 e.customizeentityforclient = customizer;
1228 e.uncustomizeentityforclient = uncustomizer;
1229 e.uncustomizeentityforclient_set = !!uncustomizer;
1232 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1236 if (e.classname == "")
1237 e.classname = "net_linked";
1239 if (e.model == "" || self.modelindex == 0)
1243 setmodel(e, "null");
1247 e.SendEntity = sendfunc;
1248 e.SendFlags = 0xFFFFFF;
1251 e.effects |= EF_NODEPTHTEST;
1255 e.nextthink = time + dt;
1256 e.think = SUB_Remove;
1261 .float(entity) isEliminated;
1262 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1266 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1267 WriteByte(MSG_ENTITY, sendflags);
1271 for(i = 1; i <= maxclients; i += 8)
1273 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1275 if(eliminatedPlayers.isEliminated(e))
1278 WriteByte(MSG_ENTITY, f);
1285 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1287 if(eliminatedPlayers)
1289 backtrace("Can't spawn eliminatedPlayers again!");
1292 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1293 eliminatedPlayers.isEliminated = isEliminated_func;
1297 void adaptor_think2touch()
1306 void adaptor_think2use()
1318 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1320 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
1321 self.projectiledeathtype |= HITTYPE_SPLASH;
1322 adaptor_think2use();
1325 // deferred dropping
1326 void DropToFloor_Handler()
1328 builtin_droptofloor();
1329 self.dropped_origin = self.origin;
1334 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1339 float trace_hits_box_a0, trace_hits_box_a1;
1341 float trace_hits_box_1d(float end, float thmi, float thma)
1345 // just check if x is in range
1353 // do the trace with respect to x
1354 // 0 -> end has to stay in thmi -> thma
1355 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1356 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1357 if (trace_hits_box_a0 > trace_hits_box_a1)
1363 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1368 // now it is a trace from 0 to end
1370 trace_hits_box_a0 = 0;
1371 trace_hits_box_a1 = 1;
1373 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1375 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1377 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1383 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1385 return trace_hits_box(start, end, thmi - ma, thma - mi);
1388 float SUB_NoImpactCheck()
1390 // zero hitcontents = this is not the real impact, but either the
1391 // mirror-impact of something hitting the projectile instead of the
1392 // projectile hitting the something, or a touchareagrid one. Neither of
1393 // these stop the projectile from moving, so...
1394 if(trace_dphitcontents == 0)
1396 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1397 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));
1400 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1402 if (other == world && self.size != '0 0 0')
1405 tic = self.velocity * sys_frametime;
1406 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1407 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1408 if (trace_fraction >= 1)
1410 LOG_TRACE("Odd... did not hit...?\n");
1412 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1414 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1422 #define SUB_OwnerCheck() (other && (other == self.owner))
1424 void W_Crylink_Dequeue(entity e);
1425 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1427 if(SUB_OwnerCheck())
1429 if(SUB_NoImpactCheck())
1431 if(self.classname == "nade")
1432 return false; // no checks here
1433 else if(self.classname == "grapplinghook")
1434 RemoveGrapplingHook(self.realowner);
1435 else if(self.classname == "spike")
1437 W_Crylink_Dequeue(self);
1444 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1445 UpdateCSQCProjectile(self);
1450 void URI_Get_Callback(float id, float status, string data)
1452 if(url_URI_Get_Callback(id, status, data))
1456 else if (id == URI_GET_DISCARD)
1460 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1463 Curl_URI_Get_Callback(id, status, data);
1465 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1468 OnlineBanList_URI_Get_Callback(id, status, data);
1472 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1476 string uid2name(string myuid) {
1478 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1480 // FIXME remove this later after 0.6 release
1481 // convert old style broken records to correct style
1484 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1487 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1488 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1493 s = "^1Unregistered Player";
1497 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1500 vector start, org, delta, end, enddown, mstart;
1503 m = e.dphitcontentsmask;
1504 e.dphitcontentsmask = goodcontents | badcontents;
1507 delta = world.maxs - world.mins;
1511 for (i = 0; i < attempts; ++i)
1513 start.x = org.x + random() * delta.x;
1514 start.y = org.y + random() * delta.y;
1515 start.z = org.z + random() * delta.z;
1517 // rule 1: start inside world bounds, and outside
1518 // solid, and don't start from somewhere where you can
1519 // fall down to evil
1520 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1521 if (trace_fraction >= 1)
1523 if (trace_startsolid)
1525 if (trace_dphitcontents & badcontents)
1527 if (trace_dphitq3surfaceflags & badsurfaceflags)
1530 // rule 2: if we are too high, lower the point
1531 if (trace_fraction * delta.z > maxaboveground)
1532 start = trace_endpos + '0 0 1' * maxaboveground;
1533 enddown = trace_endpos;
1535 // rule 3: make sure we aren't outside the map. This only works
1536 // for somewhat well formed maps. A good rule of thumb is that
1537 // the map should have a convex outside hull.
1538 // these can be traceLINES as we already verified the starting box
1539 mstart = start + 0.5 * (e.mins + e.maxs);
1540 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1541 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1543 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1544 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1546 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1547 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1549 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1550 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1552 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1553 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1556 // rule 4: we must "see" some spawnpoint or item
1557 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1558 if(checkpvs(mstart, sp))
1559 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1563 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1564 if(checkpvs(mstart, sp))
1565 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1571 // find a random vector to "look at"
1572 end.x = org.x + random() * delta.x;
1573 end.y = org.y + random() * delta.y;
1574 end.z = org.z + random() * delta.z;
1575 end = start + normalize(end - start) * vlen(delta);
1577 // rule 4: start TO end must not be too short
1578 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1579 if (trace_startsolid)
1581 if (trace_fraction < minviewdistance / vlen(delta))
1584 // rule 5: don't want to look at sky
1585 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1588 // rule 6: we must not end up in trigger_hurt
1589 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1595 e.dphitcontentsmask = m;
1599 setorigin(e, start);
1600 e.angles = vectoangles(end - start);
1601 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1608 void write_recordmarker(entity pl, float tstart, float dt)
1610 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1612 // also write a marker into demo files for demotc-race-record-extractor to find
1615 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1616 " ", ftos(tstart), " ", ftos(dt), "\n"));
1619 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1632 if(allowcenter) // 2: allow center handedness
1645 if(allowcenter) // 2: allow center handedness
1661 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1666 if (autocvar_g_shootfromeye)
1670 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1671 else { vecs.y = 0; vecs.z -= 2; }
1679 else if (autocvar_g_shootfromcenter)
1684 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1694 else if (autocvar_g_shootfromclient)
1696 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1701 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1703 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1707 void attach_sameorigin(entity e, entity to, string tag)
1709 vector org, t_forward, t_left, t_up, e_forward, e_up;
1712 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1713 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1714 t_forward = v_forward * tagscale;
1715 t_left = v_right * -tagscale;
1716 t_up = v_up * tagscale;
1718 e.origin_x = org * t_forward;
1719 e.origin_y = org * t_left;
1720 e.origin_z = org * t_up;
1722 // current forward and up directions
1723 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1724 e.angles = AnglesTransform_FromVAngles(e.angles);
1726 e.angles = AnglesTransform_FromAngles(e.angles);
1727 fixedmakevectors(e.angles);
1729 // untransform forward, up!
1730 e_forward.x = v_forward * t_forward;
1731 e_forward.y = v_forward * t_left;
1732 e_forward.z = v_forward * t_up;
1733 e_up.x = v_up * t_forward;
1734 e_up.y = v_up * t_left;
1735 e_up.z = v_up * t_up;
1737 e.angles = fixedvectoangles2(e_forward, e_up);
1738 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1739 e.angles = AnglesTransform_ToVAngles(e.angles);
1741 e.angles = AnglesTransform_ToAngles(e.angles);
1743 setattachment(e, to, tag);
1744 setorigin(e, e.origin);
1747 void detach_sameorigin(entity e)
1750 org = gettaginfo(e, 0);
1751 e.angles = fixedvectoangles2(v_forward, v_up);
1752 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1753 e.angles = AnglesTransform_ToVAngles(e.angles);
1755 e.angles = AnglesTransform_ToAngles(e.angles);
1757 setattachment(e, world, "");
1758 setorigin(e, e.origin);
1761 void follow_sameorigin(entity e, entity to)
1763 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1764 e.aiment = to; // make the hole follow bmodel
1765 e.punchangle = to.angles; // the original angles of bmodel
1766 e.view_ofs = e.origin - to.origin; // relative origin
1767 e.v_angle = e.angles - to.angles; // relative angles
1770 void unfollow_sameorigin(entity e)
1772 e.movetype = MOVETYPE_NONE;
1775 entity gettaginfo_relative_ent;
1776 vector gettaginfo_relative(entity e, float tag)
1778 if (!gettaginfo_relative_ent)
1780 gettaginfo_relative_ent = spawn();
1781 gettaginfo_relative_ent.effects = EF_NODRAW;
1783 gettaginfo_relative_ent.model = e.model;
1784 gettaginfo_relative_ent.modelindex = e.modelindex;
1785 gettaginfo_relative_ent.frame = e.frame;
1786 return gettaginfo(gettaginfo_relative_ent, tag);
1791 float modeleffect_SendEntity(entity to, int sf)
1794 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1797 if(self.velocity != '0 0 0')
1799 if(self.angles != '0 0 0')
1801 if(self.avelocity != '0 0 0')
1804 WriteByte(MSG_ENTITY, f);
1805 WriteShort(MSG_ENTITY, self.modelindex);
1806 WriteByte(MSG_ENTITY, self.skin);
1807 WriteByte(MSG_ENTITY, self.frame);
1808 WriteCoord(MSG_ENTITY, self.origin.x);
1809 WriteCoord(MSG_ENTITY, self.origin.y);
1810 WriteCoord(MSG_ENTITY, self.origin.z);
1813 WriteCoord(MSG_ENTITY, self.velocity.x);
1814 WriteCoord(MSG_ENTITY, self.velocity.y);
1815 WriteCoord(MSG_ENTITY, self.velocity.z);
1819 WriteCoord(MSG_ENTITY, self.angles.x);
1820 WriteCoord(MSG_ENTITY, self.angles.y);
1821 WriteCoord(MSG_ENTITY, self.angles.z);
1825 WriteCoord(MSG_ENTITY, self.avelocity.x);
1826 WriteCoord(MSG_ENTITY, self.avelocity.y);
1827 WriteCoord(MSG_ENTITY, self.avelocity.z);
1829 WriteShort(MSG_ENTITY, self.scale * 256.0);
1830 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1831 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1832 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1833 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1838 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)
1843 e.classname = "modeleffect";
1851 e.teleport_time = t1;
1855 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1859 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1862 sz = max(e.scale, e.scale2);
1863 setsize(e, e.mins * sz, e.maxs * sz);
1864 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1867 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1869 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1872 float randombit(float bits)
1874 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1883 for(f = 1; f <= bits; f *= 2)
1892 r = (r - 1) / (n - 1);
1899 float randombits(float bits, float k, float error_return)
1903 while(k > 0 && bits != r)
1905 r += randombit(bits - r);
1914 void randombit_test(float bits, float iter)
1918 LOG_INFO(ftos(randombit(bits)), "\n");
1923 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1925 if(halflifedist > 0)
1926 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1927 else if(halflifedist < 0)
1928 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1934 .string aiment_classname;
1935 .float aiment_deadflag;
1936 void SetMovetypeFollow(entity ent, entity e)
1938 // FIXME this may not be warpzone aware
1939 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1940 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.
1941 ent.aiment = e; // make the hole follow bmodel
1942 ent.punchangle = e.angles; // the original angles of bmodel
1943 ent.view_ofs = ent.origin - e.origin; // relative origin
1944 ent.v_angle = ent.angles - e.angles; // relative angles
1945 ent.aiment_classname = strzone(e.classname);
1946 ent.aiment_deadflag = e.deadflag;
1948 void UnsetMovetypeFollow(entity ent)
1950 ent.movetype = MOVETYPE_FLY;
1951 PROJECTILE_MAKETRIGGER(ent);
1954 float LostMovetypeFollow(entity ent)
1957 if(ent.movetype != MOVETYPE_FOLLOW)
1963 if(ent.aiment.classname != ent.aiment_classname)
1965 if(ent.aiment.deadflag != ent.aiment_deadflag)
1971 float isPushable(entity e)
1980 case "droppedweapon":
1981 case "keepawayball":
1982 case "nexball_basketball":
1983 case "nexball_football":
1985 case "bullet": // antilagged bullets can't hit this either
1988 if (e.projectiledeathtype)