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;
302 case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break;
303 case "T": replacement = seconds_tostring(time); break;
306 MUTATOR_CALLHOOK(FormatMessage, escape, replacement, msg);
307 escape = format_escape;
308 replacement = format_replacement;
313 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
314 p = p + strlen(replacement);
325 >0: receives a cvar from name=argv(f) value=argv(f+1)
327 void GetCvars_handleString(string thisname, float f, .string field, string name)
332 strunzone(self.(field));
333 self.(field) = string_null;
337 if (thisname == name)
340 strunzone(self.(field));
341 self.(field) = strzone(argv(f + 1));
345 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
347 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
349 GetCvars_handleString(thisname, f, field, name);
350 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
351 if (thisname == name)
353 string s = func(strcat1(self.(field)));
354 if (s != self.(field))
356 strunzone(self.(field));
357 self.(field) = strzone(s);
361 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
368 if (thisname == name)
369 self.(field) = stof(argv(f + 1));
372 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
374 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
381 if (thisname == name)
385 self.(field) = stof(argv(f + 1));
394 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
397 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
400 o = W_FixWeaponOrder_ForceComplete(wo);
401 if(self.weaponorder_byimpulse)
403 strunzone(self.weaponorder_byimpulse);
404 self.weaponorder_byimpulse = string_null;
406 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
409 void GetCvars(float f)
411 string s = string_null;
414 s = strcat1(argv(f));
418 MUTATOR_CALLHOOK(GetCvars);
420 Notification_GetCvars();
422 ReplicateVars(this, s, f);
424 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
425 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
426 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
427 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
428 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
429 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
430 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
431 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
432 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
437 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
438 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
439 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
440 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
441 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
442 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
443 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
444 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
445 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
446 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
447 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
448 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
450 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
451 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
453 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
454 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
455 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
456 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
457 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
459 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
462 if (s == "cl_weaponpriority")
463 self.switchweapon = w_getbestweapon(self);
464 if (s == "cl_allow_uidtracking")
465 PlayerStats_GameReport_AddPlayer(self);
469 // decolorizes and team colors the player name when needed
470 string playername(entity p)
473 if (teamplay && !intermission_running && IS_PLAYER(p))
475 t = Team_ColorCode(p.team);
476 return strcat(t, strdecolorize(p.netname));
482 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
484 int i = weaponinfo.weapon;
490 if (g_lms || g_ca || allguns)
492 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
498 d = (i == WEP_SHOTGUN.m_id);
500 d = 0; // weapon is set a few lines later
502 d = !(!weaponinfo.weaponstart);
504 if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
505 d |= (i == WEP_HOOK.m_id);
506 if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
509 float t = weaponinfo.weaponstartoverride;
511 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
516 // 4: is set by default?
525 void readplayerstartcvars()
531 // initialize starting values for players
532 start_weapons = '0 0 0';
533 start_weapons_default = '0 0 0';
534 start_weapons_defaultmask = '0 0 0';
536 start_ammo_shells = 0;
537 start_ammo_nails = 0;
538 start_ammo_rockets = 0;
539 start_ammo_cells = 0;
540 start_ammo_plasma = 0;
541 start_health = cvar("g_balance_health_start");
542 start_armorvalue = cvar("g_balance_armor_start");
545 g_weaponarena_weapons = '0 0 0';
547 s = cvar_string("g_weaponarena");
548 if (s == "0" || s == "")
550 if(g_ca || g_freezetag)
554 if (s == "0" || s == "")
560 // forcibly turn off weaponarena
562 else if (s == "all" || s == "1")
565 g_weaponarena_list = "All Weapons";
566 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
568 e = get_weaponinfo(j);
569 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
570 g_weaponarena_weapons |= WepSet_FromWeapon(j);
573 else if (s == "most")
576 g_weaponarena_list = "Most Weapons";
577 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
579 e = get_weaponinfo(j);
580 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
581 if (e.spawnflags & WEP_FLAG_NORMAL)
582 g_weaponarena_weapons |= WepSet_FromWeapon(j);
585 else if (s == "none")
588 g_weaponarena_list = "No Weapons";
593 t = tokenize_console(s);
594 g_weaponarena_list = "";
595 for (i = 0; i < t; ++i)
598 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
600 e = get_weaponinfo(j);
603 g_weaponarena_weapons |= WepSet_FromWeapon(j);
604 g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
610 LOG_INFO("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
613 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
617 g_weaponarena_random = cvar("g_weaponarena_random");
619 g_weaponarena_random = 0;
620 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
624 g_weapon_stay = 0; // incompatible
625 start_weapons = g_weaponarena_weapons;
626 start_items |= IT_UNLIMITED_AMMO;
630 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
632 e = get_weaponinfo(i);
633 int w = want_weapon(e, false);
635 start_weapons |= WepSet_FromWeapon(i);
637 start_weapons_default |= WepSet_FromWeapon(i);
639 start_weapons_defaultmask |= WepSet_FromWeapon(i);
643 if(!cvar("g_use_ammunition"))
644 start_items |= IT_UNLIMITED_AMMO;
646 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
648 start_ammo_shells = 999;
649 start_ammo_nails = 999;
650 start_ammo_rockets = 999;
651 start_ammo_cells = 999;
652 start_ammo_plasma = 999;
653 start_ammo_fuel = 999;
657 start_ammo_shells = cvar("g_start_ammo_shells");
658 start_ammo_nails = cvar("g_start_ammo_nails");
659 start_ammo_rockets = cvar("g_start_ammo_rockets");
660 start_ammo_cells = cvar("g_start_ammo_cells");
661 start_ammo_plasma = cvar("g_start_ammo_plasma");
662 start_ammo_fuel = cvar("g_start_ammo_fuel");
667 warmup_start_ammo_shells = start_ammo_shells;
668 warmup_start_ammo_nails = start_ammo_nails;
669 warmup_start_ammo_rockets = start_ammo_rockets;
670 warmup_start_ammo_cells = start_ammo_cells;
671 warmup_start_ammo_plasma = start_ammo_plasma;
672 warmup_start_ammo_fuel = start_ammo_fuel;
673 warmup_start_health = start_health;
674 warmup_start_armorvalue = start_armorvalue;
675 warmup_start_weapons = start_weapons;
676 warmup_start_weapons_default = start_weapons_default;
677 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
679 if (!g_weaponarena && !g_ca && !g_freezetag)
681 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
682 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
683 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
684 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
685 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
686 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
687 warmup_start_health = cvar("g_warmup_start_health");
688 warmup_start_armorvalue = cvar("g_warmup_start_armor");
689 warmup_start_weapons = '0 0 0';
690 warmup_start_weapons_default = '0 0 0';
691 warmup_start_weapons_defaultmask = '0 0 0';
692 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
694 e = get_weaponinfo(i);
695 int w = want_weapon(e, g_warmup_allguns);
697 warmup_start_weapons |= WepSet_FromWeapon(i);
699 warmup_start_weapons_default |= WepSet_FromWeapon(i);
701 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
707 start_items |= ITEM_Jetpack.m_itemid;
709 MUTATOR_CALLHOOK(SetStartItems);
711 if ((start_items & ITEM_Jetpack.m_itemid) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
713 start_items |= ITEM_JetpackRegen.m_itemid;
714 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
715 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
718 WepSet precache_weapons = start_weapons;
719 if (g_warmup_allguns != 1)
720 precache_weapons |= warmup_start_weapons;
721 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
723 e = get_weaponinfo(i);
724 if(precache_weapons & WepSet_FromWeapon(i))
725 WEP_ACTION(i, WR_INIT);
728 start_ammo_shells = max(0, start_ammo_shells);
729 start_ammo_nails = max(0, start_ammo_nails);
730 start_ammo_rockets = max(0, start_ammo_rockets);
731 start_ammo_cells = max(0, start_ammo_cells);
732 start_ammo_plasma = max(0, start_ammo_plasma);
733 start_ammo_fuel = max(0, start_ammo_fuel);
735 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
736 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
737 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
738 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
739 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
740 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
743 float sound_allowed(float destin, entity e)
745 // sounds from world may always pass
748 if (e.classname == "body")
750 else if (e.realowner && e.realowner != e)
752 else if (e.owner && e.owner != e)
757 // sounds to self may always pass
758 if (destin == MSG_ONE)
761 // sounds by players can be removed
762 if (autocvar_bot_sound_monopoly)
763 if (IS_REAL_CLIENT(e))
765 // anything else may pass
769 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
773 if (!sound_allowed(_dest, e))
776 entno = num_for_edict(e);
777 idx = precache_sound_index(samp);
782 attenu = floor(attenu * 64);
783 vol = floor(vol * 255);
786 sflags |= SND_VOLUME;
788 sflags |= SND_ATTENUATION;
789 if (entno >= 8192 || chan < 0 || chan > 7)
790 sflags |= SND_LARGEENTITY;
792 sflags |= SND_LARGESOUND;
794 WriteByte(_dest, SVC_SOUND);
795 WriteByte(_dest, sflags);
796 if (sflags & SND_VOLUME)
797 WriteByte(_dest, vol);
798 if (sflags & SND_ATTENUATION)
799 WriteByte(_dest, attenu);
800 if (sflags & SND_LARGEENTITY)
802 WriteShort(_dest, entno);
803 WriteByte(_dest, chan);
807 WriteShort(_dest, entno * 8 + chan);
809 if (sflags & SND_LARGESOUND)
810 WriteShort(_dest, idx);
812 WriteByte(_dest, idx);
814 WriteCoord(_dest, o.x);
815 WriteCoord(_dest, o.y);
816 WriteCoord(_dest, o.z);
818 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
822 if (!sound_allowed(_dest, e))
825 o = e.origin + 0.5 * (e.mins + e.maxs);
826 soundtoat(_dest, e, o, chan, samp, vol, _atten);
828 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
830 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
832 void stopsoundto(float _dest, entity e, float chan)
836 if (!sound_allowed(_dest, e))
839 entno = num_for_edict(e);
841 if (entno >= 8192 || chan < 0 || chan > 7)
844 idx = precache_sound_index(SND(Null));
845 sflags = SND_LARGEENTITY;
847 sflags |= SND_LARGESOUND;
848 WriteByte(_dest, SVC_SOUND);
849 WriteByte(_dest, sflags);
850 WriteShort(_dest, entno);
851 WriteByte(_dest, chan);
852 if (sflags & SND_LARGESOUND)
853 WriteShort(_dest, idx);
855 WriteByte(_dest, idx);
856 WriteCoord(_dest, e.origin.x);
857 WriteCoord(_dest, e.origin.y);
858 WriteCoord(_dest, e.origin.z);
862 WriteByte(_dest, SVC_STOPSOUND);
863 WriteShort(_dest, entno * 8 + chan);
866 void stopsound(entity e, float chan)
868 if (!sound_allowed(MSG_BROADCAST, e))
871 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
872 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
875 void play2(entity e, string filename)
877 //stuffcmd(e, strcat("play2 ", filename, "\n"));
879 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
882 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
884 float spamsound(entity e, float chan, string samp, float vol, float _atten)
886 if (!sound_allowed(MSG_BROADCAST, e))
889 if (time > e.spamtime)
892 _sound(e, chan, samp, vol, _atten);
898 void play2team(float t, string filename)
902 if (autocvar_bot_sound_monopoly)
905 FOR_EACH_REALPLAYER(head)
908 play2(head, filename);
912 void play2all(string samp)
914 if (autocvar_bot_sound_monopoly)
917 _sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
920 void PrecachePlayerSounds(string f);
921 void precache_playermodel(string m)
923 float globhandle, i, n;
926 if(substring(m, -9,5) == "_lod1")
928 if(substring(m, -9,5) == "_lod2")
931 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
934 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
938 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
941 n = search_getsize(globhandle);
942 for (i = 0; i < n; ++i)
944 //print(search_getfilename(globhandle, i), "\n");
945 f = search_getfilename(globhandle, i);
946 PrecachePlayerSounds(f);
948 search_end(globhandle);
950 void precache_all_playermodels(string pattern)
952 float globhandle, i, n;
955 globhandle = search_begin(pattern, true, false);
958 n = search_getsize(globhandle);
959 for (i = 0; i < n; ++i)
961 //print(search_getfilename(globhandle, i), "\n");
962 f = search_getfilename(globhandle, i);
963 precache_playermodel(f);
965 search_end(globhandle);
970 // gamemode related things
972 // Precache all player models if desired
973 if (autocvar_sv_precacheplayermodels)
975 PrecachePlayerSounds("sound/player/default.sounds");
976 precache_all_playermodels("models/player/*.zym");
977 precache_all_playermodels("models/player/*.dpm");
978 precache_all_playermodels("models/player/*.md3");
979 precache_all_playermodels("models/player/*.psk");
980 precache_all_playermodels("models/player/*.iqm");
983 if (autocvar_sv_defaultcharacter)
986 s = autocvar_sv_defaultplayermodel_red;
988 precache_playermodel(s);
989 s = autocvar_sv_defaultplayermodel_blue;
991 precache_playermodel(s);
992 s = autocvar_sv_defaultplayermodel_yellow;
994 precache_playermodel(s);
995 s = autocvar_sv_defaultplayermodel_pink;
997 precache_playermodel(s);
998 s = autocvar_sv_defaultplayermodel;
1000 precache_playermodel(s);
1005 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1006 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1009 // gore and miscellaneous sounds
1010 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1011 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1014 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1016 if (!self.noise && self.music) // quake 3 uses the music field
1017 self.noise = self.music;
1019 // plays music for the level if there is any
1022 precache_sound (self.noise);
1023 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1029 void make_safe_for_remove(entity e)
1031 if (e.initialize_entity)
1033 entity ent, prev = world;
1034 for (ent = initialize_entity_first; ent; )
1036 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1038 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1039 // skip it in linked list
1042 prev.initialize_entity_next = ent.initialize_entity_next;
1043 ent = prev.initialize_entity_next;
1047 initialize_entity_first = ent.initialize_entity_next;
1048 ent = initialize_entity_first;
1054 ent = ent.initialize_entity_next;
1060 void objerror(string s)
1062 make_safe_for_remove(self);
1063 builtin_objerror(s);
1066 .float remove_except_protected_forbidden;
1067 void remove_except_protected(entity e)
1069 if(e.remove_except_protected_forbidden)
1070 error("not allowed to remove this at this point");
1074 void remove_unsafely(entity e)
1076 if(e.classname == "spike")
1077 error("Removing spikes is forbidden (crylink bug), please report");
1081 void remove_safely(entity e)
1083 make_safe_for_remove(e);
1087 void InitializeEntity(entity e, void(void) func, float order)
1091 if (!e || e.initialize_entity)
1093 // make a proxy initializer entity
1095 e = new(initialize_entity);
1099 e.initialize_entity = func;
1100 e.initialize_entity_order = order;
1102 cur = initialize_entity_first;
1106 if (!cur || cur.initialize_entity_order > order)
1108 // insert between prev and cur
1110 prev.initialize_entity_next = e;
1112 initialize_entity_first = e;
1113 e.initialize_entity_next = cur;
1117 cur = cur.initialize_entity_next;
1120 void InitializeEntitiesRun()
1122 entity startoflist = initialize_entity_first;
1123 initialize_entity_first = NULL;
1124 remove = remove_except_protected;
1125 for (entity e = startoflist; e; e = e.initialize_entity_next)
1127 e.remove_except_protected_forbidden = 1;
1129 for (entity e = startoflist; e; )
1131 e.remove_except_protected_forbidden = 0;
1132 e.initialize_entity_order = 0;
1133 entity next = e.initialize_entity_next;
1134 e.initialize_entity_next = NULL;
1135 var void() func = e.initialize_entity;
1136 e.initialize_entity = func_null;
1137 if (e.classname == "initialize_entity")
1139 entity wrappee = e.enemy;
1143 //dprint("Delayed initialization: ", e.classname, "\n");
1146 WITH(entity, self, e, func());
1151 backtrace(strcat("Null function in: ", e.classname, "\n"));
1155 remove = remove_unsafely;
1158 void UncustomizeEntitiesRun()
1160 for (entity e = NULL; (e = findfloat(e, uncustomizeentityforclient_set, 1)); )
1162 WITH(entity, self, e, e.uncustomizeentityforclient());
1165 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1167 e.customizeentityforclient = customizer;
1168 e.uncustomizeentityforclient = uncustomizer;
1169 e.uncustomizeentityforclient_set = !!uncustomizer;
1172 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1176 if (e.classname == "")
1177 e.classname = "net_linked";
1179 if (e.model == "" || self.modelindex == 0)
1183 setmodel(e, MDL_Null);
1187 e.SendEntity = sendfunc;
1188 e.SendFlags = 0xFFFFFF;
1191 e.effects |= EF_NODEPTHTEST;
1195 e.nextthink = time + dt;
1196 e.think = SUB_Remove;
1201 .float(entity) isEliminated;
1202 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1206 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1207 WriteByte(MSG_ENTITY, sendflags);
1211 for(i = 1; i <= maxclients; i += 8)
1213 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1215 if(eliminatedPlayers.isEliminated(e))
1218 WriteByte(MSG_ENTITY, f);
1225 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1227 if(eliminatedPlayers)
1229 backtrace("Can't spawn eliminatedPlayers again!");
1232 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1233 eliminatedPlayers.isEliminated = isEliminated_func;
1237 void adaptor_think2touch()
1246 void adaptor_think2use()
1258 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1260 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
1261 self.projectiledeathtype |= HITTYPE_SPLASH;
1262 adaptor_think2use();
1265 // deferred dropping
1266 void DropToFloor_Handler()
1268 builtin_droptofloor();
1269 self.dropped_origin = self.origin;
1274 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1279 float trace_hits_box_a0, trace_hits_box_a1;
1281 float trace_hits_box_1d(float end, float thmi, float thma)
1285 // just check if x is in range
1293 // do the trace with respect to x
1294 // 0 -> end has to stay in thmi -> thma
1295 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1296 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1297 if (trace_hits_box_a0 > trace_hits_box_a1)
1303 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1308 // now it is a trace from 0 to end
1310 trace_hits_box_a0 = 0;
1311 trace_hits_box_a1 = 1;
1313 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1315 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1317 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1323 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1325 return trace_hits_box(start, end, thmi - ma, thma - mi);
1328 float SUB_NoImpactCheck()
1330 // zero hitcontents = this is not the real impact, but either the
1331 // mirror-impact of something hitting the projectile instead of the
1332 // projectile hitting the something, or a touchareagrid one. Neither of
1333 // these stop the projectile from moving, so...
1334 if(trace_dphitcontents == 0)
1336 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1337 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));
1340 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1342 if (other == world && self.size != '0 0 0')
1345 tic = self.velocity * sys_frametime;
1346 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1347 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1348 if (trace_fraction >= 1)
1350 LOG_TRACE("Odd... did not hit...?\n");
1352 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1354 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1362 #define SUB_OwnerCheck() (other && (other == self.owner))
1364 void W_Crylink_Dequeue(entity e);
1365 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1367 if(SUB_OwnerCheck())
1369 if(SUB_NoImpactCheck())
1371 if(self.classname == "nade")
1372 return false; // no checks here
1373 else if(self.classname == "grapplinghook")
1374 RemoveGrapplingHook(self.realowner);
1375 else if(self.classname == "spike")
1377 W_Crylink_Dequeue(self);
1384 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1385 UpdateCSQCProjectile(self);
1390 void URI_Get_Callback(float id, float status, string data)
1392 if(url_URI_Get_Callback(id, status, data))
1396 else if (id == URI_GET_DISCARD)
1400 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1403 Curl_URI_Get_Callback(id, status, data);
1405 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1408 OnlineBanList_URI_Get_Callback(id, status, data);
1412 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1416 string uid2name(string myuid) {
1418 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1420 // FIXME remove this later after 0.6 release
1421 // convert old style broken records to correct style
1424 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1427 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1428 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1433 s = "^1Unregistered Player";
1437 float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1440 vector start, org, delta, end, enddown, mstart;
1443 m = e.dphitcontentsmask;
1444 e.dphitcontentsmask = goodcontents | badcontents;
1447 delta = boundmax - boundmin;
1451 for (i = 0; i < attempts; ++i)
1453 start.x = org.x + random() * delta.x;
1454 start.y = org.y + random() * delta.y;
1455 start.z = org.z + random() * delta.z;
1457 // rule 1: start inside world bounds, and outside
1458 // solid, and don't start from somewhere where you can
1459 // fall down to evil
1460 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1461 if (trace_fraction >= 1)
1463 if (trace_startsolid)
1465 if (trace_dphitcontents & badcontents)
1467 if (trace_dphitq3surfaceflags & badsurfaceflags)
1470 // rule 2: if we are too high, lower the point
1471 if (trace_fraction * delta.z > maxaboveground)
1472 start = trace_endpos + '0 0 1' * maxaboveground;
1473 enddown = trace_endpos;
1475 // rule 3: make sure we aren't outside the map. This only works
1476 // for somewhat well formed maps. A good rule of thumb is that
1477 // the map should have a convex outside hull.
1478 // these can be traceLINES as we already verified the starting box
1479 mstart = start + 0.5 * (e.mins + e.maxs);
1480 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1481 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1483 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1484 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1486 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1487 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1489 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1490 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1492 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1493 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1496 // rule 4: we must "see" some spawnpoint or item
1497 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1498 if(checkpvs(mstart, sp))
1499 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1503 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1504 if(checkpvs(mstart, sp))
1505 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1511 // find a random vector to "look at"
1512 end.x = org.x + random() * delta.x;
1513 end.y = org.y + random() * delta.y;
1514 end.z = org.z + random() * delta.z;
1515 end = start + normalize(end - start) * vlen(delta);
1517 // rule 4: start TO end must not be too short
1518 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1519 if (trace_startsolid)
1521 if (trace_fraction < minviewdistance / vlen(delta))
1524 // rule 5: don't want to look at sky
1525 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1528 // rule 6: we must not end up in trigger_hurt
1529 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1535 e.dphitcontentsmask = m;
1539 setorigin(e, start);
1540 e.angles = vectoangles(end - start);
1541 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1548 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1550 return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
1553 void write_recordmarker(entity pl, float tstart, float dt)
1555 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1557 // also write a marker into demo files for demotc-race-record-extractor to find
1560 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1561 " ", ftos(tstart), " ", ftos(dt), "\n"));
1564 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1577 if(allowcenter) // 2: allow center handedness
1590 if(allowcenter) // 2: allow center handedness
1606 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1611 if (autocvar_g_shootfromeye)
1615 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1616 else { vecs.y = 0; vecs.z -= 2; }
1624 else if (autocvar_g_shootfromcenter)
1629 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1639 else if (autocvar_g_shootfromclient)
1641 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1646 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1648 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1652 void attach_sameorigin(entity e, entity to, string tag)
1654 vector org, t_forward, t_left, t_up, e_forward, e_up;
1657 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1658 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1659 t_forward = v_forward * tagscale;
1660 t_left = v_right * -tagscale;
1661 t_up = v_up * tagscale;
1663 e.origin_x = org * t_forward;
1664 e.origin_y = org * t_left;
1665 e.origin_z = org * t_up;
1667 // current forward and up directions
1668 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1669 e.angles = AnglesTransform_FromVAngles(e.angles);
1671 e.angles = AnglesTransform_FromAngles(e.angles);
1672 fixedmakevectors(e.angles);
1674 // untransform forward, up!
1675 e_forward.x = v_forward * t_forward;
1676 e_forward.y = v_forward * t_left;
1677 e_forward.z = v_forward * t_up;
1678 e_up.x = v_up * t_forward;
1679 e_up.y = v_up * t_left;
1680 e_up.z = v_up * t_up;
1682 e.angles = fixedvectoangles2(e_forward, e_up);
1683 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1684 e.angles = AnglesTransform_ToVAngles(e.angles);
1686 e.angles = AnglesTransform_ToAngles(e.angles);
1688 setattachment(e, to, tag);
1689 setorigin(e, e.origin);
1692 void detach_sameorigin(entity e)
1695 org = gettaginfo(e, 0);
1696 e.angles = fixedvectoangles2(v_forward, v_up);
1697 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1698 e.angles = AnglesTransform_ToVAngles(e.angles);
1700 e.angles = AnglesTransform_ToAngles(e.angles);
1702 setattachment(e, world, "");
1703 setorigin(e, e.origin);
1706 void follow_sameorigin(entity e, entity to)
1708 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1709 e.aiment = to; // make the hole follow bmodel
1710 e.punchangle = to.angles; // the original angles of bmodel
1711 e.view_ofs = e.origin - to.origin; // relative origin
1712 e.v_angle = e.angles - to.angles; // relative angles
1715 void unfollow_sameorigin(entity e)
1717 e.movetype = MOVETYPE_NONE;
1720 entity gettaginfo_relative_ent;
1721 vector gettaginfo_relative(entity e, float tag)
1723 if (!gettaginfo_relative_ent)
1725 gettaginfo_relative_ent = spawn();
1726 gettaginfo_relative_ent.effects = EF_NODRAW;
1728 gettaginfo_relative_ent.model = e.model;
1729 gettaginfo_relative_ent.modelindex = e.modelindex;
1730 gettaginfo_relative_ent.frame = e.frame;
1731 return gettaginfo(gettaginfo_relative_ent, tag);
1736 float modeleffect_SendEntity(entity to, int sf)
1739 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1742 if(self.velocity != '0 0 0')
1744 if(self.angles != '0 0 0')
1746 if(self.avelocity != '0 0 0')
1749 WriteByte(MSG_ENTITY, f);
1750 WriteShort(MSG_ENTITY, self.modelindex);
1751 WriteByte(MSG_ENTITY, self.skin);
1752 WriteByte(MSG_ENTITY, self.frame);
1753 WriteCoord(MSG_ENTITY, self.origin.x);
1754 WriteCoord(MSG_ENTITY, self.origin.y);
1755 WriteCoord(MSG_ENTITY, self.origin.z);
1758 WriteCoord(MSG_ENTITY, self.velocity.x);
1759 WriteCoord(MSG_ENTITY, self.velocity.y);
1760 WriteCoord(MSG_ENTITY, self.velocity.z);
1764 WriteCoord(MSG_ENTITY, self.angles.x);
1765 WriteCoord(MSG_ENTITY, self.angles.y);
1766 WriteCoord(MSG_ENTITY, self.angles.z);
1770 WriteCoord(MSG_ENTITY, self.avelocity.x);
1771 WriteCoord(MSG_ENTITY, self.avelocity.y);
1772 WriteCoord(MSG_ENTITY, self.avelocity.z);
1774 WriteShort(MSG_ENTITY, self.scale * 256.0);
1775 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1776 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1777 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1778 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1783 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)
1788 e.classname = "modeleffect";
1796 e.teleport_time = t1;
1800 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1804 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1807 sz = max(e.scale, e.scale2);
1808 setsize(e, e.mins * sz, e.maxs * sz);
1809 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1812 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1814 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1817 float randombit(float bits)
1819 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1828 for(f = 1; f <= bits; f *= 2)
1837 r = (r - 1) / (n - 1);
1844 float randombits(float bits, float k, float error_return)
1848 while(k > 0 && bits != r)
1850 r += randombit(bits - r);
1859 void randombit_test(float bits, float iter)
1863 LOG_INFO(ftos(randombit(bits)), "\n");
1868 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1870 if(halflifedist > 0)
1871 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1872 else if(halflifedist < 0)
1873 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1879 .string aiment_classname;
1880 .float aiment_deadflag;
1881 void SetMovetypeFollow(entity ent, entity e)
1883 // FIXME this may not be warpzone aware
1884 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1885 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.
1886 ent.aiment = e; // make the hole follow bmodel
1887 ent.punchangle = e.angles; // the original angles of bmodel
1888 ent.view_ofs = ent.origin - e.origin; // relative origin
1889 ent.v_angle = ent.angles - e.angles; // relative angles
1890 ent.aiment_classname = strzone(e.classname);
1891 ent.aiment_deadflag = e.deadflag;
1893 void UnsetMovetypeFollow(entity ent)
1895 ent.movetype = MOVETYPE_FLY;
1896 PROJECTILE_MAKETRIGGER(ent);
1899 float LostMovetypeFollow(entity ent)
1902 if(ent.movetype != MOVETYPE_FOLLOW)
1908 if(ent.aiment.classname != ent.aiment_classname)
1910 if(ent.aiment.deadflag != ent.aiment_deadflag)
1916 float isPushable(entity e)
1927 case "droppedweapon":
1928 case "keepawayball":
1929 case "nexball_basketball":
1930 case "nexball_football":
1932 case "bullet": // antilagged bullets can't hit this either
1935 if (e.projectiledeathtype)