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/urllib.qh"
22 #include "../common/util.qh"
23 #include "../common/turrets/sv_turrets.qh"
24 #include "../common/weapons/all.qh"
25 #include "../csqcmodellib/sv_model.qh"
26 #include "../warpzonelib/anglestransform.qh"
27 #include "../warpzonelib/server.qh"
29 void crosshair_trace(entity pl)
31 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));
33 void crosshair_trace_plusvisibletriggers(entity pl)
37 first = findchainfloat(solid, SOLID_TRIGGER);
39 for (e = first; e; e = e.chain)
45 for (e = first; e; e = e.chain)
46 e.solid = SOLID_TRIGGER;
48 void WarpZone_crosshair_trace(entity pl)
50 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));
54 string admin_name(void)
56 if(autocvar_sv_adminnick != "")
57 return autocvar_sv_adminnick;
59 return "SERVER ADMIN";
62 void DistributeEvenly_Init(float amount, float totalweight)
64 if (DistributeEvenly_amount)
66 dprint("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
67 dprint(ftos(DistributeEvenly_totalweight), " left!)\n");
70 DistributeEvenly_amount = 0;
72 DistributeEvenly_amount = amount;
73 DistributeEvenly_totalweight = totalweight;
75 float DistributeEvenly_Get(float weight)
80 f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
81 DistributeEvenly_totalweight -= weight;
82 DistributeEvenly_amount -= f;
85 float DistributeEvenly_GetRandomized(float weight)
90 f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
91 DistributeEvenly_totalweight -= weight;
92 DistributeEvenly_amount -= f;
97 void GameLogEcho(string s)
102 if (autocvar_sv_eventlog_files)
107 matches = autocvar_sv_eventlog_files_counter + 1;
108 cvar_set("sv_eventlog_files_counter", itos(matches));
111 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
112 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
113 logfile = fopen(fn, FILE_APPEND);
114 fputs(logfile, ":logversion:3\n");
118 if (autocvar_sv_eventlog_files_timestamps)
119 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
121 fputs(logfile, strcat(s, "\n"));
124 if (autocvar_sv_eventlog_console)
133 // will be opened later
138 if (logfile_open && logfile >= 0)
145 entity findnearest(vector point, .string field, string value, vector axismod)
156 localhead = find(world, field, value);
159 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
160 dist = localhead.oldorigin;
162 dist = localhead.origin;
164 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
167 for (i = 0; i < num_nearest; ++i)
169 if (len < nearest_length[i])
173 // now i tells us where to insert at
174 // INSERTION SORT! YOU'VE SEEN IT! RUN!
175 if (i < NUM_NEAREST_ENTITIES)
177 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
179 nearest_length[j + 1] = nearest_length[j];
180 nearest_entity[j + 1] = nearest_entity[j];
182 nearest_length[i] = len;
183 nearest_entity[i] = localhead;
184 if (num_nearest < NUM_NEAREST_ENTITIES)
185 num_nearest = num_nearest + 1;
188 localhead = find(localhead, field, value);
191 // now use the first one from our list that we can see
192 for (i = 0; i < num_nearest; ++i)
194 traceline(point, nearest_entity[i].origin, true, world);
195 if (trace_fraction == 1)
199 dprint("Nearest point (");
200 dprint(nearest_entity[0].netname);
201 dprint(") is not visible, using a visible one.\n");
203 return nearest_entity[i];
207 if (num_nearest == 0)
210 dprint("Not seeing any location point, using nearest as fallback.\n");
212 dprint("Candidates were: ");
213 for(j = 0; j < num_nearest; ++j)
217 dprint(nearest_entity[j].netname);
222 return nearest_entity[0];
225 string NearestLocation(vector p)
230 loc = findnearest(p, classname, "target_location", '1 1 1');
237 loc = findnearest(p, target, "###item###", '1 1 4');
244 string formatmessage(string msg)
256 ammoitems = "batteries";
257 if(self.items & ITEM_Plasma.m_itemid) ammoitems = ITEM_Plasma.m_name;
258 if(self.items & ITEM_Cells.m_itemid) ammoitems = ITEM_Cells.m_name;
259 if(self.items & ITEM_Rockets.m_itemid) ammoitems = ITEM_Rockets.m_name;
260 if(self.items & ITEM_Shells.m_itemid) ammoitems = ITEM_Shells.m_name;
262 WarpZone_crosshair_trace(self);
263 cursor = trace_endpos;
264 cursor_ent = trace_ent;
268 break; // too many replacements
271 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
272 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
285 replacement = substring(msg, p, 2);
286 escape = substring(msg, p + 1, 1);
290 case "%": replacement = "%"; break;
291 case "\\":replacement = "\\"; break;
292 case "n": replacement = "\n"; break;
293 case "a": replacement = ftos(floor(self.armorvalue)); break;
294 case "h": replacement = ftos(floor(self.health)); break;
295 case "l": replacement = NearestLocation(self.origin); break;
296 case "y": replacement = NearestLocation(cursor); break;
297 case "d": replacement = NearestLocation(self.death_origin); break;
298 case "w": replacement = WEP_NAME((!self.weapon) ? (!self.switchweapon ? self.cnt : self.switchweapon) : self.weapon); break;
299 case "W": replacement = ammoitems; break;
300 case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
301 case "s": replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); break;
302 case "S": replacement = ftos(vlen(self.velocity)); break;
305 MUTATOR_CALLHOOK(FormatMessage, escape, replacement);
310 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
311 p = p + strlen(replacement);
316 float boolean(float value) { // if value is 0 return false (0), otherwise return true (1)
317 return (value == 0) ? false : true;
326 >0: receives a cvar from name=argv(f) value=argv(f+1)
328 void GetCvars_handleString(string thisname, float f, .string field, string name)
333 strunzone(self.(field));
334 self.(field) = string_null;
338 if (thisname == name)
341 strunzone(self.(field));
342 self.(field) = strzone(argv(f + 1));
346 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
348 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
350 GetCvars_handleString(thisname, f, field, name);
351 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
352 if (thisname == name)
354 string s = func(strcat1(self.(field)));
355 if (s != self.(field))
357 strunzone(self.(field));
358 self.(field) = strzone(s);
362 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
369 if (thisname == name)
370 self.(field) = stof(argv(f + 1));
373 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
375 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
382 if (thisname == name)
386 self.(field) = stof(argv(f + 1));
395 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
398 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
401 o = W_FixWeaponOrder_ForceComplete(wo);
402 if(self.weaponorder_byimpulse)
404 strunzone(self.weaponorder_byimpulse);
405 self.weaponorder_byimpulse = string_null;
407 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
410 void GetCvars(float f)
412 string s = string_null;
415 s = strcat1(argv(f));
419 MUTATOR_CALLHOOK(GetCvars);
421 Notification_GetCvars();
423 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
424 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
425 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
426 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
427 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
428 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
429 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
430 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
431 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
432 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
437 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
438 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
439 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
440 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
441 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
442 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
443 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
444 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
445 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
446 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
447 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
449 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
450 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
452 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
453 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
454 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
455 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
456 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
458 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
461 if (s == "cl_weaponpriority")
462 self.switchweapon = w_getbestweapon(self);
463 if (s == "cl_allow_uidtracking")
464 PlayerStats_GameReport_AddPlayer(self);
468 // decolorizes and team colors the player name when needed
469 string playername(entity p)
472 if (teamplay && !intermission_running && IS_PLAYER(p))
474 t = Team_ColorCode(p.team);
475 return strcat(t, strdecolorize(p.netname));
481 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
483 int i = weaponinfo.weapon;
489 if (g_lms || g_ca || allguns)
491 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
497 d = (i == WEP_SHOTGUN.m_id);
499 d = 0; // weapon is set a few lines later
501 d = !(!weaponinfo.weaponstart);
503 if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
504 d |= (i == WEP_HOOK.m_id);
505 if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
508 float t = weaponinfo.weaponstartoverride;
510 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
515 // 4: is set by default?
524 void readplayerstartcvars()
530 // initialize starting values for players
531 start_weapons = '0 0 0';
532 start_weapons_default = '0 0 0';
533 start_weapons_defaultmask = '0 0 0';
535 start_ammo_shells = 0;
536 start_ammo_nails = 0;
537 start_ammo_rockets = 0;
538 start_ammo_cells = 0;
539 start_ammo_plasma = 0;
540 start_health = cvar("g_balance_health_start");
541 start_armorvalue = cvar("g_balance_armor_start");
544 g_weaponarena_weapons = '0 0 0';
546 s = cvar_string("g_weaponarena");
547 if (s == "0" || s == "")
549 if(g_ca || g_freezetag)
553 if (s == "0" || s == "")
559 // forcibly turn off weaponarena
561 else if (s == "all" || s == "1")
564 g_weaponarena_list = "All Weapons";
565 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
567 e = get_weaponinfo(j);
568 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
569 g_weaponarena_weapons |= WepSet_FromWeapon(j);
572 else if (s == "most")
575 g_weaponarena_list = "Most Weapons";
576 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
578 e = get_weaponinfo(j);
579 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
580 if (e.spawnflags & WEP_FLAG_NORMAL)
581 g_weaponarena_weapons |= WepSet_FromWeapon(j);
584 else if (s == "none")
587 g_weaponarena_list = "No Weapons";
592 t = tokenize_console(s);
593 g_weaponarena_list = "";
594 for (i = 0; i < t; ++i)
597 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
599 e = get_weaponinfo(j);
602 g_weaponarena_weapons |= WepSet_FromWeapon(j);
603 g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
609 print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
612 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
616 g_weaponarena_random = cvar("g_weaponarena_random");
618 g_weaponarena_random = 0;
619 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
623 g_weapon_stay = 0; // incompatible
624 start_weapons = g_weaponarena_weapons;
625 start_items |= IT_UNLIMITED_AMMO;
629 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
631 e = get_weaponinfo(i);
632 int w = want_weapon(e, false);
634 start_weapons |= WepSet_FromWeapon(i);
636 start_weapons_default |= WepSet_FromWeapon(i);
638 start_weapons_defaultmask |= WepSet_FromWeapon(i);
642 if(!cvar("g_use_ammunition"))
643 start_items |= IT_UNLIMITED_AMMO;
645 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
647 start_ammo_shells = 999;
648 start_ammo_nails = 999;
649 start_ammo_rockets = 999;
650 start_ammo_cells = 999;
651 start_ammo_plasma = 999;
652 start_ammo_fuel = 999;
656 start_ammo_shells = cvar("g_start_ammo_shells");
657 start_ammo_nails = cvar("g_start_ammo_nails");
658 start_ammo_rockets = cvar("g_start_ammo_rockets");
659 start_ammo_cells = cvar("g_start_ammo_cells");
660 start_ammo_plasma = cvar("g_start_ammo_plasma");
661 start_ammo_fuel = cvar("g_start_ammo_fuel");
666 warmup_start_ammo_shells = start_ammo_shells;
667 warmup_start_ammo_nails = start_ammo_nails;
668 warmup_start_ammo_rockets = start_ammo_rockets;
669 warmup_start_ammo_cells = start_ammo_cells;
670 warmup_start_ammo_plasma = start_ammo_plasma;
671 warmup_start_ammo_fuel = start_ammo_fuel;
672 warmup_start_health = start_health;
673 warmup_start_armorvalue = start_armorvalue;
674 warmup_start_weapons = start_weapons;
675 warmup_start_weapons_default = start_weapons_default;
676 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
678 if (!g_weaponarena && !g_ca && !g_freezetag)
680 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
681 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
682 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
683 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
684 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
685 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
686 warmup_start_health = cvar("g_warmup_start_health");
687 warmup_start_armorvalue = cvar("g_warmup_start_armor");
688 warmup_start_weapons = '0 0 0';
689 warmup_start_weapons_default = '0 0 0';
690 warmup_start_weapons_defaultmask = '0 0 0';
691 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
693 e = get_weaponinfo(i);
694 int w = want_weapon(e, g_warmup_allguns);
696 warmup_start_weapons |= WepSet_FromWeapon(i);
698 warmup_start_weapons_default |= WepSet_FromWeapon(i);
700 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
706 start_items |= ITEM_Jetpack.m_itemid;
708 MUTATOR_CALLHOOK(SetStartItems);
710 if ((start_items & ITEM_Jetpack.m_itemid) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
712 start_items |= ITEM_JetpackRegen.m_itemid;
713 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
714 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
717 WepSet precache_weapons = start_weapons;
718 if (g_warmup_allguns != 1)
719 precache_weapons |= warmup_start_weapons;
720 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
722 e = get_weaponinfo(i);
723 if(precache_weapons & WepSet_FromWeapon(i))
724 WEP_ACTION(i, WR_INIT);
727 start_ammo_shells = max(0, start_ammo_shells);
728 start_ammo_nails = max(0, start_ammo_nails);
729 start_ammo_rockets = max(0, start_ammo_rockets);
730 start_ammo_cells = max(0, start_ammo_cells);
731 start_ammo_plasma = max(0, start_ammo_plasma);
732 start_ammo_fuel = max(0, start_ammo_fuel);
734 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
735 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
736 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
737 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
738 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
739 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
742 float sound_allowed(float destin, entity e)
744 // sounds from world may always pass
747 if (e.classname == "body")
749 else if (e.realowner && e.realowner != e)
751 else if (e.owner && e.owner != e)
756 // sounds to self may always pass
757 if (destin == MSG_ONE)
760 // sounds by players can be removed
761 if (autocvar_bot_sound_monopoly)
762 if (IS_REAL_CLIENT(e))
764 // anything else may pass
769 void sound(entity e, float chan, string samp, float vol, float attenu)
771 if (!sound_allowed(MSG_BROADCAST, e))
773 sound7(e, chan, samp, vol, attenu, 0, 0);
776 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
780 if (!sound_allowed(_dest, e))
783 entno = num_for_edict(e);
784 idx = precache_sound_index(samp);
789 attenu = floor(attenu * 64);
790 vol = floor(vol * 255);
793 sflags |= SND_VOLUME;
795 sflags |= SND_ATTENUATION;
796 if (entno >= 8192 || chan < 0 || chan > 7)
797 sflags |= SND_LARGEENTITY;
799 sflags |= SND_LARGESOUND;
801 WriteByte(_dest, SVC_SOUND);
802 WriteByte(_dest, sflags);
803 if (sflags & SND_VOLUME)
804 WriteByte(_dest, vol);
805 if (sflags & SND_ATTENUATION)
806 WriteByte(_dest, attenu);
807 if (sflags & SND_LARGEENTITY)
809 WriteShort(_dest, entno);
810 WriteByte(_dest, chan);
814 WriteShort(_dest, entno * 8 + chan);
816 if (sflags & SND_LARGESOUND)
817 WriteShort(_dest, idx);
819 WriteByte(_dest, idx);
821 WriteCoord(_dest, o.x);
822 WriteCoord(_dest, o.y);
823 WriteCoord(_dest, o.z);
825 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
829 if (!sound_allowed(_dest, e))
832 o = e.origin + 0.5 * (e.mins + e.maxs);
833 soundtoat(_dest, e, o, chan, samp, vol, _atten);
835 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
837 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
839 void stopsoundto(float _dest, entity e, float chan)
843 if (!sound_allowed(_dest, e))
846 entno = num_for_edict(e);
848 if (entno >= 8192 || chan < 0 || chan > 7)
851 idx = precache_sound_index("misc/null.wav");
852 sflags = SND_LARGEENTITY;
854 sflags |= SND_LARGESOUND;
855 WriteByte(_dest, SVC_SOUND);
856 WriteByte(_dest, sflags);
857 WriteShort(_dest, entno);
858 WriteByte(_dest, chan);
859 if (sflags & SND_LARGESOUND)
860 WriteShort(_dest, idx);
862 WriteByte(_dest, idx);
863 WriteCoord(_dest, e.origin.x);
864 WriteCoord(_dest, e.origin.y);
865 WriteCoord(_dest, e.origin.z);
869 WriteByte(_dest, SVC_STOPSOUND);
870 WriteShort(_dest, entno * 8 + chan);
873 void stopsound(entity e, float chan)
875 if (!sound_allowed(MSG_BROADCAST, e))
878 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
879 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
882 void play2(entity e, string filename)
884 //stuffcmd(e, strcat("play2 ", filename, "\n"));
886 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
889 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
891 float spamsound(entity e, float chan, string samp, float vol, float _atten)
893 if (!sound_allowed(MSG_BROADCAST, e))
896 if (time > e.spamtime)
899 sound(e, chan, samp, vol, _atten);
905 void play2team(float t, string filename)
909 if (autocvar_bot_sound_monopoly)
912 FOR_EACH_REALPLAYER(head)
915 play2(head, filename);
919 void play2all(string samp)
921 if (autocvar_bot_sound_monopoly)
924 sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
927 void PrecachePlayerSounds(string f);
928 void precache_playermodel(string m)
930 float globhandle, i, n;
933 if(substring(m, -9,5) == "_lod1")
935 if(substring(m, -9,5) == "_lod2")
938 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
941 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
945 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
948 n = search_getsize(globhandle);
949 for (i = 0; i < n; ++i)
951 //print(search_getfilename(globhandle, i), "\n");
952 f = search_getfilename(globhandle, i);
953 PrecachePlayerSounds(f);
955 search_end(globhandle);
957 void precache_all_playermodels(string pattern)
959 float globhandle, i, n;
962 globhandle = search_begin(pattern, true, false);
965 n = search_getsize(globhandle);
966 for (i = 0; i < n; ++i)
968 //print(search_getfilename(globhandle, i), "\n");
969 f = search_getfilename(globhandle, i);
970 precache_playermodel(f);
972 search_end(globhandle);
977 // gamemode related things
978 precache_model ("models/misc/chatbubble.spr");
979 precache_model("models/ice/ice.md3");
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)
995 s = autocvar_sv_defaultplayermodel_red;
997 precache_playermodel(s);
998 s = autocvar_sv_defaultplayermodel_blue;
1000 precache_playermodel(s);
1001 s = autocvar_sv_defaultplayermodel_yellow;
1003 precache_playermodel(s);
1004 s = autocvar_sv_defaultplayermodel_pink;
1006 precache_playermodel(s);
1007 s = autocvar_sv_defaultplayermodel;
1009 precache_playermodel(s);
1014 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1015 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1018 // gore and miscellaneous sounds
1019 //precache_sound ("misc/h2ohit.wav");
1020 precache_model ("models/hook.md3");
1021 precache_sound ("misc/armorimpact.wav");
1022 precache_sound ("misc/bodyimpact1.wav");
1023 precache_sound ("misc/bodyimpact2.wav");
1024 precache_sound ("misc/gib.wav");
1025 precache_sound ("misc/gib_splat01.wav");
1026 precache_sound ("misc/gib_splat02.wav");
1027 precache_sound ("misc/gib_splat03.wav");
1028 precache_sound ("misc/gib_splat04.wav");
1029 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1030 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1031 precache_sound ("misc/null.wav");
1032 precache_sound ("misc/spawn.wav");
1033 precache_sound ("misc/talk.wav");
1034 precache_sound ("misc/teleport.wav");
1035 precache_sound ("misc/poweroff.wav");
1036 precache_sound ("player/lava.wav");
1037 precache_sound ("player/slime.wav");
1039 precache_model ("models/sprites/0.spr32");
1040 precache_model ("models/sprites/1.spr32");
1041 precache_model ("models/sprites/2.spr32");
1042 precache_model ("models/sprites/3.spr32");
1043 precache_model ("models/sprites/4.spr32");
1044 precache_model ("models/sprites/5.spr32");
1045 precache_model ("models/sprites/6.spr32");
1046 precache_model ("models/sprites/7.spr32");
1047 precache_model ("models/sprites/8.spr32");
1048 precache_model ("models/sprites/9.spr32");
1049 precache_model ("models/sprites/10.spr32");
1051 // common weapon precaches
1052 precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here
1053 precache_sound (W_Sound("weapon_switch"));
1054 precache_sound (W_Sound("weaponpickup"));
1055 precache_sound (W_Sound("unavailable"));
1056 precache_sound (W_Sound("dryfire"));
1057 if (g_grappling_hook)
1059 precache_sound (W_Sound("hook_fire")); // hook
1060 precache_sound (W_Sound("hook_impact")); // hook
1063 precache_model("models/elaser.mdl");
1064 precache_model("models/laser.mdl");
1065 precache_model("models/ebomb.mdl");
1068 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1070 if (!self.noise && self.music) // quake 3 uses the music field
1071 self.noise = self.music;
1073 // plays music for the level if there is any
1076 precache_sound (self.noise);
1077 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1081 #include "precache-for-csqc.inc"
1085 void make_safe_for_remove(entity e)
1087 if (e.initialize_entity)
1089 entity ent, prev = world;
1090 for (ent = initialize_entity_first; ent; )
1092 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1094 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1095 // skip it in linked list
1098 prev.initialize_entity_next = ent.initialize_entity_next;
1099 ent = prev.initialize_entity_next;
1103 initialize_entity_first = ent.initialize_entity_next;
1104 ent = initialize_entity_first;
1110 ent = ent.initialize_entity_next;
1116 void objerror(string s)
1118 make_safe_for_remove(self);
1119 builtin_objerror(s);
1122 .float remove_except_protected_forbidden;
1123 void remove_except_protected(entity e)
1125 if(e.remove_except_protected_forbidden)
1126 error("not allowed to remove this at this point");
1130 void remove_unsafely(entity e)
1132 if(e.classname == "spike")
1133 error("Removing spikes is forbidden (crylink bug), please report");
1137 void remove_safely(entity e)
1139 make_safe_for_remove(e);
1143 void InitializeEntity(entity e, void(void) func, float order)
1147 if (!e || e.initialize_entity)
1149 // make a proxy initializer entity
1153 e.classname = "initialize_entity";
1157 e.initialize_entity = func;
1158 e.initialize_entity_order = order;
1160 cur = initialize_entity_first;
1164 if (!cur || cur.initialize_entity_order > order)
1166 // insert between prev and cur
1168 prev.initialize_entity_next = e;
1170 initialize_entity_first = e;
1171 e.initialize_entity_next = cur;
1175 cur = cur.initialize_entity_next;
1178 void InitializeEntitiesRun()
1181 startoflist = initialize_entity_first;
1182 initialize_entity_first = world;
1183 remove = remove_except_protected;
1184 for (self = startoflist; self; self = self.initialize_entity_next)
1186 self.remove_except_protected_forbidden = 1;
1188 for (self = startoflist; self; )
1191 var void(void) func;
1192 e = self.initialize_entity_next;
1193 func = self.initialize_entity;
1194 self.initialize_entity_order = 0;
1195 self.initialize_entity = func_null;
1196 self.initialize_entity_next = world;
1197 self.remove_except_protected_forbidden = 0;
1198 if (self.classname == "initialize_entity")
1202 builtin_remove(self);
1205 //dprint("Delayed initialization: ", self.classname, "\n");
1211 backtrace(strcat("Null function in: ", self.classname, "\n"));
1215 remove = remove_unsafely;
1218 void UncustomizeEntitiesRun()
1222 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1223 self.uncustomizeentityforclient();
1226 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1228 e.customizeentityforclient = customizer;
1229 e.uncustomizeentityforclient = uncustomizer;
1230 e.uncustomizeentityforclient_set = !!uncustomizer;
1233 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1237 if (e.classname == "")
1238 e.classname = "net_linked";
1240 if (e.model == "" || self.modelindex == 0)
1244 setmodel(e, "null");
1248 e.SendEntity = sendfunc;
1249 e.SendFlags = 0xFFFFFF;
1252 e.effects |= EF_NODEPTHTEST;
1256 e.nextthink = time + dt;
1257 e.think = SUB_Remove;
1262 .float(entity) isEliminated;
1263 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1267 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1268 WriteByte(MSG_ENTITY, sendflags);
1272 for(i = 1; i <= maxclients; i += 8)
1274 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1276 if(eliminatedPlayers.isEliminated(e))
1279 WriteByte(MSG_ENTITY, f);
1286 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1288 if(eliminatedPlayers)
1290 backtrace("Can't spawn eliminatedPlayers again!");
1293 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1294 eliminatedPlayers.isEliminated = isEliminated_func;
1298 void adaptor_think2touch()
1307 void adaptor_think2use()
1319 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1321 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
1322 self.projectiledeathtype |= HITTYPE_SPLASH;
1323 adaptor_think2use();
1326 // deferred dropping
1327 void DropToFloor_Handler()
1329 builtin_droptofloor();
1330 self.dropped_origin = self.origin;
1335 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1340 float trace_hits_box_a0, trace_hits_box_a1;
1342 float trace_hits_box_1d(float end, float thmi, float thma)
1346 // just check if x is in range
1354 // do the trace with respect to x
1355 // 0 -> end has to stay in thmi -> thma
1356 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1357 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1358 if (trace_hits_box_a0 > trace_hits_box_a1)
1364 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1369 // now it is a trace from 0 to end
1371 trace_hits_box_a0 = 0;
1372 trace_hits_box_a1 = 1;
1374 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1376 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1378 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1384 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1386 return trace_hits_box(start, end, thmi - ma, thma - mi);
1389 float SUB_NoImpactCheck()
1391 // zero hitcontents = this is not the real impact, but either the
1392 // mirror-impact of something hitting the projectile instead of the
1393 // projectile hitting the something, or a touchareagrid one. Neither of
1394 // these stop the projectile from moving, so...
1395 if(trace_dphitcontents == 0)
1397 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1398 dprintf("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));
1401 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1403 if (other == world && self.size != '0 0 0')
1406 tic = self.velocity * sys_frametime;
1407 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1408 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1409 if (trace_fraction >= 1)
1411 dprint("Odd... did not hit...?\n");
1413 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1415 dprint("Detected and prevented the sky-grapple bug.\n");
1423 #define SUB_OwnerCheck() (other && (other == self.owner))
1425 void W_Crylink_Dequeue(entity e);
1426 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1428 if(SUB_OwnerCheck())
1430 if(SUB_NoImpactCheck())
1432 if(self.classname == "nade")
1433 return false; // no checks here
1434 else if(self.classname == "grapplinghook")
1435 RemoveGrapplingHook(self.realowner);
1436 else if(self.classname == "spike")
1438 W_Crylink_Dequeue(self);
1445 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1446 UpdateCSQCProjectile(self);
1451 void URI_Get_Callback(float id, float status, string data)
1453 if(url_URI_Get_Callback(id, status, data))
1457 else if (id == URI_GET_DISCARD)
1461 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1464 Curl_URI_Get_Callback(id, status, data);
1466 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1469 OnlineBanList_URI_Get_Callback(id, status, data);
1473 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1477 string uid2name(string myuid) {
1479 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1481 // FIXME remove this later after 0.6 release
1482 // convert old style broken records to correct style
1485 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1488 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1489 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1494 s = "^1Unregistered Player";
1498 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1501 vector start, org, delta, end, enddown, mstart;
1504 m = e.dphitcontentsmask;
1505 e.dphitcontentsmask = goodcontents | badcontents;
1508 delta = world.maxs - world.mins;
1512 for (i = 0; i < attempts; ++i)
1514 start.x = org.x + random() * delta.x;
1515 start.y = org.y + random() * delta.y;
1516 start.z = org.z + random() * delta.z;
1518 // rule 1: start inside world bounds, and outside
1519 // solid, and don't start from somewhere where you can
1520 // fall down to evil
1521 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1522 if (trace_fraction >= 1)
1524 if (trace_startsolid)
1526 if (trace_dphitcontents & badcontents)
1528 if (trace_dphitq3surfaceflags & badsurfaceflags)
1531 // rule 2: if we are too high, lower the point
1532 if (trace_fraction * delta.z > maxaboveground)
1533 start = trace_endpos + '0 0 1' * maxaboveground;
1534 enddown = trace_endpos;
1536 // rule 3: make sure we aren't outside the map. This only works
1537 // for somewhat well formed maps. A good rule of thumb is that
1538 // the map should have a convex outside hull.
1539 // these can be traceLINES as we already verified the starting box
1540 mstart = start + 0.5 * (e.mins + e.maxs);
1541 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1542 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1544 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1545 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1547 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1548 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1550 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1551 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1553 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1554 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1557 // rule 4: we must "see" some spawnpoint or item
1558 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1559 if(checkpvs(mstart, sp))
1560 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1564 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1565 if(checkpvs(mstart, sp))
1566 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1572 // find a random vector to "look at"
1573 end.x = org.x + random() * delta.x;
1574 end.y = org.y + random() * delta.y;
1575 end.z = org.z + random() * delta.z;
1576 end = start + normalize(end - start) * vlen(delta);
1578 // rule 4: start TO end must not be too short
1579 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1580 if (trace_startsolid)
1582 if (trace_fraction < minviewdistance / vlen(delta))
1585 // rule 5: don't want to look at sky
1586 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1589 // rule 6: we must not end up in trigger_hurt
1590 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1596 e.dphitcontentsmask = m;
1600 setorigin(e, start);
1601 e.angles = vectoangles(end - start);
1602 dprint("Needed ", ftos(i + 1), " attempts\n");
1609 void write_recordmarker(entity pl, float tstart, float dt)
1611 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1613 // also write a marker into demo files for demotc-race-record-extractor to find
1616 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1617 " ", ftos(tstart), " ", ftos(dt), "\n"));
1620 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1633 if(allowcenter) // 2: allow center handedness
1646 if(allowcenter) // 2: allow center handedness
1662 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1667 if (autocvar_g_shootfromeye)
1671 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1672 else { vecs.y = 0; vecs.z -= 2; }
1680 else if (autocvar_g_shootfromcenter)
1685 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1695 else if (autocvar_g_shootfromclient)
1697 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1702 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1704 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1708 void attach_sameorigin(entity e, entity to, string tag)
1710 vector org, t_forward, t_left, t_up, e_forward, e_up;
1713 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1714 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1715 t_forward = v_forward * tagscale;
1716 t_left = v_right * -tagscale;
1717 t_up = v_up * tagscale;
1719 e.origin_x = org * t_forward;
1720 e.origin_y = org * t_left;
1721 e.origin_z = org * t_up;
1723 // current forward and up directions
1724 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1725 e.angles = AnglesTransform_FromVAngles(e.angles);
1727 e.angles = AnglesTransform_FromAngles(e.angles);
1728 fixedmakevectors(e.angles);
1730 // untransform forward, up!
1731 e_forward.x = v_forward * t_forward;
1732 e_forward.y = v_forward * t_left;
1733 e_forward.z = v_forward * t_up;
1734 e_up.x = v_up * t_forward;
1735 e_up.y = v_up * t_left;
1736 e_up.z = v_up * t_up;
1738 e.angles = fixedvectoangles2(e_forward, e_up);
1739 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1740 e.angles = AnglesTransform_ToVAngles(e.angles);
1742 e.angles = AnglesTransform_ToAngles(e.angles);
1744 setattachment(e, to, tag);
1745 setorigin(e, e.origin);
1748 void detach_sameorigin(entity e)
1751 org = gettaginfo(e, 0);
1752 e.angles = fixedvectoangles2(v_forward, v_up);
1753 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1754 e.angles = AnglesTransform_ToVAngles(e.angles);
1756 e.angles = AnglesTransform_ToAngles(e.angles);
1758 setattachment(e, world, "");
1759 setorigin(e, e.origin);
1762 void follow_sameorigin(entity e, entity to)
1764 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1765 e.aiment = to; // make the hole follow bmodel
1766 e.punchangle = to.angles; // the original angles of bmodel
1767 e.view_ofs = e.origin - to.origin; // relative origin
1768 e.v_angle = e.angles - to.angles; // relative angles
1771 void unfollow_sameorigin(entity e)
1773 e.movetype = MOVETYPE_NONE;
1776 entity gettaginfo_relative_ent;
1777 vector gettaginfo_relative(entity e, float tag)
1779 if (!gettaginfo_relative_ent)
1781 gettaginfo_relative_ent = spawn();
1782 gettaginfo_relative_ent.effects = EF_NODRAW;
1784 gettaginfo_relative_ent.model = e.model;
1785 gettaginfo_relative_ent.modelindex = e.modelindex;
1786 gettaginfo_relative_ent.frame = e.frame;
1787 return gettaginfo(gettaginfo_relative_ent, tag);
1792 float modeleffect_SendEntity(entity to, int sf)
1795 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1798 if(self.velocity != '0 0 0')
1800 if(self.angles != '0 0 0')
1802 if(self.avelocity != '0 0 0')
1805 WriteByte(MSG_ENTITY, f);
1806 WriteShort(MSG_ENTITY, self.modelindex);
1807 WriteByte(MSG_ENTITY, self.skin);
1808 WriteByte(MSG_ENTITY, self.frame);
1809 WriteCoord(MSG_ENTITY, self.origin.x);
1810 WriteCoord(MSG_ENTITY, self.origin.y);
1811 WriteCoord(MSG_ENTITY, self.origin.z);
1814 WriteCoord(MSG_ENTITY, self.velocity.x);
1815 WriteCoord(MSG_ENTITY, self.velocity.y);
1816 WriteCoord(MSG_ENTITY, self.velocity.z);
1820 WriteCoord(MSG_ENTITY, self.angles.x);
1821 WriteCoord(MSG_ENTITY, self.angles.y);
1822 WriteCoord(MSG_ENTITY, self.angles.z);
1826 WriteCoord(MSG_ENTITY, self.avelocity.x);
1827 WriteCoord(MSG_ENTITY, self.avelocity.y);
1828 WriteCoord(MSG_ENTITY, self.avelocity.z);
1830 WriteShort(MSG_ENTITY, self.scale * 256.0);
1831 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1832 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1833 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1834 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1839 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)
1844 e.classname = "modeleffect";
1852 e.teleport_time = t1;
1856 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1860 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1863 sz = max(e.scale, e.scale2);
1864 setsize(e, e.mins * sz, e.maxs * sz);
1865 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1868 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1870 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1873 float randombit(float bits)
1875 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1884 for(f = 1; f <= bits; f *= 2)
1893 r = (r - 1) / (n - 1);
1900 float randombits(float bits, float k, float error_return)
1904 while(k > 0 && bits != r)
1906 r += randombit(bits - r);
1915 void randombit_test(float bits, float iter)
1919 print(ftos(randombit(bits)), "\n");
1924 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1926 if(halflifedist > 0)
1927 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1928 else if(halflifedist < 0)
1929 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1941 oself.think = SUB_Remove;
1942 oself.nextthink = time;
1948 Execute func() after time + fdelay.
1949 self when func is executed = self when defer is called
1951 void defer(float fdelay, void() func)
1958 e.think = defer_think;
1959 e.nextthink = time + fdelay;
1962 .string aiment_classname;
1963 .float aiment_deadflag;
1964 void SetMovetypeFollow(entity ent, entity e)
1966 // FIXME this may not be warpzone aware
1967 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1968 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.
1969 ent.aiment = e; // make the hole follow bmodel
1970 ent.punchangle = e.angles; // the original angles of bmodel
1971 ent.view_ofs = ent.origin - e.origin; // relative origin
1972 ent.v_angle = ent.angles - e.angles; // relative angles
1973 ent.aiment_classname = strzone(e.classname);
1974 ent.aiment_deadflag = e.deadflag;
1976 void UnsetMovetypeFollow(entity ent)
1978 ent.movetype = MOVETYPE_FLY;
1979 PROJECTILE_MAKETRIGGER(ent);
1982 float LostMovetypeFollow(entity ent)
1985 if(ent.movetype != MOVETYPE_FOLLOW)
1991 if(ent.aiment.classname != ent.aiment_classname)
1993 if(ent.aiment.deadflag != ent.aiment_deadflag)
1999 float isPushable(entity e)
2008 case "droppedweapon":
2009 case "keepawayball":
2010 case "nexball_basketball":
2011 case "nexball_football":
2013 case "bullet": // antilagged bullets can't hit this either
2016 if (e.projectiledeathtype)