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
975 precache_model ("models/misc/chatbubble.spr");
976 precache_model("models/ice/ice.md3");
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_model ("models/hook.md3");
1018 precache_sound ("misc/armorimpact.wav");
1019 precache_sound ("misc/bodyimpact1.wav");
1020 precache_sound ("misc/bodyimpact2.wav");
1021 precache_sound ("misc/gib.wav");
1022 precache_sound ("misc/gib_splat01.wav");
1023 precache_sound ("misc/gib_splat02.wav");
1024 precache_sound ("misc/gib_splat03.wav");
1025 precache_sound ("misc/gib_splat04.wav");
1026 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1027 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1028 precache_sound ("misc/null.wav");
1029 precache_sound ("misc/spawn.wav");
1030 precache_sound ("misc/talk.wav");
1031 precache_sound ("misc/teleport.wav");
1032 precache_sound ("misc/poweroff.wav");
1033 precache_sound ("player/lava.wav");
1034 precache_sound ("player/slime.wav");
1036 precache_model ("models/sprites/0.spr32");
1037 precache_model ("models/sprites/1.spr32");
1038 precache_model ("models/sprites/2.spr32");
1039 precache_model ("models/sprites/3.spr32");
1040 precache_model ("models/sprites/4.spr32");
1041 precache_model ("models/sprites/5.spr32");
1042 precache_model ("models/sprites/6.spr32");
1043 precache_model ("models/sprites/7.spr32");
1044 precache_model ("models/sprites/8.spr32");
1045 precache_model ("models/sprites/9.spr32");
1046 precache_model ("models/sprites/10.spr32");
1048 // common weapon precaches
1049 precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here
1050 precache_sound (W_Sound("weapon_switch"));
1051 precache_sound (W_Sound("weaponpickup"));
1052 precache_sound (W_Sound("unavailable"));
1053 precache_sound (W_Sound("dryfire"));
1054 if (g_grappling_hook)
1056 precache_sound (W_Sound("hook_fire")); // hook
1057 precache_sound (W_Sound("hook_impact")); // hook
1060 precache_model("models/elaser.mdl");
1061 precache_model("models/laser.mdl");
1062 precache_model("models/ebomb.mdl");
1065 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1067 if (!self.noise && self.music) // quake 3 uses the music field
1068 self.noise = self.music;
1070 // plays music for the level if there is any
1073 precache_sound (self.noise);
1074 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1078 #include "precache-for-csqc.inc"
1082 void make_safe_for_remove(entity e)
1084 if (e.initialize_entity)
1086 entity ent, prev = world;
1087 for (ent = initialize_entity_first; ent; )
1089 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1091 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1092 // skip it in linked list
1095 prev.initialize_entity_next = ent.initialize_entity_next;
1096 ent = prev.initialize_entity_next;
1100 initialize_entity_first = ent.initialize_entity_next;
1101 ent = initialize_entity_first;
1107 ent = ent.initialize_entity_next;
1113 void objerror(string s)
1115 make_safe_for_remove(self);
1116 builtin_objerror(s);
1119 .float remove_except_protected_forbidden;
1120 void remove_except_protected(entity e)
1122 if(e.remove_except_protected_forbidden)
1123 error("not allowed to remove this at this point");
1127 void remove_unsafely(entity e)
1129 if(e.classname == "spike")
1130 error("Removing spikes is forbidden (crylink bug), please report");
1134 void remove_safely(entity e)
1136 make_safe_for_remove(e);
1140 void InitializeEntity(entity e, void(void) func, float order)
1144 if (!e || e.initialize_entity)
1146 // make a proxy initializer entity
1150 e.classname = "initialize_entity";
1154 e.initialize_entity = func;
1155 e.initialize_entity_order = order;
1157 cur = initialize_entity_first;
1161 if (!cur || cur.initialize_entity_order > order)
1163 // insert between prev and cur
1165 prev.initialize_entity_next = e;
1167 initialize_entity_first = e;
1168 e.initialize_entity_next = cur;
1172 cur = cur.initialize_entity_next;
1175 void InitializeEntitiesRun()
1178 startoflist = initialize_entity_first;
1179 initialize_entity_first = world;
1180 remove = remove_except_protected;
1181 for (self = startoflist; self; self = self.initialize_entity_next)
1183 self.remove_except_protected_forbidden = 1;
1185 for (self = startoflist; self; )
1188 var void(void) func;
1189 e = self.initialize_entity_next;
1190 func = self.initialize_entity;
1191 self.initialize_entity_order = 0;
1192 self.initialize_entity = func_null;
1193 self.initialize_entity_next = world;
1194 self.remove_except_protected_forbidden = 0;
1195 if (self.classname == "initialize_entity")
1199 builtin_remove(self);
1202 //dprint("Delayed initialization: ", self.classname, "\n");
1208 backtrace(strcat("Null function in: ", self.classname, "\n"));
1212 remove = remove_unsafely;
1215 void UncustomizeEntitiesRun()
1219 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1220 self.uncustomizeentityforclient();
1223 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1225 e.customizeentityforclient = customizer;
1226 e.uncustomizeentityforclient = uncustomizer;
1227 e.uncustomizeentityforclient_set = !!uncustomizer;
1230 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1234 if (e.classname == "")
1235 e.classname = "net_linked";
1237 if (e.model == "" || self.modelindex == 0)
1241 setmodel(e, "null");
1245 e.SendEntity = sendfunc;
1246 e.SendFlags = 0xFFFFFF;
1249 e.effects |= EF_NODEPTHTEST;
1253 e.nextthink = time + dt;
1254 e.think = SUB_Remove;
1259 .float(entity) isEliminated;
1260 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1264 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1265 WriteByte(MSG_ENTITY, sendflags);
1269 for(i = 1; i <= maxclients; i += 8)
1271 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1273 if(eliminatedPlayers.isEliminated(e))
1276 WriteByte(MSG_ENTITY, f);
1283 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1285 if(eliminatedPlayers)
1287 backtrace("Can't spawn eliminatedPlayers again!");
1290 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1291 eliminatedPlayers.isEliminated = isEliminated_func;
1295 void adaptor_think2touch()
1304 void adaptor_think2use()
1316 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1318 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
1319 self.projectiledeathtype |= HITTYPE_SPLASH;
1320 adaptor_think2use();
1323 // deferred dropping
1324 void DropToFloor_Handler()
1326 builtin_droptofloor();
1327 self.dropped_origin = self.origin;
1332 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1337 float trace_hits_box_a0, trace_hits_box_a1;
1339 float trace_hits_box_1d(float end, float thmi, float thma)
1343 // just check if x is in range
1351 // do the trace with respect to x
1352 // 0 -> end has to stay in thmi -> thma
1353 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1354 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1355 if (trace_hits_box_a0 > trace_hits_box_a1)
1361 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1366 // now it is a trace from 0 to end
1368 trace_hits_box_a0 = 0;
1369 trace_hits_box_a1 = 1;
1371 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1373 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1375 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1381 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1383 return trace_hits_box(start, end, thmi - ma, thma - mi);
1386 float SUB_NoImpactCheck()
1388 // zero hitcontents = this is not the real impact, but either the
1389 // mirror-impact of something hitting the projectile instead of the
1390 // projectile hitting the something, or a touchareagrid one. Neither of
1391 // these stop the projectile from moving, so...
1392 if(trace_dphitcontents == 0)
1394 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1395 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));
1398 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1400 if (other == world && self.size != '0 0 0')
1403 tic = self.velocity * sys_frametime;
1404 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1405 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1406 if (trace_fraction >= 1)
1408 LOG_TRACE("Odd... did not hit...?\n");
1410 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1412 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1420 #define SUB_OwnerCheck() (other && (other == self.owner))
1422 void W_Crylink_Dequeue(entity e);
1423 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1425 if(SUB_OwnerCheck())
1427 if(SUB_NoImpactCheck())
1429 if(self.classname == "nade")
1430 return false; // no checks here
1431 else if(self.classname == "grapplinghook")
1432 RemoveGrapplingHook(self.realowner);
1433 else if(self.classname == "spike")
1435 W_Crylink_Dequeue(self);
1442 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1443 UpdateCSQCProjectile(self);
1448 void URI_Get_Callback(float id, float status, string data)
1450 if(url_URI_Get_Callback(id, status, data))
1454 else if (id == URI_GET_DISCARD)
1458 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1461 Curl_URI_Get_Callback(id, status, data);
1463 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1466 OnlineBanList_URI_Get_Callback(id, status, data);
1470 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1474 string uid2name(string myuid) {
1476 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1478 // FIXME remove this later after 0.6 release
1479 // convert old style broken records to correct style
1482 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1485 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1486 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1491 s = "^1Unregistered Player";
1495 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1498 vector start, org, delta, end, enddown, mstart;
1501 m = e.dphitcontentsmask;
1502 e.dphitcontentsmask = goodcontents | badcontents;
1505 delta = world.maxs - world.mins;
1509 for (i = 0; i < attempts; ++i)
1511 start.x = org.x + random() * delta.x;
1512 start.y = org.y + random() * delta.y;
1513 start.z = org.z + random() * delta.z;
1515 // rule 1: start inside world bounds, and outside
1516 // solid, and don't start from somewhere where you can
1517 // fall down to evil
1518 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1519 if (trace_fraction >= 1)
1521 if (trace_startsolid)
1523 if (trace_dphitcontents & badcontents)
1525 if (trace_dphitq3surfaceflags & badsurfaceflags)
1528 // rule 2: if we are too high, lower the point
1529 if (trace_fraction * delta.z > maxaboveground)
1530 start = trace_endpos + '0 0 1' * maxaboveground;
1531 enddown = trace_endpos;
1533 // rule 3: make sure we aren't outside the map. This only works
1534 // for somewhat well formed maps. A good rule of thumb is that
1535 // the map should have a convex outside hull.
1536 // these can be traceLINES as we already verified the starting box
1537 mstart = start + 0.5 * (e.mins + e.maxs);
1538 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1539 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1541 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1542 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1544 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1545 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1547 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1548 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1550 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1551 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1554 // rule 4: we must "see" some spawnpoint or item
1555 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1556 if(checkpvs(mstart, sp))
1557 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1561 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1562 if(checkpvs(mstart, sp))
1563 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1569 // find a random vector to "look at"
1570 end.x = org.x + random() * delta.x;
1571 end.y = org.y + random() * delta.y;
1572 end.z = org.z + random() * delta.z;
1573 end = start + normalize(end - start) * vlen(delta);
1575 // rule 4: start TO end must not be too short
1576 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1577 if (trace_startsolid)
1579 if (trace_fraction < minviewdistance / vlen(delta))
1582 // rule 5: don't want to look at sky
1583 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1586 // rule 6: we must not end up in trigger_hurt
1587 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1593 e.dphitcontentsmask = m;
1597 setorigin(e, start);
1598 e.angles = vectoangles(end - start);
1599 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1606 void write_recordmarker(entity pl, float tstart, float dt)
1608 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1610 // also write a marker into demo files for demotc-race-record-extractor to find
1613 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1614 " ", ftos(tstart), " ", ftos(dt), "\n"));
1617 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1630 if(allowcenter) // 2: allow center handedness
1643 if(allowcenter) // 2: allow center handedness
1659 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1664 if (autocvar_g_shootfromeye)
1668 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1669 else { vecs.y = 0; vecs.z -= 2; }
1677 else if (autocvar_g_shootfromcenter)
1682 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1692 else if (autocvar_g_shootfromclient)
1694 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1699 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1701 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1705 void attach_sameorigin(entity e, entity to, string tag)
1707 vector org, t_forward, t_left, t_up, e_forward, e_up;
1710 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1711 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1712 t_forward = v_forward * tagscale;
1713 t_left = v_right * -tagscale;
1714 t_up = v_up * tagscale;
1716 e.origin_x = org * t_forward;
1717 e.origin_y = org * t_left;
1718 e.origin_z = org * t_up;
1720 // current forward and up directions
1721 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1722 e.angles = AnglesTransform_FromVAngles(e.angles);
1724 e.angles = AnglesTransform_FromAngles(e.angles);
1725 fixedmakevectors(e.angles);
1727 // untransform forward, up!
1728 e_forward.x = v_forward * t_forward;
1729 e_forward.y = v_forward * t_left;
1730 e_forward.z = v_forward * t_up;
1731 e_up.x = v_up * t_forward;
1732 e_up.y = v_up * t_left;
1733 e_up.z = v_up * t_up;
1735 e.angles = fixedvectoangles2(e_forward, e_up);
1736 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1737 e.angles = AnglesTransform_ToVAngles(e.angles);
1739 e.angles = AnglesTransform_ToAngles(e.angles);
1741 setattachment(e, to, tag);
1742 setorigin(e, e.origin);
1745 void detach_sameorigin(entity e)
1748 org = gettaginfo(e, 0);
1749 e.angles = fixedvectoangles2(v_forward, v_up);
1750 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1751 e.angles = AnglesTransform_ToVAngles(e.angles);
1753 e.angles = AnglesTransform_ToAngles(e.angles);
1755 setattachment(e, world, "");
1756 setorigin(e, e.origin);
1759 void follow_sameorigin(entity e, entity to)
1761 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1762 e.aiment = to; // make the hole follow bmodel
1763 e.punchangle = to.angles; // the original angles of bmodel
1764 e.view_ofs = e.origin - to.origin; // relative origin
1765 e.v_angle = e.angles - to.angles; // relative angles
1768 void unfollow_sameorigin(entity e)
1770 e.movetype = MOVETYPE_NONE;
1773 entity gettaginfo_relative_ent;
1774 vector gettaginfo_relative(entity e, float tag)
1776 if (!gettaginfo_relative_ent)
1778 gettaginfo_relative_ent = spawn();
1779 gettaginfo_relative_ent.effects = EF_NODRAW;
1781 gettaginfo_relative_ent.model = e.model;
1782 gettaginfo_relative_ent.modelindex = e.modelindex;
1783 gettaginfo_relative_ent.frame = e.frame;
1784 return gettaginfo(gettaginfo_relative_ent, tag);
1789 float modeleffect_SendEntity(entity to, int sf)
1792 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1795 if(self.velocity != '0 0 0')
1797 if(self.angles != '0 0 0')
1799 if(self.avelocity != '0 0 0')
1802 WriteByte(MSG_ENTITY, f);
1803 WriteShort(MSG_ENTITY, self.modelindex);
1804 WriteByte(MSG_ENTITY, self.skin);
1805 WriteByte(MSG_ENTITY, self.frame);
1806 WriteCoord(MSG_ENTITY, self.origin.x);
1807 WriteCoord(MSG_ENTITY, self.origin.y);
1808 WriteCoord(MSG_ENTITY, self.origin.z);
1811 WriteCoord(MSG_ENTITY, self.velocity.x);
1812 WriteCoord(MSG_ENTITY, self.velocity.y);
1813 WriteCoord(MSG_ENTITY, self.velocity.z);
1817 WriteCoord(MSG_ENTITY, self.angles.x);
1818 WriteCoord(MSG_ENTITY, self.angles.y);
1819 WriteCoord(MSG_ENTITY, self.angles.z);
1823 WriteCoord(MSG_ENTITY, self.avelocity.x);
1824 WriteCoord(MSG_ENTITY, self.avelocity.y);
1825 WriteCoord(MSG_ENTITY, self.avelocity.z);
1827 WriteShort(MSG_ENTITY, self.scale * 256.0);
1828 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1829 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1830 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1831 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1836 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)
1841 e.classname = "modeleffect";
1849 e.teleport_time = t1;
1853 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1857 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1860 sz = max(e.scale, e.scale2);
1861 setsize(e, e.mins * sz, e.maxs * sz);
1862 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1865 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1867 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1870 float randombit(float bits)
1872 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1881 for(f = 1; f <= bits; f *= 2)
1890 r = (r - 1) / (n - 1);
1897 float randombits(float bits, float k, float error_return)
1901 while(k > 0 && bits != r)
1903 r += randombit(bits - r);
1912 void randombit_test(float bits, float iter)
1916 LOG_INFO(ftos(randombit(bits)), "\n");
1921 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1923 if(halflifedist > 0)
1924 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1925 else if(halflifedist < 0)
1926 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1932 .string aiment_classname;
1933 .float aiment_deadflag;
1934 void SetMovetypeFollow(entity ent, entity e)
1936 // FIXME this may not be warpzone aware
1937 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1938 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.
1939 ent.aiment = e; // make the hole follow bmodel
1940 ent.punchangle = e.angles; // the original angles of bmodel
1941 ent.view_ofs = ent.origin - e.origin; // relative origin
1942 ent.v_angle = ent.angles - e.angles; // relative angles
1943 ent.aiment_classname = strzone(e.classname);
1944 ent.aiment_deadflag = e.deadflag;
1946 void UnsetMovetypeFollow(entity ent)
1948 ent.movetype = MOVETYPE_FLY;
1949 PROJECTILE_MAKETRIGGER(ent);
1952 float LostMovetypeFollow(entity ent)
1955 if(ent.movetype != MOVETYPE_FOLLOW)
1961 if(ent.aiment.classname != ent.aiment_classname)
1963 if(ent.aiment.deadflag != ent.aiment_deadflag)
1969 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)