1 #include "miscfunctions.qh"
3 #include "command/common.qh"
4 #include "constants.qh"
7 #include "mutators/all.qh"
9 #include "weapons/accuracy.qh"
10 #include "weapons/csqcprojectile.qh"
11 #include "weapons/selection.qh"
12 #include "../common/command/generic.qh"
13 #include "../common/constants.qh"
14 #include "../common/deathtypes/all.qh"
15 #include "../common/mapinfo.qh"
16 #include "../common/notifications.qh"
17 #include "../common/playerstats.qh"
18 #include "../common/teams.qh"
19 #include "../common/triggers/subs.qh"
20 #include "../common/util.qh"
21 #include "../common/turrets/sv_turrets.qh"
22 #include "../common/weapons/all.qh"
23 #include "../common/vehicles/sv_vehicles.qh"
24 #include "../common/vehicles/vehicle.qh"
25 #include "../common/items/all.qc"
26 #include "../lib/csqcmodel/sv_model.qh"
27 #include "../lib/warpzone/anglestransform.qh"
28 #include "../lib/warpzone/server.qh"
30 void crosshair_trace(entity pl)
32 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));
34 void crosshair_trace_plusvisibletriggers(entity pl)
38 first = findchainfloat(solid, SOLID_TRIGGER);
40 for (e = first; e; e = e.chain)
46 for (e = first; e; e = e.chain)
47 e.solid = SOLID_TRIGGER;
49 void WarpZone_crosshair_trace(entity pl)
51 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));
55 string admin_name(void)
57 if(autocvar_sv_adminnick != "")
58 return autocvar_sv_adminnick;
60 return "SERVER ADMIN";
63 void DistributeEvenly_Init(float amount, float totalweight)
65 if (DistributeEvenly_amount)
67 LOG_TRACE("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
68 LOG_TRACE(ftos(DistributeEvenly_totalweight), " left!)\n");
71 DistributeEvenly_amount = 0;
73 DistributeEvenly_amount = amount;
74 DistributeEvenly_totalweight = totalweight;
76 float DistributeEvenly_Get(float weight)
81 f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
82 DistributeEvenly_totalweight -= weight;
83 DistributeEvenly_amount -= f;
86 float DistributeEvenly_GetRandomized(float weight)
91 f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
92 DistributeEvenly_totalweight -= weight;
93 DistributeEvenly_amount -= f;
98 void GameLogEcho(string s)
103 if (autocvar_sv_eventlog_files)
108 matches = autocvar_sv_eventlog_files_counter + 1;
109 cvar_set("sv_eventlog_files_counter", itos(matches));
112 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
113 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
114 logfile = fopen(fn, FILE_APPEND);
115 fputs(logfile, ":logversion:3\n");
119 if (autocvar_sv_eventlog_files_timestamps)
120 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
122 fputs(logfile, strcat(s, "\n"));
125 if (autocvar_sv_eventlog_console)
134 // will be opened later
139 if (logfile_open && logfile >= 0)
146 entity findnearest(vector point, .string field, string value, vector axismod)
157 localhead = find(world, field, value);
160 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
161 dist = localhead.oldorigin;
163 dist = localhead.origin;
165 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
168 for (i = 0; i < num_nearest; ++i)
170 if (len < nearest_length[i])
174 // now i tells us where to insert at
175 // INSERTION SORT! YOU'VE SEEN IT! RUN!
176 if (i < NUM_NEAREST_ENTITIES)
178 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
180 nearest_length[j + 1] = nearest_length[j];
181 nearest_entity[j + 1] = nearest_entity[j];
183 nearest_length[i] = len;
184 nearest_entity[i] = localhead;
185 if (num_nearest < NUM_NEAREST_ENTITIES)
186 num_nearest = num_nearest + 1;
189 localhead = find(localhead, field, value);
192 // now use the first one from our list that we can see
193 for (i = 0; i < num_nearest; ++i)
195 traceline(point, nearest_entity[i].origin, true, world);
196 if (trace_fraction == 1)
200 LOG_TRACE("Nearest point (");
201 LOG_TRACE(nearest_entity[0].netname);
202 LOG_TRACE(") is not visible, using a visible one.\n");
204 return nearest_entity[i];
208 if (num_nearest == 0)
211 LOG_TRACE("Not seeing any location point, using nearest as fallback.\n");
213 dprint("Candidates were: ");
214 for(j = 0; j < num_nearest; ++j)
218 dprint(nearest_entity[j].netname);
223 return nearest_entity[0];
226 string NearestLocation(vector p)
231 loc = findnearest(p, classname, "target_location", '1 1 1');
238 loc = findnearest(p, target, "###item###", '1 1 4');
245 string formatmessage(string msg)
257 ammoitems = "batteries";
258 if(self.items & ITEM_Plasma.m_itemid) ammoitems = ITEM_Plasma.m_name;
259 if(self.items & ITEM_Cells.m_itemid) ammoitems = ITEM_Cells.m_name;
260 if(self.items & ITEM_Rockets.m_itemid) ammoitems = ITEM_Rockets.m_name;
261 if(self.items & ITEM_Shells.m_itemid) ammoitems = ITEM_Shells.m_name;
263 WarpZone_crosshair_trace(self);
264 cursor = trace_endpos;
265 cursor_ent = trace_ent;
269 break; // too many replacements
272 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
273 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
286 replacement = substring(msg, p, 2);
287 escape = substring(msg, p + 1, 1);
291 case "%": replacement = "%"; break;
292 case "\\":replacement = "\\"; break;
293 case "n": replacement = "\n"; break;
294 case "a": replacement = ftos(floor(self.armorvalue)); break;
295 case "h": replacement = ftos(floor(self.health)); break;
296 case "l": replacement = NearestLocation(self.origin); break;
297 case "y": replacement = NearestLocation(cursor); break;
298 case "d": replacement = NearestLocation(self.death_origin); break;
299 case "w": replacement = WEP_NAME((!self.weapon) ? (!self.switchweapon ? self.cnt : self.switchweapon) : self.weapon); break;
300 case "W": replacement = ammoitems; break;
301 case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
302 case "s": replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); break;
303 case "S": replacement = ftos(vlen(self.velocity)); break;
304 case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break;
305 case "T": replacement = seconds_tostring(floor(time - game_starttime)); break;
308 MUTATOR_CALLHOOK(FormatMessage, escape, replacement, msg);
309 escape = format_escape;
310 replacement = format_replacement;
315 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
316 p = p + strlen(replacement);
327 >0: receives a cvar from name=argv(f) value=argv(f+1)
329 void GetCvars_handleString(string thisname, float f, .string field, string name)
334 strunzone(self.(field));
335 self.(field) = string_null;
339 if (thisname == name)
342 strunzone(self.(field));
343 self.(field) = strzone(argv(f + 1));
347 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
349 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
351 GetCvars_handleString(thisname, f, field, name);
352 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
353 if (thisname == name)
355 string s = func(strcat1(self.(field)));
356 if (s != self.(field))
358 strunzone(self.(field));
359 self.(field) = strzone(s);
363 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
370 if (thisname == name)
371 self.(field) = stof(argv(f + 1));
374 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
376 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
383 if (thisname == name)
387 self.(field) = stof(argv(f + 1));
396 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
399 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
402 o = W_FixWeaponOrder_ForceComplete(wo);
403 if(self.weaponorder_byimpulse)
405 strunzone(self.weaponorder_byimpulse);
406 self.weaponorder_byimpulse = string_null;
408 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
411 void GetCvars(float f)
413 string s = string_null;
416 s = strcat1(argv(f));
420 MUTATOR_CALLHOOK(GetCvars);
422 Notification_GetCvars();
424 ReplicateVars(this, s, f);
426 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
427 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
428 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
429 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
430 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
431 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
432 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
437 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
438 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
439 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
440 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
441 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
442 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
443 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
444 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
445 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
446 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
447 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
448 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
450 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
451 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
452 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
453 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
454 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
456 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
459 if (s == "cl_weaponpriority")
460 self.switchweapon = w_getbestweapon(self);
461 if (s == "cl_allow_uidtracking")
462 PlayerStats_GameReport_AddPlayer(self);
466 // decolorizes and team colors the player name when needed
467 string playername(entity p)
470 if (teamplay && !intermission_running && IS_PLAYER(p))
472 t = Team_ColorCode(p.team);
473 return strcat(t, strdecolorize(p.netname));
479 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
481 int i = weaponinfo.weapon;
483 bool allow_mutatorblocked = false;
488 bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked);
490 allguns = want_allguns;
491 allow_mutatorblocked = false;
495 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
500 else if(!mutator_returnvalue)
501 d = !(!weaponinfo.weaponstart);
503 if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
506 float t = weaponinfo.weaponstartoverride;
508 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
513 // 4: is set by default?
522 void readplayerstartcvars()
528 // initialize starting values for players
529 start_weapons = '0 0 0';
530 start_weapons_default = '0 0 0';
531 start_weapons_defaultmask = '0 0 0';
533 start_ammo_shells = 0;
534 start_ammo_nails = 0;
535 start_ammo_rockets = 0;
536 start_ammo_cells = 0;
537 start_ammo_plasma = 0;
538 start_health = cvar("g_balance_health_start");
539 start_armorvalue = cvar("g_balance_armor_start");
542 g_weaponarena_weapons = '0 0 0';
544 s = cvar_string("g_weaponarena");
546 MUTATOR_CALLHOOK(SetWeaponArena, s);
549 if (s == "0" || s == "")
555 // forcibly turn off weaponarena
557 else if (s == "all" || s == "1")
560 g_weaponarena_list = "All Weapons";
561 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
563 e = get_weaponinfo(j);
564 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
565 g_weaponarena_weapons |= WepSet_FromWeapon(j);
568 else if (s == "most")
571 g_weaponarena_list = "Most Weapons";
572 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
574 e = get_weaponinfo(j);
575 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
576 if (e.spawnflags & WEP_FLAG_NORMAL)
577 g_weaponarena_weapons |= WepSet_FromWeapon(j);
580 else if (s == "none")
583 g_weaponarena_list = "No Weapons";
588 t = tokenize_console(s);
589 g_weaponarena_list = "";
590 for (i = 0; i < t; ++i)
593 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
595 e = get_weaponinfo(j);
598 g_weaponarena_weapons |= WepSet_FromWeapon(j);
599 g_weaponarena_list = strcat(g_weaponarena_list, e.m_name, " & ");
605 LOG_INFO("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
608 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
612 g_weaponarena_random = cvar("g_weaponarena_random");
614 g_weaponarena_random = 0;
615 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
619 g_weapon_stay = 0; // incompatible
620 start_weapons = g_weaponarena_weapons;
621 start_items |= IT_UNLIMITED_AMMO;
625 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
627 e = get_weaponinfo(i);
628 int w = want_weapon(e, false);
630 start_weapons |= WepSet_FromWeapon(i);
632 start_weapons_default |= WepSet_FromWeapon(i);
634 start_weapons_defaultmask |= WepSet_FromWeapon(i);
638 if(!cvar("g_use_ammunition"))
639 start_items |= IT_UNLIMITED_AMMO;
641 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
643 start_ammo_shells = 999;
644 start_ammo_nails = 999;
645 start_ammo_rockets = 999;
646 start_ammo_cells = 999;
647 start_ammo_plasma = 999;
648 start_ammo_fuel = 999;
652 start_ammo_shells = cvar("g_start_ammo_shells");
653 start_ammo_nails = cvar("g_start_ammo_nails");
654 start_ammo_rockets = cvar("g_start_ammo_rockets");
655 start_ammo_cells = cvar("g_start_ammo_cells");
656 start_ammo_plasma = cvar("g_start_ammo_plasma");
657 start_ammo_fuel = cvar("g_start_ammo_fuel");
662 warmup_start_ammo_shells = start_ammo_shells;
663 warmup_start_ammo_nails = start_ammo_nails;
664 warmup_start_ammo_rockets = start_ammo_rockets;
665 warmup_start_ammo_cells = start_ammo_cells;
666 warmup_start_ammo_plasma = start_ammo_plasma;
667 warmup_start_ammo_fuel = start_ammo_fuel;
668 warmup_start_health = start_health;
669 warmup_start_armorvalue = start_armorvalue;
670 warmup_start_weapons = start_weapons;
671 warmup_start_weapons_default = start_weapons_default;
672 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
674 if (!g_weaponarena && !g_ca && !g_freezetag)
676 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
677 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
678 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
679 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
680 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
681 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
682 warmup_start_health = cvar("g_warmup_start_health");
683 warmup_start_armorvalue = cvar("g_warmup_start_armor");
684 warmup_start_weapons = '0 0 0';
685 warmup_start_weapons_default = '0 0 0';
686 warmup_start_weapons_defaultmask = '0 0 0';
687 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
689 e = get_weaponinfo(i);
690 int w = want_weapon(e, g_warmup_allguns);
692 warmup_start_weapons |= WepSet_FromWeapon(i);
694 warmup_start_weapons_default |= WepSet_FromWeapon(i);
696 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
702 start_items |= ITEM_Jetpack.m_itemid;
704 MUTATOR_CALLHOOK(SetStartItems);
706 if (start_items & ITEM_Jetpack.m_itemid)
708 start_items |= ITEM_JetpackRegen.m_itemid;
709 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
710 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
713 WepSet precache_weapons = start_weapons;
714 if (g_warmup_allguns != 1)
715 precache_weapons |= warmup_start_weapons;
716 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
718 e = get_weaponinfo(i);
719 if(precache_weapons & WepSet_FromWeapon(i)) {
720 Weapon w = get_weaponinfo(i);
725 start_ammo_shells = max(0, start_ammo_shells);
726 start_ammo_nails = max(0, start_ammo_nails);
727 start_ammo_rockets = max(0, start_ammo_rockets);
728 start_ammo_cells = max(0, start_ammo_cells);
729 start_ammo_plasma = max(0, start_ammo_plasma);
730 start_ammo_fuel = max(0, start_ammo_fuel);
732 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
733 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
734 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
735 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
736 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
737 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
740 float sound_allowed(float destin, entity e)
742 // sounds from world may always pass
745 if (e.classname == "body")
747 else if (e.realowner && e.realowner != e)
749 else if (e.owner && e.owner != e)
754 // sounds to self may always pass
755 if (destin == MSG_ONE)
758 // sounds by players can be removed
759 if (autocvar_bot_sound_monopoly)
760 if (IS_REAL_CLIENT(e))
762 // anything else may pass
766 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
770 if (!sound_allowed(_dest, e))
773 entno = num_for_edict(e);
774 idx = precache_sound_index(samp);
779 attenu = floor(attenu * 64);
780 vol = floor(vol * 255);
783 sflags |= SND_VOLUME;
785 sflags |= SND_ATTENUATION;
786 if (entno >= 8192 || chan < 0 || chan > 7)
787 sflags |= SND_LARGEENTITY;
789 sflags |= SND_LARGESOUND;
791 WriteByte(_dest, SVC_SOUND);
792 WriteByte(_dest, sflags);
793 if (sflags & SND_VOLUME)
794 WriteByte(_dest, vol);
795 if (sflags & SND_ATTENUATION)
796 WriteByte(_dest, attenu);
797 if (sflags & SND_LARGEENTITY)
799 WriteShort(_dest, entno);
800 WriteByte(_dest, chan);
804 WriteShort(_dest, entno * 8 + chan);
806 if (sflags & SND_LARGESOUND)
807 WriteShort(_dest, idx);
809 WriteByte(_dest, idx);
811 WriteCoord(_dest, o.x);
812 WriteCoord(_dest, o.y);
813 WriteCoord(_dest, o.z);
815 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
819 if (!sound_allowed(_dest, e))
822 o = e.origin + 0.5 * (e.mins + e.maxs);
823 soundtoat(_dest, e, o, chan, samp, vol, _atten);
825 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
827 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
829 void stopsoundto(float _dest, entity e, float chan)
833 if (!sound_allowed(_dest, e))
836 entno = num_for_edict(e);
838 if (entno >= 8192 || chan < 0 || chan > 7)
841 idx = precache_sound_index(SND(Null));
842 sflags = SND_LARGEENTITY;
844 sflags |= SND_LARGESOUND;
845 WriteByte(_dest, SVC_SOUND);
846 WriteByte(_dest, sflags);
847 WriteShort(_dest, entno);
848 WriteByte(_dest, chan);
849 if (sflags & SND_LARGESOUND)
850 WriteShort(_dest, idx);
852 WriteByte(_dest, idx);
853 WriteCoord(_dest, e.origin.x);
854 WriteCoord(_dest, e.origin.y);
855 WriteCoord(_dest, e.origin.z);
859 WriteByte(_dest, SVC_STOPSOUND);
860 WriteShort(_dest, entno * 8 + chan);
863 void stopsound(entity e, float chan)
865 if (!sound_allowed(MSG_BROADCAST, e))
868 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
869 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
872 void play2(entity e, string filename)
874 //stuffcmd(e, strcat("play2 ", filename, "\n"));
876 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
879 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
881 float spamsound(entity e, float chan, string samp, float vol, float _atten)
883 if (!sound_allowed(MSG_BROADCAST, e))
886 if (time > e.spamtime)
889 _sound(e, chan, samp, vol, _atten);
895 void play2team(float t, string filename)
899 if (autocvar_bot_sound_monopoly)
902 FOR_EACH_REALPLAYER(head)
905 play2(head, filename);
909 void play2all(string samp)
911 if (autocvar_bot_sound_monopoly)
914 _sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
917 void PrecachePlayerSounds(string f);
918 void precache_playermodel(string m)
920 float globhandle, i, n;
923 if(substring(m, -9,5) == "_lod1")
925 if(substring(m, -9,5) == "_lod2")
928 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
931 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
935 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
938 n = search_getsize(globhandle);
939 for (i = 0; i < n; ++i)
941 //print(search_getfilename(globhandle, i), "\n");
942 f = search_getfilename(globhandle, i);
943 PrecachePlayerSounds(f);
945 search_end(globhandle);
947 void precache_all_playermodels(string pattern)
949 float globhandle, i, n;
952 globhandle = search_begin(pattern, true, false);
955 n = search_getsize(globhandle);
956 for (i = 0; i < n; ++i)
958 //print(search_getfilename(globhandle, i), "\n");
959 f = search_getfilename(globhandle, i);
960 precache_playermodel(f);
962 search_end(globhandle);
965 void precache_playermodels(string s)
969 int n = tokenize_console(s);
970 precache_playermodel(argv(0));
972 for (int i = 1; i < n; ++i)
973 precache_model(argv(i));
979 // gamemode related things
981 // Precache all player models if desired
982 if (autocvar_sv_precacheplayermodels)
984 PrecachePlayerSounds("sound/player/default.sounds");
985 precache_all_playermodels("models/player/*.zym");
986 precache_all_playermodels("models/player/*.dpm");
987 precache_all_playermodels("models/player/*.md3");
988 precache_all_playermodels("models/player/*.psk");
989 precache_all_playermodels("models/player/*.iqm");
992 if (autocvar_sv_defaultcharacter)
994 precache_playermodels(autocvar_sv_defaultplayermodel_red);
995 precache_playermodels(autocvar_sv_defaultplayermodel_blue);
996 precache_playermodels(autocvar_sv_defaultplayermodel_yellow);
997 precache_playermodels(autocvar_sv_defaultplayermodel_pink);
998 precache_playermodels(autocvar_sv_defaultplayermodel);
1003 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1004 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1007 // gore and miscellaneous sounds
1008 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1009 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1012 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1014 if (!self.noise && self.music) // quake 3 uses the music field
1015 self.noise = self.music;
1017 // plays music for the level if there is any
1020 precache_sound (self.noise);
1021 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1027 void make_safe_for_remove(entity e)
1029 if (e.initialize_entity)
1031 entity ent, prev = world;
1032 for (ent = initialize_entity_first; ent; )
1034 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1036 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1037 // skip it in linked list
1040 prev.initialize_entity_next = ent.initialize_entity_next;
1041 ent = prev.initialize_entity_next;
1045 initialize_entity_first = ent.initialize_entity_next;
1046 ent = initialize_entity_first;
1052 ent = ent.initialize_entity_next;
1058 void objerror(string s)
1060 make_safe_for_remove(self);
1061 builtin_objerror(s);
1064 .float remove_except_protected_forbidden;
1065 void remove_except_protected(entity e)
1067 if(e.remove_except_protected_forbidden)
1068 error("not allowed to remove this at this point");
1072 void remove_unsafely(entity e)
1074 if(e.classname == "spike")
1075 error("Removing spikes is forbidden (crylink bug), please report");
1079 void remove_safely(entity e)
1081 make_safe_for_remove(e);
1085 void InitializeEntity(entity e, void(void) func, float order)
1089 if (!e || e.initialize_entity)
1091 // make a proxy initializer entity
1093 e = new(initialize_entity);
1097 e.initialize_entity = func;
1098 e.initialize_entity_order = order;
1100 cur = initialize_entity_first;
1104 if (!cur || cur.initialize_entity_order > order)
1106 // insert between prev and cur
1108 prev.initialize_entity_next = e;
1110 initialize_entity_first = e;
1111 e.initialize_entity_next = cur;
1115 cur = cur.initialize_entity_next;
1118 void InitializeEntitiesRun()
1120 entity startoflist = initialize_entity_first;
1121 initialize_entity_first = NULL;
1122 remove = remove_except_protected;
1123 for (entity e = startoflist; e; e = e.initialize_entity_next)
1125 e.remove_except_protected_forbidden = 1;
1127 for (entity e = startoflist; e; )
1129 e.remove_except_protected_forbidden = 0;
1130 e.initialize_entity_order = 0;
1131 entity next = e.initialize_entity_next;
1132 e.initialize_entity_next = NULL;
1133 var void() func = e.initialize_entity;
1134 e.initialize_entity = func_null;
1135 if (e.classname == "initialize_entity")
1137 entity wrappee = e.enemy;
1141 //dprint("Delayed initialization: ", e.classname, "\n");
1144 WITH(entity, self, e, func());
1149 backtrace(strcat("Null function in: ", e.classname, "\n"));
1153 remove = remove_unsafely;
1156 .float(entity) isEliminated;
1157 bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags)
1161 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1162 WriteByte(MSG_ENTITY, sendflags);
1166 for(i = 1; i <= maxclients; i += 8)
1168 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1170 if(eliminatedPlayers.isEliminated(e))
1173 WriteByte(MSG_ENTITY, f);
1180 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1182 if(eliminatedPlayers)
1184 backtrace("Can't spawn eliminatedPlayers again!");
1187 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1188 eliminatedPlayers.isEliminated = isEliminated_func;
1192 void adaptor_think2touch()
1201 void adaptor_think2use()
1213 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1215 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
1216 self.projectiledeathtype |= HITTYPE_SPLASH;
1217 adaptor_think2use();
1220 // deferred dropping
1221 void DropToFloor_Handler()
1223 builtin_droptofloor();
1224 self.dropped_origin = self.origin;
1229 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1234 float trace_hits_box_a0, trace_hits_box_a1;
1236 float trace_hits_box_1d(float end, float thmi, float thma)
1240 // just check if x is in range
1248 // do the trace with respect to x
1249 // 0 -> end has to stay in thmi -> thma
1250 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1251 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1252 if (trace_hits_box_a0 > trace_hits_box_a1)
1258 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1263 // now it is a trace from 0 to end
1265 trace_hits_box_a0 = 0;
1266 trace_hits_box_a1 = 1;
1268 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1270 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1272 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1278 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1280 return trace_hits_box(start, end, thmi - ma, thma - mi);
1283 float SUB_NoImpactCheck()
1285 // zero hitcontents = this is not the real impact, but either the
1286 // mirror-impact of something hitting the projectile instead of the
1287 // projectile hitting the something, or a touchareagrid one. Neither of
1288 // these stop the projectile from moving, so...
1289 if(trace_dphitcontents == 0)
1291 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1292 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));
1295 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1297 if (other == world && self.size != '0 0 0')
1300 tic = self.velocity * sys_frametime;
1301 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1302 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1303 if (trace_fraction >= 1)
1305 LOG_TRACE("Odd... did not hit...?\n");
1307 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1309 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1317 #define SUB_OwnerCheck() (other && (other == self.owner))
1319 void W_Crylink_Dequeue(entity e);
1320 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1322 if(SUB_OwnerCheck())
1324 if(SUB_NoImpactCheck())
1326 if(self.classname == "nade")
1327 return false; // no checks here
1328 else if(self.classname == "grapplinghook")
1329 RemoveGrapplingHook(self.realowner);
1330 else if(self.classname == "spike")
1332 W_Crylink_Dequeue(self);
1339 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1340 UpdateCSQCProjectile(self);
1345 void URI_Get_Callback(float id, float status, string data)
1347 if(url_URI_Get_Callback(id, status, data))
1351 else if (id == URI_GET_DISCARD)
1355 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1358 Curl_URI_Get_Callback(id, status, data);
1360 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1363 OnlineBanList_URI_Get_Callback(id, status, data);
1367 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1371 string uid2name(string myuid) {
1373 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1375 // FIXME remove this later after 0.6 release
1376 // convert old style broken records to correct style
1379 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1382 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1383 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1388 s = "^1Unregistered Player";
1392 float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1395 vector start, org, delta, end, enddown, mstart;
1398 m = e.dphitcontentsmask;
1399 e.dphitcontentsmask = goodcontents | badcontents;
1402 delta = boundmax - boundmin;
1406 for (i = 0; i < attempts; ++i)
1408 start.x = org.x + random() * delta.x;
1409 start.y = org.y + random() * delta.y;
1410 start.z = org.z + random() * delta.z;
1412 // rule 1: start inside world bounds, and outside
1413 // solid, and don't start from somewhere where you can
1414 // fall down to evil
1415 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1416 if (trace_fraction >= 1)
1418 if (trace_startsolid)
1420 if (trace_dphitcontents & badcontents)
1422 if (trace_dphitq3surfaceflags & badsurfaceflags)
1425 // rule 2: if we are too high, lower the point
1426 if (trace_fraction * delta.z > maxaboveground)
1427 start = trace_endpos + '0 0 1' * maxaboveground;
1428 enddown = trace_endpos;
1430 // rule 3: make sure we aren't outside the map. This only works
1431 // for somewhat well formed maps. A good rule of thumb is that
1432 // the map should have a convex outside hull.
1433 // these can be traceLINES as we already verified the starting box
1434 mstart = start + 0.5 * (e.mins + e.maxs);
1435 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1436 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1438 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1439 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1441 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1442 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1444 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1445 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1447 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1448 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1451 // rule 4: we must "see" some spawnpoint or item
1452 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1453 if(checkpvs(mstart, sp))
1454 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1458 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1459 if(checkpvs(mstart, sp))
1460 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1466 // find a random vector to "look at"
1467 end.x = org.x + random() * delta.x;
1468 end.y = org.y + random() * delta.y;
1469 end.z = org.z + random() * delta.z;
1470 end = start + normalize(end - start) * vlen(delta);
1472 // rule 4: start TO end must not be too short
1473 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1474 if (trace_startsolid)
1476 if (trace_fraction < minviewdistance / vlen(delta))
1479 // rule 5: don't want to look at sky
1480 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1483 // rule 6: we must not end up in trigger_hurt
1484 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1490 e.dphitcontentsmask = m;
1494 setorigin(e, start);
1495 e.angles = vectoangles(end - start);
1496 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1503 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1505 return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
1508 void write_recordmarker(entity pl, float tstart, float dt)
1510 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1512 // also write a marker into demo files for demotc-race-record-extractor to find
1515 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1516 " ", ftos(tstart), " ", ftos(dt), "\n"));
1519 void attach_sameorigin(entity e, entity to, string tag)
1521 vector org, t_forward, t_left, t_up, e_forward, e_up;
1524 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1525 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1526 t_forward = v_forward * tagscale;
1527 t_left = v_right * -tagscale;
1528 t_up = v_up * tagscale;
1530 e.origin_x = org * t_forward;
1531 e.origin_y = org * t_left;
1532 e.origin_z = org * t_up;
1534 // current forward and up directions
1535 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1536 e.angles = AnglesTransform_FromVAngles(e.angles);
1538 e.angles = AnglesTransform_FromAngles(e.angles);
1539 fixedmakevectors(e.angles);
1541 // untransform forward, up!
1542 e_forward.x = v_forward * t_forward;
1543 e_forward.y = v_forward * t_left;
1544 e_forward.z = v_forward * t_up;
1545 e_up.x = v_up * t_forward;
1546 e_up.y = v_up * t_left;
1547 e_up.z = v_up * t_up;
1549 e.angles = fixedvectoangles2(e_forward, e_up);
1550 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1551 e.angles = AnglesTransform_ToVAngles(e.angles);
1553 e.angles = AnglesTransform_ToAngles(e.angles);
1555 setattachment(e, to, tag);
1556 setorigin(e, e.origin);
1559 void detach_sameorigin(entity e)
1562 org = gettaginfo(e, 0);
1563 e.angles = fixedvectoangles2(v_forward, v_up);
1564 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1565 e.angles = AnglesTransform_ToVAngles(e.angles);
1567 e.angles = AnglesTransform_ToAngles(e.angles);
1569 setattachment(e, world, "");
1570 setorigin(e, e.origin);
1573 void follow_sameorigin(entity e, entity to)
1575 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1576 e.aiment = to; // make the hole follow bmodel
1577 e.punchangle = to.angles; // the original angles of bmodel
1578 e.view_ofs = e.origin - to.origin; // relative origin
1579 e.v_angle = e.angles - to.angles; // relative angles
1582 void unfollow_sameorigin(entity e)
1584 e.movetype = MOVETYPE_NONE;
1587 entity gettaginfo_relative_ent;
1588 vector gettaginfo_relative(entity e, float tag)
1590 if (!gettaginfo_relative_ent)
1592 gettaginfo_relative_ent = spawn();
1593 gettaginfo_relative_ent.effects = EF_NODRAW;
1595 gettaginfo_relative_ent.model = e.model;
1596 gettaginfo_relative_ent.modelindex = e.modelindex;
1597 gettaginfo_relative_ent.frame = e.frame;
1598 return gettaginfo(gettaginfo_relative_ent, tag);
1603 bool modeleffect_SendEntity(entity this, entity to, int sf)
1606 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1609 if(self.velocity != '0 0 0')
1611 if(self.angles != '0 0 0')
1613 if(self.avelocity != '0 0 0')
1616 WriteByte(MSG_ENTITY, f);
1617 WriteShort(MSG_ENTITY, self.modelindex);
1618 WriteByte(MSG_ENTITY, self.skin);
1619 WriteByte(MSG_ENTITY, self.frame);
1620 WriteCoord(MSG_ENTITY, self.origin.x);
1621 WriteCoord(MSG_ENTITY, self.origin.y);
1622 WriteCoord(MSG_ENTITY, self.origin.z);
1625 WriteCoord(MSG_ENTITY, self.velocity.x);
1626 WriteCoord(MSG_ENTITY, self.velocity.y);
1627 WriteCoord(MSG_ENTITY, self.velocity.z);
1631 WriteCoord(MSG_ENTITY, self.angles.x);
1632 WriteCoord(MSG_ENTITY, self.angles.y);
1633 WriteCoord(MSG_ENTITY, self.angles.z);
1637 WriteCoord(MSG_ENTITY, self.avelocity.x);
1638 WriteCoord(MSG_ENTITY, self.avelocity.y);
1639 WriteCoord(MSG_ENTITY, self.avelocity.z);
1641 WriteShort(MSG_ENTITY, self.scale * 256.0);
1642 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1643 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1644 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1645 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1650 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)
1652 entity e = new(modeleffect);
1660 e.teleport_time = t1;
1664 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1668 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1671 float sz = max(e.scale, e.scale2);
1672 setsize(e, e.mins * sz, e.maxs * sz);
1673 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1676 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1678 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1681 float randombit(float bits)
1683 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1692 for(f = 1; f <= bits; f *= 2)
1701 r = (r - 1) / (n - 1);
1708 float randombits(float bits, float k, float error_return)
1712 while(k > 0 && bits != r)
1714 r += randombit(bits - r);
1723 void randombit_test(float bits, float iter)
1727 LOG_INFO(ftos(randombit(bits)), "\n");
1732 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1734 if(halflifedist > 0)
1735 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1736 else if(halflifedist < 0)
1737 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1743 .string aiment_classname;
1744 .float aiment_deadflag;
1745 void SetMovetypeFollow(entity ent, entity e)
1747 // FIXME this may not be warpzone aware
1748 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1749 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.
1750 ent.aiment = e; // make the hole follow bmodel
1751 ent.punchangle = e.angles; // the original angles of bmodel
1752 ent.view_ofs = ent.origin - e.origin; // relative origin
1753 ent.v_angle = ent.angles - e.angles; // relative angles
1754 ent.aiment_classname = strzone(e.classname);
1755 ent.aiment_deadflag = e.deadflag;
1757 void UnsetMovetypeFollow(entity ent)
1759 ent.movetype = MOVETYPE_FLY;
1760 PROJECTILE_MAKETRIGGER(ent);
1763 float LostMovetypeFollow(entity ent)
1766 if(ent.movetype != MOVETYPE_FOLLOW)
1772 if(ent.aiment.classname != ent.aiment_classname)
1774 if(ent.aiment.deadflag != ent.aiment_deadflag)
1780 float isPushable(entity e)
1791 case "droppedweapon":
1792 case "keepawayball":
1793 case "nexball_basketball":
1794 case "nexball_football":
1796 case "bullet": // antilagged bullets can't hit this either
1799 if (e.projectiledeathtype)