1 #include "miscfunctions.qh"
3 #include "command/common.qh"
4 #include "constants.qh"
7 #include "mutators/mutators_include.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");
449 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
450 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
452 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
453 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
455 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
456 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
457 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
458 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
459 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
461 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
464 if (s == "cl_weaponpriority")
465 self.switchweapon = w_getbestweapon(self);
466 if (s == "cl_allow_uidtracking")
467 PlayerStats_GameReport_AddPlayer(self);
471 // decolorizes and team colors the player name when needed
472 string playername(entity p)
475 if (teamplay && !intermission_running && IS_PLAYER(p))
477 t = Team_ColorCode(p.team);
478 return strcat(t, strdecolorize(p.netname));
484 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
486 int i = weaponinfo.weapon;
492 if (g_lms || g_ca || allguns)
494 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
500 d = (i == WEP_SHOTGUN.m_id);
502 d = 0; // weapon is set a few lines later
504 d = !(!weaponinfo.weaponstart);
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)
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 Weapon w = get_weaponinfo(i);
730 start_ammo_shells = max(0, start_ammo_shells);
731 start_ammo_nails = max(0, start_ammo_nails);
732 start_ammo_rockets = max(0, start_ammo_rockets);
733 start_ammo_cells = max(0, start_ammo_cells);
734 start_ammo_plasma = max(0, start_ammo_plasma);
735 start_ammo_fuel = max(0, start_ammo_fuel);
737 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
738 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
739 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
740 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
741 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
742 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
745 float sound_allowed(float destin, entity e)
747 // sounds from world may always pass
750 if (e.classname == "body")
752 else if (e.realowner && e.realowner != e)
754 else if (e.owner && e.owner != e)
759 // sounds to self may always pass
760 if (destin == MSG_ONE)
763 // sounds by players can be removed
764 if (autocvar_bot_sound_monopoly)
765 if (IS_REAL_CLIENT(e))
767 // anything else may pass
771 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
775 if (!sound_allowed(_dest, e))
778 entno = num_for_edict(e);
779 idx = precache_sound_index(samp);
784 attenu = floor(attenu * 64);
785 vol = floor(vol * 255);
788 sflags |= SND_VOLUME;
790 sflags |= SND_ATTENUATION;
791 if (entno >= 8192 || chan < 0 || chan > 7)
792 sflags |= SND_LARGEENTITY;
794 sflags |= SND_LARGESOUND;
796 WriteByte(_dest, SVC_SOUND);
797 WriteByte(_dest, sflags);
798 if (sflags & SND_VOLUME)
799 WriteByte(_dest, vol);
800 if (sflags & SND_ATTENUATION)
801 WriteByte(_dest, attenu);
802 if (sflags & SND_LARGEENTITY)
804 WriteShort(_dest, entno);
805 WriteByte(_dest, chan);
809 WriteShort(_dest, entno * 8 + chan);
811 if (sflags & SND_LARGESOUND)
812 WriteShort(_dest, idx);
814 WriteByte(_dest, idx);
816 WriteCoord(_dest, o.x);
817 WriteCoord(_dest, o.y);
818 WriteCoord(_dest, o.z);
820 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
824 if (!sound_allowed(_dest, e))
827 o = e.origin + 0.5 * (e.mins + e.maxs);
828 soundtoat(_dest, e, o, chan, samp, vol, _atten);
830 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
832 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
834 void stopsoundto(float _dest, entity e, float chan)
838 if (!sound_allowed(_dest, e))
841 entno = num_for_edict(e);
843 if (entno >= 8192 || chan < 0 || chan > 7)
846 idx = precache_sound_index(SND(Null));
847 sflags = SND_LARGEENTITY;
849 sflags |= SND_LARGESOUND;
850 WriteByte(_dest, SVC_SOUND);
851 WriteByte(_dest, sflags);
852 WriteShort(_dest, entno);
853 WriteByte(_dest, chan);
854 if (sflags & SND_LARGESOUND)
855 WriteShort(_dest, idx);
857 WriteByte(_dest, idx);
858 WriteCoord(_dest, e.origin.x);
859 WriteCoord(_dest, e.origin.y);
860 WriteCoord(_dest, e.origin.z);
864 WriteByte(_dest, SVC_STOPSOUND);
865 WriteShort(_dest, entno * 8 + chan);
868 void stopsound(entity e, float chan)
870 if (!sound_allowed(MSG_BROADCAST, e))
873 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
874 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
877 void play2(entity e, string filename)
879 //stuffcmd(e, strcat("play2 ", filename, "\n"));
881 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
884 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
886 float spamsound(entity e, float chan, string samp, float vol, float _atten)
888 if (!sound_allowed(MSG_BROADCAST, e))
891 if (time > e.spamtime)
894 _sound(e, chan, samp, vol, _atten);
900 void play2team(float t, string filename)
904 if (autocvar_bot_sound_monopoly)
907 FOR_EACH_REALPLAYER(head)
910 play2(head, filename);
914 void play2all(string samp)
916 if (autocvar_bot_sound_monopoly)
919 _sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
922 void PrecachePlayerSounds(string f);
923 void precache_playermodel(string m)
925 float globhandle, i, n;
928 if(substring(m, -9,5) == "_lod1")
930 if(substring(m, -9,5) == "_lod2")
933 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
936 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
940 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
943 n = search_getsize(globhandle);
944 for (i = 0; i < n; ++i)
946 //print(search_getfilename(globhandle, i), "\n");
947 f = search_getfilename(globhandle, i);
948 PrecachePlayerSounds(f);
950 search_end(globhandle);
952 void precache_all_playermodels(string pattern)
954 float globhandle, i, n;
957 globhandle = search_begin(pattern, true, false);
960 n = search_getsize(globhandle);
961 for (i = 0; i < n; ++i)
963 //print(search_getfilename(globhandle, i), "\n");
964 f = search_getfilename(globhandle, i);
965 precache_playermodel(f);
967 search_end(globhandle);
972 // gamemode related things
974 // Precache all player models if desired
975 if (autocvar_sv_precacheplayermodels)
977 PrecachePlayerSounds("sound/player/default.sounds");
978 precache_all_playermodels("models/player/*.zym");
979 precache_all_playermodels("models/player/*.dpm");
980 precache_all_playermodels("models/player/*.md3");
981 precache_all_playermodels("models/player/*.psk");
982 precache_all_playermodels("models/player/*.iqm");
985 if (autocvar_sv_defaultcharacter)
988 s = autocvar_sv_defaultplayermodel_red;
990 precache_playermodel(s);
991 s = autocvar_sv_defaultplayermodel_blue;
993 precache_playermodel(s);
994 s = autocvar_sv_defaultplayermodel_yellow;
996 precache_playermodel(s);
997 s = autocvar_sv_defaultplayermodel_pink;
999 precache_playermodel(s);
1000 s = autocvar_sv_defaultplayermodel;
1002 precache_playermodel(s);
1007 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1008 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1011 // gore and miscellaneous sounds
1012 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1013 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1016 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1018 if (!self.noise && self.music) // quake 3 uses the music field
1019 self.noise = self.music;
1021 // plays music for the level if there is any
1024 precache_sound (self.noise);
1025 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1031 void make_safe_for_remove(entity e)
1033 if (e.initialize_entity)
1035 entity ent, prev = world;
1036 for (ent = initialize_entity_first; ent; )
1038 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1040 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1041 // skip it in linked list
1044 prev.initialize_entity_next = ent.initialize_entity_next;
1045 ent = prev.initialize_entity_next;
1049 initialize_entity_first = ent.initialize_entity_next;
1050 ent = initialize_entity_first;
1056 ent = ent.initialize_entity_next;
1062 void objerror(string s)
1064 make_safe_for_remove(self);
1065 builtin_objerror(s);
1068 .float remove_except_protected_forbidden;
1069 void remove_except_protected(entity e)
1071 if(e.remove_except_protected_forbidden)
1072 error("not allowed to remove this at this point");
1076 void remove_unsafely(entity e)
1078 if(e.classname == "spike")
1079 error("Removing spikes is forbidden (crylink bug), please report");
1083 void remove_safely(entity e)
1085 make_safe_for_remove(e);
1089 void InitializeEntity(entity e, void(void) func, float order)
1093 if (!e || e.initialize_entity)
1095 // make a proxy initializer entity
1097 e = new(initialize_entity);
1101 e.initialize_entity = func;
1102 e.initialize_entity_order = order;
1104 cur = initialize_entity_first;
1108 if (!cur || cur.initialize_entity_order > order)
1110 // insert between prev and cur
1112 prev.initialize_entity_next = e;
1114 initialize_entity_first = e;
1115 e.initialize_entity_next = cur;
1119 cur = cur.initialize_entity_next;
1122 void InitializeEntitiesRun()
1124 entity startoflist = initialize_entity_first;
1125 initialize_entity_first = NULL;
1126 remove = remove_except_protected;
1127 for (entity e = startoflist; e; e = e.initialize_entity_next)
1129 e.remove_except_protected_forbidden = 1;
1131 for (entity e = startoflist; e; )
1133 e.remove_except_protected_forbidden = 0;
1134 e.initialize_entity_order = 0;
1135 entity next = e.initialize_entity_next;
1136 e.initialize_entity_next = NULL;
1137 var void() func = e.initialize_entity;
1138 e.initialize_entity = func_null;
1139 if (e.classname == "initialize_entity")
1141 entity wrappee = e.enemy;
1145 //dprint("Delayed initialization: ", e.classname, "\n");
1148 WITH(entity, self, e, func());
1153 backtrace(strcat("Null function in: ", e.classname, "\n"));
1157 remove = remove_unsafely;
1160 .float(entity) isEliminated;
1161 bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags)
1165 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1166 WriteByte(MSG_ENTITY, sendflags);
1170 for(i = 1; i <= maxclients; i += 8)
1172 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1174 if(eliminatedPlayers.isEliminated(e))
1177 WriteByte(MSG_ENTITY, f);
1184 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1186 if(eliminatedPlayers)
1188 backtrace("Can't spawn eliminatedPlayers again!");
1191 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1192 eliminatedPlayers.isEliminated = isEliminated_func;
1196 void adaptor_think2touch()
1205 void adaptor_think2use()
1217 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1219 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
1220 self.projectiledeathtype |= HITTYPE_SPLASH;
1221 adaptor_think2use();
1224 // deferred dropping
1225 void DropToFloor_Handler()
1227 builtin_droptofloor();
1228 self.dropped_origin = self.origin;
1233 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1238 float trace_hits_box_a0, trace_hits_box_a1;
1240 float trace_hits_box_1d(float end, float thmi, float thma)
1244 // just check if x is in range
1252 // do the trace with respect to x
1253 // 0 -> end has to stay in thmi -> thma
1254 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1255 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1256 if (trace_hits_box_a0 > trace_hits_box_a1)
1262 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1267 // now it is a trace from 0 to end
1269 trace_hits_box_a0 = 0;
1270 trace_hits_box_a1 = 1;
1272 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1274 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1276 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1282 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1284 return trace_hits_box(start, end, thmi - ma, thma - mi);
1287 float SUB_NoImpactCheck()
1289 // zero hitcontents = this is not the real impact, but either the
1290 // mirror-impact of something hitting the projectile instead of the
1291 // projectile hitting the something, or a touchareagrid one. Neither of
1292 // these stop the projectile from moving, so...
1293 if(trace_dphitcontents == 0)
1295 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1296 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));
1299 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1301 if (other == world && self.size != '0 0 0')
1304 tic = self.velocity * sys_frametime;
1305 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1306 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1307 if (trace_fraction >= 1)
1309 LOG_TRACE("Odd... did not hit...?\n");
1311 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1313 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1321 #define SUB_OwnerCheck() (other && (other == self.owner))
1323 void W_Crylink_Dequeue(entity e);
1324 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1326 if(SUB_OwnerCheck())
1328 if(SUB_NoImpactCheck())
1330 if(self.classname == "nade")
1331 return false; // no checks here
1332 else if(self.classname == "grapplinghook")
1333 RemoveGrapplingHook(self.realowner);
1334 else if(self.classname == "spike")
1336 W_Crylink_Dequeue(self);
1343 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1344 UpdateCSQCProjectile(self);
1349 void URI_Get_Callback(float id, float status, string data)
1351 if(url_URI_Get_Callback(id, status, data))
1355 else if (id == URI_GET_DISCARD)
1359 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1362 Curl_URI_Get_Callback(id, status, data);
1364 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1367 OnlineBanList_URI_Get_Callback(id, status, data);
1371 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1375 string uid2name(string myuid) {
1377 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1379 // FIXME remove this later after 0.6 release
1380 // convert old style broken records to correct style
1383 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1386 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1387 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1392 s = "^1Unregistered Player";
1396 float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1399 vector start, org, delta, end, enddown, mstart;
1402 m = e.dphitcontentsmask;
1403 e.dphitcontentsmask = goodcontents | badcontents;
1406 delta = boundmax - boundmin;
1410 for (i = 0; i < attempts; ++i)
1412 start.x = org.x + random() * delta.x;
1413 start.y = org.y + random() * delta.y;
1414 start.z = org.z + random() * delta.z;
1416 // rule 1: start inside world bounds, and outside
1417 // solid, and don't start from somewhere where you can
1418 // fall down to evil
1419 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1420 if (trace_fraction >= 1)
1422 if (trace_startsolid)
1424 if (trace_dphitcontents & badcontents)
1426 if (trace_dphitq3surfaceflags & badsurfaceflags)
1429 // rule 2: if we are too high, lower the point
1430 if (trace_fraction * delta.z > maxaboveground)
1431 start = trace_endpos + '0 0 1' * maxaboveground;
1432 enddown = trace_endpos;
1434 // rule 3: make sure we aren't outside the map. This only works
1435 // for somewhat well formed maps. A good rule of thumb is that
1436 // the map should have a convex outside hull.
1437 // these can be traceLINES as we already verified the starting box
1438 mstart = start + 0.5 * (e.mins + e.maxs);
1439 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1440 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1442 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1443 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1445 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1446 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1448 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1449 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1451 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1452 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1455 // rule 4: we must "see" some spawnpoint or item
1456 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1457 if(checkpvs(mstart, sp))
1458 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1462 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1463 if(checkpvs(mstart, sp))
1464 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1470 // find a random vector to "look at"
1471 end.x = org.x + random() * delta.x;
1472 end.y = org.y + random() * delta.y;
1473 end.z = org.z + random() * delta.z;
1474 end = start + normalize(end - start) * vlen(delta);
1476 // rule 4: start TO end must not be too short
1477 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1478 if (trace_startsolid)
1480 if (trace_fraction < minviewdistance / vlen(delta))
1483 // rule 5: don't want to look at sky
1484 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1487 // rule 6: we must not end up in trigger_hurt
1488 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1494 e.dphitcontentsmask = m;
1498 setorigin(e, start);
1499 e.angles = vectoangles(end - start);
1500 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1507 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1509 return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
1512 void write_recordmarker(entity pl, float tstart, float dt)
1514 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1516 // also write a marker into demo files for demotc-race-record-extractor to find
1519 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1520 " ", ftos(tstart), " ", ftos(dt), "\n"));
1523 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1536 if(allowcenter) // 2: allow center handedness
1549 if(allowcenter) // 2: allow center handedness
1565 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1570 if (autocvar_g_shootfromeye)
1574 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1575 else { vecs.y = 0; vecs.z -= 2; }
1583 else if (autocvar_g_shootfromcenter)
1588 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1598 else if (autocvar_g_shootfromclient)
1600 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1605 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1607 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1611 void attach_sameorigin(entity e, entity to, string tag)
1613 vector org, t_forward, t_left, t_up, e_forward, e_up;
1616 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1617 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1618 t_forward = v_forward * tagscale;
1619 t_left = v_right * -tagscale;
1620 t_up = v_up * tagscale;
1622 e.origin_x = org * t_forward;
1623 e.origin_y = org * t_left;
1624 e.origin_z = org * t_up;
1626 // current forward and up directions
1627 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1628 e.angles = AnglesTransform_FromVAngles(e.angles);
1630 e.angles = AnglesTransform_FromAngles(e.angles);
1631 fixedmakevectors(e.angles);
1633 // untransform forward, up!
1634 e_forward.x = v_forward * t_forward;
1635 e_forward.y = v_forward * t_left;
1636 e_forward.z = v_forward * t_up;
1637 e_up.x = v_up * t_forward;
1638 e_up.y = v_up * t_left;
1639 e_up.z = v_up * t_up;
1641 e.angles = fixedvectoangles2(e_forward, e_up);
1642 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1643 e.angles = AnglesTransform_ToVAngles(e.angles);
1645 e.angles = AnglesTransform_ToAngles(e.angles);
1647 setattachment(e, to, tag);
1648 setorigin(e, e.origin);
1651 void detach_sameorigin(entity e)
1654 org = gettaginfo(e, 0);
1655 e.angles = fixedvectoangles2(v_forward, v_up);
1656 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1657 e.angles = AnglesTransform_ToVAngles(e.angles);
1659 e.angles = AnglesTransform_ToAngles(e.angles);
1661 setattachment(e, world, "");
1662 setorigin(e, e.origin);
1665 void follow_sameorigin(entity e, entity to)
1667 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1668 e.aiment = to; // make the hole follow bmodel
1669 e.punchangle = to.angles; // the original angles of bmodel
1670 e.view_ofs = e.origin - to.origin; // relative origin
1671 e.v_angle = e.angles - to.angles; // relative angles
1674 void unfollow_sameorigin(entity e)
1676 e.movetype = MOVETYPE_NONE;
1679 entity gettaginfo_relative_ent;
1680 vector gettaginfo_relative(entity e, float tag)
1682 if (!gettaginfo_relative_ent)
1684 gettaginfo_relative_ent = spawn();
1685 gettaginfo_relative_ent.effects = EF_NODRAW;
1687 gettaginfo_relative_ent.model = e.model;
1688 gettaginfo_relative_ent.modelindex = e.modelindex;
1689 gettaginfo_relative_ent.frame = e.frame;
1690 return gettaginfo(gettaginfo_relative_ent, tag);
1695 bool modeleffect_SendEntity(entity this, entity to, int sf)
1698 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1701 if(self.velocity != '0 0 0')
1703 if(self.angles != '0 0 0')
1705 if(self.avelocity != '0 0 0')
1708 WriteByte(MSG_ENTITY, f);
1709 WriteShort(MSG_ENTITY, self.modelindex);
1710 WriteByte(MSG_ENTITY, self.skin);
1711 WriteByte(MSG_ENTITY, self.frame);
1712 WriteCoord(MSG_ENTITY, self.origin.x);
1713 WriteCoord(MSG_ENTITY, self.origin.y);
1714 WriteCoord(MSG_ENTITY, self.origin.z);
1717 WriteCoord(MSG_ENTITY, self.velocity.x);
1718 WriteCoord(MSG_ENTITY, self.velocity.y);
1719 WriteCoord(MSG_ENTITY, self.velocity.z);
1723 WriteCoord(MSG_ENTITY, self.angles.x);
1724 WriteCoord(MSG_ENTITY, self.angles.y);
1725 WriteCoord(MSG_ENTITY, self.angles.z);
1729 WriteCoord(MSG_ENTITY, self.avelocity.x);
1730 WriteCoord(MSG_ENTITY, self.avelocity.y);
1731 WriteCoord(MSG_ENTITY, self.avelocity.z);
1733 WriteShort(MSG_ENTITY, self.scale * 256.0);
1734 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1735 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1736 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1737 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1742 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)
1747 e.classname = "modeleffect";
1755 e.teleport_time = t1;
1759 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1763 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1766 sz = max(e.scale, e.scale2);
1767 setsize(e, e.mins * sz, e.maxs * sz);
1768 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1771 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1773 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1776 float randombit(float bits)
1778 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1787 for(f = 1; f <= bits; f *= 2)
1796 r = (r - 1) / (n - 1);
1803 float randombits(float bits, float k, float error_return)
1807 while(k > 0 && bits != r)
1809 r += randombit(bits - r);
1818 void randombit_test(float bits, float iter)
1822 LOG_INFO(ftos(randombit(bits)), "\n");
1827 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1829 if(halflifedist > 0)
1830 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1831 else if(halflifedist < 0)
1832 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1838 .string aiment_classname;
1839 .float aiment_deadflag;
1840 void SetMovetypeFollow(entity ent, entity e)
1842 // FIXME this may not be warpzone aware
1843 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1844 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.
1845 ent.aiment = e; // make the hole follow bmodel
1846 ent.punchangle = e.angles; // the original angles of bmodel
1847 ent.view_ofs = ent.origin - e.origin; // relative origin
1848 ent.v_angle = ent.angles - e.angles; // relative angles
1849 ent.aiment_classname = strzone(e.classname);
1850 ent.aiment_deadflag = e.deadflag;
1852 void UnsetMovetypeFollow(entity ent)
1854 ent.movetype = MOVETYPE_FLY;
1855 PROJECTILE_MAKETRIGGER(ent);
1858 float LostMovetypeFollow(entity ent)
1861 if(ent.movetype != MOVETYPE_FOLLOW)
1867 if(ent.aiment.classname != ent.aiment_classname)
1869 if(ent.aiment.deadflag != ent.aiment_deadflag)
1875 float isPushable(entity e)
1886 case "droppedweapon":
1887 case "keepawayball":
1888 case "nexball_basketball":
1889 case "nexball_football":
1891 case "bullet": // antilagged bullets can't hit this either
1894 if (e.projectiledeathtype)