1 #include "miscfunctions.qh"
4 #include "command/common.qh"
5 #include "constants.qh"
8 #include "mutators/mutators_include.qh"
10 #include "weapons/accuracy.qh"
11 #include "weapons/csqcprojectile.qh"
12 #include "weapons/selection.qh"
13 #include "../common/command/generic.qh"
14 #include "../common/constants.qh"
15 #include "../common/deathtypes.qh"
16 #include "../common/mapinfo.qh"
17 #include "../common/notifications.qh"
18 #include "../common/playerstats.qh"
19 #include "../common/teams.qh"
20 #include "../common/triggers/subs.qh"
21 #include "../common/util.qh"
22 #include "../common/turrets/sv_turrets.qh"
23 #include "../common/weapons/all.qh"
24 #include "../csqcmodellib/sv_model.qh"
25 #include "../warpzonelib/anglestransform.qh"
26 #include "../warpzonelib/server.qh"
28 void crosshair_trace(entity pl)
30 traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
32 void crosshair_trace_plusvisibletriggers(entity pl)
36 first = findchainfloat(solid, SOLID_TRIGGER);
38 for (e = first; e; e = e.chain)
44 for (e = first; e; e = e.chain)
45 e.solid = SOLID_TRIGGER;
47 void WarpZone_crosshair_trace(entity pl)
49 WarpZone_traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
53 string admin_name(void)
55 if(autocvar_sv_adminnick != "")
56 return autocvar_sv_adminnick;
58 return "SERVER ADMIN";
61 void DistributeEvenly_Init(float amount, float totalweight)
63 if (DistributeEvenly_amount)
65 LOG_TRACE("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
66 LOG_TRACE(ftos(DistributeEvenly_totalweight), " left!)\n");
69 DistributeEvenly_amount = 0;
71 DistributeEvenly_amount = amount;
72 DistributeEvenly_totalweight = totalweight;
74 float DistributeEvenly_Get(float weight)
79 f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
80 DistributeEvenly_totalweight -= weight;
81 DistributeEvenly_amount -= f;
84 float DistributeEvenly_GetRandomized(float weight)
89 f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
90 DistributeEvenly_totalweight -= weight;
91 DistributeEvenly_amount -= f;
96 void GameLogEcho(string s)
101 if (autocvar_sv_eventlog_files)
106 matches = autocvar_sv_eventlog_files_counter + 1;
107 cvar_set("sv_eventlog_files_counter", itos(matches));
110 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
111 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
112 logfile = fopen(fn, FILE_APPEND);
113 fputs(logfile, ":logversion:3\n");
117 if (autocvar_sv_eventlog_files_timestamps)
118 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
120 fputs(logfile, strcat(s, "\n"));
123 if (autocvar_sv_eventlog_console)
132 // will be opened later
137 if (logfile_open && logfile >= 0)
144 entity findnearest(vector point, .string field, string value, vector axismod)
155 localhead = find(world, field, value);
158 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
159 dist = localhead.oldorigin;
161 dist = localhead.origin;
163 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
166 for (i = 0; i < num_nearest; ++i)
168 if (len < nearest_length[i])
172 // now i tells us where to insert at
173 // INSERTION SORT! YOU'VE SEEN IT! RUN!
174 if (i < NUM_NEAREST_ENTITIES)
176 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
178 nearest_length[j + 1] = nearest_length[j];
179 nearest_entity[j + 1] = nearest_entity[j];
181 nearest_length[i] = len;
182 nearest_entity[i] = localhead;
183 if (num_nearest < NUM_NEAREST_ENTITIES)
184 num_nearest = num_nearest + 1;
187 localhead = find(localhead, field, value);
190 // now use the first one from our list that we can see
191 for (i = 0; i < num_nearest; ++i)
193 traceline(point, nearest_entity[i].origin, true, world);
194 if (trace_fraction == 1)
198 LOG_TRACE("Nearest point (");
199 LOG_TRACE(nearest_entity[0].netname);
200 LOG_TRACE(") is not visible, using a visible one.\n");
202 return nearest_entity[i];
206 if (num_nearest == 0)
209 LOG_TRACE("Not seeing any location point, using nearest as fallback.\n");
211 dprint("Candidates were: ");
212 for(j = 0; j < num_nearest; ++j)
216 dprint(nearest_entity[j].netname);
221 return nearest_entity[0];
224 string NearestLocation(vector p)
229 loc = findnearest(p, classname, "target_location", '1 1 1');
236 loc = findnearest(p, target, "###item###", '1 1 4');
243 string formatmessage(string msg)
255 ammoitems = "batteries";
256 if(self.items & ITEM_Plasma.m_itemid) ammoitems = ITEM_Plasma.m_name;
257 if(self.items & ITEM_Cells.m_itemid) ammoitems = ITEM_Cells.m_name;
258 if(self.items & ITEM_Rockets.m_itemid) ammoitems = ITEM_Rockets.m_name;
259 if(self.items & ITEM_Shells.m_itemid) ammoitems = ITEM_Shells.m_name;
261 WarpZone_crosshair_trace(self);
262 cursor = trace_endpos;
263 cursor_ent = trace_ent;
267 break; // too many replacements
270 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
271 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
284 replacement = substring(msg, p, 2);
285 escape = substring(msg, p + 1, 1);
289 case "%": replacement = "%"; break;
290 case "\\":replacement = "\\"; break;
291 case "n": replacement = "\n"; break;
292 case "a": replacement = ftos(floor(self.armorvalue)); break;
293 case "h": replacement = ftos(floor(self.health)); break;
294 case "l": replacement = NearestLocation(self.origin); break;
295 case "y": replacement = NearestLocation(cursor); break;
296 case "d": replacement = NearestLocation(self.death_origin); break;
297 case "w": replacement = WEP_NAME((!self.weapon) ? (!self.switchweapon ? self.cnt : self.switchweapon) : self.weapon); break;
298 case "W": replacement = ammoitems; break;
299 case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
300 case "s": replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); break;
301 case "S": replacement = ftos(vlen(self.velocity)); break;
304 MUTATOR_CALLHOOK(FormatMessage, escape, replacement);
309 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
310 p = p + strlen(replacement);
321 >0: receives a cvar from name=argv(f) value=argv(f+1)
323 void GetCvars_handleString(string thisname, float f, .string field, string name)
328 strunzone(self.(field));
329 self.(field) = string_null;
333 if (thisname == name)
336 strunzone(self.(field));
337 self.(field) = strzone(argv(f + 1));
341 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
343 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
345 GetCvars_handleString(thisname, f, field, name);
346 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
347 if (thisname == name)
349 string s = func(strcat1(self.(field)));
350 if (s != self.(field))
352 strunzone(self.(field));
353 self.(field) = strzone(s);
357 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
364 if (thisname == name)
365 self.(field) = stof(argv(f + 1));
368 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
370 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
377 if (thisname == name)
381 self.(field) = stof(argv(f + 1));
390 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
393 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
396 o = W_FixWeaponOrder_ForceComplete(wo);
397 if(self.weaponorder_byimpulse)
399 strunzone(self.weaponorder_byimpulse);
400 self.weaponorder_byimpulse = string_null;
402 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
405 void GetCvars(float f)
407 string s = string_null;
410 s = strcat1(argv(f));
414 MUTATOR_CALLHOOK(GetCvars);
416 Notification_GetCvars();
418 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
419 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
420 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
421 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
422 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
423 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
424 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
425 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
426 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
427 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
428 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
429 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
430 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
431 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
432 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
433 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
434 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
435 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
436 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
437 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
438 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
439 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
440 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
441 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
442 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
444 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
445 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
447 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
448 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
449 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
450 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
451 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
453 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
456 if (s == "cl_weaponpriority")
457 self.switchweapon = w_getbestweapon(self);
458 if (s == "cl_allow_uidtracking")
459 PlayerStats_GameReport_AddPlayer(self);
463 // decolorizes and team colors the player name when needed
464 string playername(entity p)
467 if (teamplay && !intermission_running && IS_PLAYER(p))
469 t = Team_ColorCode(p.team);
470 return strcat(t, strdecolorize(p.netname));
476 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
478 int i = weaponinfo.weapon;
484 if (g_lms || g_ca || allguns)
486 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
492 d = (i == WEP_SHOTGUN.m_id);
494 d = 0; // weapon is set a few lines later
496 d = !(!weaponinfo.weaponstart);
498 if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
499 d |= (i == WEP_HOOK.m_id);
500 if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
503 float t = weaponinfo.weaponstartoverride;
505 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
510 // 4: is set by default?
519 void readplayerstartcvars()
525 // initialize starting values for players
526 start_weapons = '0 0 0';
527 start_weapons_default = '0 0 0';
528 start_weapons_defaultmask = '0 0 0';
530 start_ammo_shells = 0;
531 start_ammo_nails = 0;
532 start_ammo_rockets = 0;
533 start_ammo_cells = 0;
534 start_ammo_plasma = 0;
535 start_health = cvar("g_balance_health_start");
536 start_armorvalue = cvar("g_balance_armor_start");
539 g_weaponarena_weapons = '0 0 0';
541 s = cvar_string("g_weaponarena");
542 if (s == "0" || s == "")
544 if(g_ca || g_freezetag)
548 if (s == "0" || s == "")
554 // forcibly turn off weaponarena
556 else if (s == "all" || s == "1")
559 g_weaponarena_list = "All Weapons";
560 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
562 e = get_weaponinfo(j);
563 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
564 g_weaponarena_weapons |= WepSet_FromWeapon(j);
567 else if (s == "most")
570 g_weaponarena_list = "Most Weapons";
571 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
573 e = get_weaponinfo(j);
574 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
575 if (e.spawnflags & WEP_FLAG_NORMAL)
576 g_weaponarena_weapons |= WepSet_FromWeapon(j);
579 else if (s == "none")
582 g_weaponarena_list = "No Weapons";
587 t = tokenize_console(s);
588 g_weaponarena_list = "";
589 for (i = 0; i < t; ++i)
592 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
594 e = get_weaponinfo(j);
597 g_weaponarena_weapons |= WepSet_FromWeapon(j);
598 g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
604 LOG_INFO("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
607 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
611 g_weaponarena_random = cvar("g_weaponarena_random");
613 g_weaponarena_random = 0;
614 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
618 g_weapon_stay = 0; // incompatible
619 start_weapons = g_weaponarena_weapons;
620 start_items |= IT_UNLIMITED_AMMO;
624 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
626 e = get_weaponinfo(i);
627 int w = want_weapon(e, false);
629 start_weapons |= WepSet_FromWeapon(i);
631 start_weapons_default |= WepSet_FromWeapon(i);
633 start_weapons_defaultmask |= WepSet_FromWeapon(i);
637 if(!cvar("g_use_ammunition"))
638 start_items |= IT_UNLIMITED_AMMO;
640 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
642 start_ammo_shells = 999;
643 start_ammo_nails = 999;
644 start_ammo_rockets = 999;
645 start_ammo_cells = 999;
646 start_ammo_plasma = 999;
647 start_ammo_fuel = 999;
651 start_ammo_shells = cvar("g_start_ammo_shells");
652 start_ammo_nails = cvar("g_start_ammo_nails");
653 start_ammo_rockets = cvar("g_start_ammo_rockets");
654 start_ammo_cells = cvar("g_start_ammo_cells");
655 start_ammo_plasma = cvar("g_start_ammo_plasma");
656 start_ammo_fuel = cvar("g_start_ammo_fuel");
661 warmup_start_ammo_shells = start_ammo_shells;
662 warmup_start_ammo_nails = start_ammo_nails;
663 warmup_start_ammo_rockets = start_ammo_rockets;
664 warmup_start_ammo_cells = start_ammo_cells;
665 warmup_start_ammo_plasma = start_ammo_plasma;
666 warmup_start_ammo_fuel = start_ammo_fuel;
667 warmup_start_health = start_health;
668 warmup_start_armorvalue = start_armorvalue;
669 warmup_start_weapons = start_weapons;
670 warmup_start_weapons_default = start_weapons_default;
671 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
673 if (!g_weaponarena && !g_ca && !g_freezetag)
675 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
676 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
677 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
678 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
679 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
680 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
681 warmup_start_health = cvar("g_warmup_start_health");
682 warmup_start_armorvalue = cvar("g_warmup_start_armor");
683 warmup_start_weapons = '0 0 0';
684 warmup_start_weapons_default = '0 0 0';
685 warmup_start_weapons_defaultmask = '0 0 0';
686 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
688 e = get_weaponinfo(i);
689 int w = want_weapon(e, g_warmup_allguns);
691 warmup_start_weapons |= WepSet_FromWeapon(i);
693 warmup_start_weapons_default |= WepSet_FromWeapon(i);
695 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
701 start_items |= ITEM_Jetpack.m_itemid;
703 MUTATOR_CALLHOOK(SetStartItems);
705 if ((start_items & ITEM_Jetpack.m_itemid) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
707 start_items |= ITEM_JetpackRegen.m_itemid;
708 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
709 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
712 WepSet precache_weapons = start_weapons;
713 if (g_warmup_allguns != 1)
714 precache_weapons |= warmup_start_weapons;
715 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
717 e = get_weaponinfo(i);
718 if(precache_weapons & WepSet_FromWeapon(i))
719 WEP_ACTION(i, WR_INIT);
722 start_ammo_shells = max(0, start_ammo_shells);
723 start_ammo_nails = max(0, start_ammo_nails);
724 start_ammo_rockets = max(0, start_ammo_rockets);
725 start_ammo_cells = max(0, start_ammo_cells);
726 start_ammo_plasma = max(0, start_ammo_plasma);
727 start_ammo_fuel = max(0, start_ammo_fuel);
729 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
730 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
731 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
732 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
733 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
734 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
737 float sound_allowed(float destin, entity e)
739 // sounds from world may always pass
742 if (e.classname == "body")
744 else if (e.realowner && e.realowner != e)
746 else if (e.owner && e.owner != e)
751 // sounds to self may always pass
752 if (destin == MSG_ONE)
755 // sounds by players can be removed
756 if (autocvar_bot_sound_monopoly)
757 if (IS_REAL_CLIENT(e))
759 // anything else may pass
764 void sound(entity e, float chan, string samp, float vol, float attenu)
766 if (!sound_allowed(MSG_BROADCAST, e))
768 sound7(e, chan, samp, vol, attenu, 0, 0);
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("misc/null.wav");
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
973 precache_model ("models/misc/chatbubble.spr");
974 precache_model("models/ice/ice.md3");
976 // Precache all player models if desired
977 if (autocvar_sv_precacheplayermodels)
979 PrecachePlayerSounds("sound/player/default.sounds");
980 precache_all_playermodels("models/player/*.zym");
981 precache_all_playermodels("models/player/*.dpm");
982 precache_all_playermodels("models/player/*.md3");
983 precache_all_playermodels("models/player/*.psk");
984 precache_all_playermodels("models/player/*.iqm");
987 if (autocvar_sv_defaultcharacter)
990 s = autocvar_sv_defaultplayermodel_red;
992 precache_playermodel(s);
993 s = autocvar_sv_defaultplayermodel_blue;
995 precache_playermodel(s);
996 s = autocvar_sv_defaultplayermodel_yellow;
998 precache_playermodel(s);
999 s = autocvar_sv_defaultplayermodel_pink;
1001 precache_playermodel(s);
1002 s = autocvar_sv_defaultplayermodel;
1004 precache_playermodel(s);
1009 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1010 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1013 // gore and miscellaneous sounds
1014 //precache_sound ("misc/h2ohit.wav");
1015 precache_model ("models/hook.md3");
1016 precache_sound ("misc/armorimpact.wav");
1017 precache_sound ("misc/bodyimpact1.wav");
1018 precache_sound ("misc/bodyimpact2.wav");
1019 precache_sound ("misc/gib.wav");
1020 precache_sound ("misc/gib_splat01.wav");
1021 precache_sound ("misc/gib_splat02.wav");
1022 precache_sound ("misc/gib_splat03.wav");
1023 precache_sound ("misc/gib_splat04.wav");
1024 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1025 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1026 precache_sound ("misc/null.wav");
1027 precache_sound ("misc/spawn.wav");
1028 precache_sound ("misc/talk.wav");
1029 precache_sound ("misc/teleport.wav");
1030 precache_sound ("misc/poweroff.wav");
1031 precache_sound ("player/lava.wav");
1032 precache_sound ("player/slime.wav");
1034 precache_model ("models/sprites/0.spr32");
1035 precache_model ("models/sprites/1.spr32");
1036 precache_model ("models/sprites/2.spr32");
1037 precache_model ("models/sprites/3.spr32");
1038 precache_model ("models/sprites/4.spr32");
1039 precache_model ("models/sprites/5.spr32");
1040 precache_model ("models/sprites/6.spr32");
1041 precache_model ("models/sprites/7.spr32");
1042 precache_model ("models/sprites/8.spr32");
1043 precache_model ("models/sprites/9.spr32");
1044 precache_model ("models/sprites/10.spr32");
1046 // common weapon precaches
1047 precache_sound (W_Sound("reload")); // until weapons have individual reload sounds, precache the reload sound here
1048 precache_sound (W_Sound("weapon_switch"));
1049 precache_sound (W_Sound("weaponpickup"));
1050 precache_sound (W_Sound("unavailable"));
1051 precache_sound (W_Sound("dryfire"));
1052 if (g_grappling_hook)
1054 precache_sound (W_Sound("hook_fire")); // hook
1055 precache_sound (W_Sound("hook_impact")); // hook
1058 precache_model("models/elaser.mdl");
1059 precache_model("models/laser.mdl");
1060 precache_model("models/ebomb.mdl");
1063 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1065 if (!self.noise && self.music) // quake 3 uses the music field
1066 self.noise = self.music;
1068 // plays music for the level if there is any
1071 precache_sound (self.noise);
1072 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1076 #include "precache-for-csqc.inc"
1080 void make_safe_for_remove(entity e)
1082 if (e.initialize_entity)
1084 entity ent, prev = world;
1085 for (ent = initialize_entity_first; ent; )
1087 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1089 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1090 // skip it in linked list
1093 prev.initialize_entity_next = ent.initialize_entity_next;
1094 ent = prev.initialize_entity_next;
1098 initialize_entity_first = ent.initialize_entity_next;
1099 ent = initialize_entity_first;
1105 ent = ent.initialize_entity_next;
1111 void objerror(string s)
1113 make_safe_for_remove(self);
1114 builtin_objerror(s);
1117 .float remove_except_protected_forbidden;
1118 void remove_except_protected(entity e)
1120 if(e.remove_except_protected_forbidden)
1121 error("not allowed to remove this at this point");
1125 void remove_unsafely(entity e)
1127 if(e.classname == "spike")
1128 error("Removing spikes is forbidden (crylink bug), please report");
1132 void remove_safely(entity e)
1134 make_safe_for_remove(e);
1138 void InitializeEntity(entity e, void(void) func, float order)
1142 if (!e || e.initialize_entity)
1144 // make a proxy initializer entity
1148 e.classname = "initialize_entity";
1152 e.initialize_entity = func;
1153 e.initialize_entity_order = order;
1155 cur = initialize_entity_first;
1159 if (!cur || cur.initialize_entity_order > order)
1161 // insert between prev and cur
1163 prev.initialize_entity_next = e;
1165 initialize_entity_first = e;
1166 e.initialize_entity_next = cur;
1170 cur = cur.initialize_entity_next;
1173 void InitializeEntitiesRun()
1176 startoflist = initialize_entity_first;
1177 initialize_entity_first = world;
1178 remove = remove_except_protected;
1179 for (self = startoflist; self; self = self.initialize_entity_next)
1181 self.remove_except_protected_forbidden = 1;
1183 for (self = startoflist; self; )
1186 var void(void) func;
1187 e = self.initialize_entity_next;
1188 func = self.initialize_entity;
1189 self.initialize_entity_order = 0;
1190 self.initialize_entity = func_null;
1191 self.initialize_entity_next = world;
1192 self.remove_except_protected_forbidden = 0;
1193 if (self.classname == "initialize_entity")
1197 builtin_remove(self);
1200 //dprint("Delayed initialization: ", self.classname, "\n");
1206 backtrace(strcat("Null function in: ", self.classname, "\n"));
1210 remove = remove_unsafely;
1213 void UncustomizeEntitiesRun()
1217 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1218 self.uncustomizeentityforclient();
1221 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1223 e.customizeentityforclient = customizer;
1224 e.uncustomizeentityforclient = uncustomizer;
1225 e.uncustomizeentityforclient_set = !!uncustomizer;
1228 void Net_LinkEntity(entity e, bool docull, float dt, bool(entity, int) sendfunc)
1232 if (e.classname == "")
1233 e.classname = "net_linked";
1235 if (e.model == "" || self.modelindex == 0)
1239 setmodel(e, "null");
1243 e.SendEntity = sendfunc;
1244 e.SendFlags = 0xFFFFFF;
1247 e.effects |= EF_NODEPTHTEST;
1251 e.nextthink = time + dt;
1252 e.think = SUB_Remove;
1257 .float(entity) isEliminated;
1258 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1262 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1263 WriteByte(MSG_ENTITY, sendflags);
1267 for(i = 1; i <= maxclients; i += 8)
1269 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1271 if(eliminatedPlayers.isEliminated(e))
1274 WriteByte(MSG_ENTITY, f);
1281 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1283 if(eliminatedPlayers)
1285 backtrace("Can't spawn eliminatedPlayers again!");
1288 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1289 eliminatedPlayers.isEliminated = isEliminated_func;
1293 void adaptor_think2touch()
1302 void adaptor_think2use()
1314 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1316 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
1317 self.projectiledeathtype |= HITTYPE_SPLASH;
1318 adaptor_think2use();
1321 // deferred dropping
1322 void DropToFloor_Handler()
1324 builtin_droptofloor();
1325 self.dropped_origin = self.origin;
1330 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1335 float trace_hits_box_a0, trace_hits_box_a1;
1337 float trace_hits_box_1d(float end, float thmi, float thma)
1341 // just check if x is in range
1349 // do the trace with respect to x
1350 // 0 -> end has to stay in thmi -> thma
1351 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1352 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1353 if (trace_hits_box_a0 > trace_hits_box_a1)
1359 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1364 // now it is a trace from 0 to end
1366 trace_hits_box_a0 = 0;
1367 trace_hits_box_a1 = 1;
1369 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1371 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1373 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1379 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1381 return trace_hits_box(start, end, thmi - ma, thma - mi);
1384 float SUB_NoImpactCheck()
1386 // zero hitcontents = this is not the real impact, but either the
1387 // mirror-impact of something hitting the projectile instead of the
1388 // projectile hitting the something, or a touchareagrid one. Neither of
1389 // these stop the projectile from moving, so...
1390 if(trace_dphitcontents == 0)
1392 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1393 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));
1396 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1398 if (other == world && self.size != '0 0 0')
1401 tic = self.velocity * sys_frametime;
1402 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1403 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1404 if (trace_fraction >= 1)
1406 LOG_TRACE("Odd... did not hit...?\n");
1408 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1410 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1418 #define SUB_OwnerCheck() (other && (other == self.owner))
1420 void W_Crylink_Dequeue(entity e);
1421 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1423 if(SUB_OwnerCheck())
1425 if(SUB_NoImpactCheck())
1427 if(self.classname == "nade")
1428 return false; // no checks here
1429 else if(self.classname == "grapplinghook")
1430 RemoveGrapplingHook(self.realowner);
1431 else if(self.classname == "spike")
1433 W_Crylink_Dequeue(self);
1440 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1441 UpdateCSQCProjectile(self);
1446 void URI_Get_Callback(float id, float status, string data)
1448 if(url_URI_Get_Callback(id, status, data))
1452 else if (id == URI_GET_DISCARD)
1456 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1459 Curl_URI_Get_Callback(id, status, data);
1461 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1464 OnlineBanList_URI_Get_Callback(id, status, data);
1468 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1472 string uid2name(string myuid) {
1474 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1476 // FIXME remove this later after 0.6 release
1477 // convert old style broken records to correct style
1480 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1483 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1484 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1489 s = "^1Unregistered Player";
1493 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1496 vector start, org, delta, end, enddown, mstart;
1499 m = e.dphitcontentsmask;
1500 e.dphitcontentsmask = goodcontents | badcontents;
1503 delta = world.maxs - world.mins;
1507 for (i = 0; i < attempts; ++i)
1509 start.x = org.x + random() * delta.x;
1510 start.y = org.y + random() * delta.y;
1511 start.z = org.z + random() * delta.z;
1513 // rule 1: start inside world bounds, and outside
1514 // solid, and don't start from somewhere where you can
1515 // fall down to evil
1516 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1517 if (trace_fraction >= 1)
1519 if (trace_startsolid)
1521 if (trace_dphitcontents & badcontents)
1523 if (trace_dphitq3surfaceflags & badsurfaceflags)
1526 // rule 2: if we are too high, lower the point
1527 if (trace_fraction * delta.z > maxaboveground)
1528 start = trace_endpos + '0 0 1' * maxaboveground;
1529 enddown = trace_endpos;
1531 // rule 3: make sure we aren't outside the map. This only works
1532 // for somewhat well formed maps. A good rule of thumb is that
1533 // the map should have a convex outside hull.
1534 // these can be traceLINES as we already verified the starting box
1535 mstart = start + 0.5 * (e.mins + e.maxs);
1536 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1537 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1539 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1540 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1542 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1543 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1545 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1546 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1548 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1549 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1552 // rule 4: we must "see" some spawnpoint or item
1553 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1554 if(checkpvs(mstart, sp))
1555 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1559 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1560 if(checkpvs(mstart, sp))
1561 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1567 // find a random vector to "look at"
1568 end.x = org.x + random() * delta.x;
1569 end.y = org.y + random() * delta.y;
1570 end.z = org.z + random() * delta.z;
1571 end = start + normalize(end - start) * vlen(delta);
1573 // rule 4: start TO end must not be too short
1574 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1575 if (trace_startsolid)
1577 if (trace_fraction < minviewdistance / vlen(delta))
1580 // rule 5: don't want to look at sky
1581 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1584 // rule 6: we must not end up in trigger_hurt
1585 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1591 e.dphitcontentsmask = m;
1595 setorigin(e, start);
1596 e.angles = vectoangles(end - start);
1597 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1604 void write_recordmarker(entity pl, float tstart, float dt)
1606 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1608 // also write a marker into demo files for demotc-race-record-extractor to find
1611 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1612 " ", ftos(tstart), " ", ftos(dt), "\n"));
1615 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1628 if(allowcenter) // 2: allow center handedness
1641 if(allowcenter) // 2: allow center handedness
1657 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1662 if (autocvar_g_shootfromeye)
1666 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1667 else { vecs.y = 0; vecs.z -= 2; }
1675 else if (autocvar_g_shootfromcenter)
1680 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1690 else if (autocvar_g_shootfromclient)
1692 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1697 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1699 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1703 void attach_sameorigin(entity e, entity to, string tag)
1705 vector org, t_forward, t_left, t_up, e_forward, e_up;
1708 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1709 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1710 t_forward = v_forward * tagscale;
1711 t_left = v_right * -tagscale;
1712 t_up = v_up * tagscale;
1714 e.origin_x = org * t_forward;
1715 e.origin_y = org * t_left;
1716 e.origin_z = org * t_up;
1718 // current forward and up directions
1719 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1720 e.angles = AnglesTransform_FromVAngles(e.angles);
1722 e.angles = AnglesTransform_FromAngles(e.angles);
1723 fixedmakevectors(e.angles);
1725 // untransform forward, up!
1726 e_forward.x = v_forward * t_forward;
1727 e_forward.y = v_forward * t_left;
1728 e_forward.z = v_forward * t_up;
1729 e_up.x = v_up * t_forward;
1730 e_up.y = v_up * t_left;
1731 e_up.z = v_up * t_up;
1733 e.angles = fixedvectoangles2(e_forward, e_up);
1734 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1735 e.angles = AnglesTransform_ToVAngles(e.angles);
1737 e.angles = AnglesTransform_ToAngles(e.angles);
1739 setattachment(e, to, tag);
1740 setorigin(e, e.origin);
1743 void detach_sameorigin(entity e)
1746 org = gettaginfo(e, 0);
1747 e.angles = fixedvectoangles2(v_forward, v_up);
1748 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1749 e.angles = AnglesTransform_ToVAngles(e.angles);
1751 e.angles = AnglesTransform_ToAngles(e.angles);
1753 setattachment(e, world, "");
1754 setorigin(e, e.origin);
1757 void follow_sameorigin(entity e, entity to)
1759 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1760 e.aiment = to; // make the hole follow bmodel
1761 e.punchangle = to.angles; // the original angles of bmodel
1762 e.view_ofs = e.origin - to.origin; // relative origin
1763 e.v_angle = e.angles - to.angles; // relative angles
1766 void unfollow_sameorigin(entity e)
1768 e.movetype = MOVETYPE_NONE;
1771 entity gettaginfo_relative_ent;
1772 vector gettaginfo_relative(entity e, float tag)
1774 if (!gettaginfo_relative_ent)
1776 gettaginfo_relative_ent = spawn();
1777 gettaginfo_relative_ent.effects = EF_NODRAW;
1779 gettaginfo_relative_ent.model = e.model;
1780 gettaginfo_relative_ent.modelindex = e.modelindex;
1781 gettaginfo_relative_ent.frame = e.frame;
1782 return gettaginfo(gettaginfo_relative_ent, tag);
1787 float modeleffect_SendEntity(entity to, int sf)
1790 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1793 if(self.velocity != '0 0 0')
1795 if(self.angles != '0 0 0')
1797 if(self.avelocity != '0 0 0')
1800 WriteByte(MSG_ENTITY, f);
1801 WriteShort(MSG_ENTITY, self.modelindex);
1802 WriteByte(MSG_ENTITY, self.skin);
1803 WriteByte(MSG_ENTITY, self.frame);
1804 WriteCoord(MSG_ENTITY, self.origin.x);
1805 WriteCoord(MSG_ENTITY, self.origin.y);
1806 WriteCoord(MSG_ENTITY, self.origin.z);
1809 WriteCoord(MSG_ENTITY, self.velocity.x);
1810 WriteCoord(MSG_ENTITY, self.velocity.y);
1811 WriteCoord(MSG_ENTITY, self.velocity.z);
1815 WriteCoord(MSG_ENTITY, self.angles.x);
1816 WriteCoord(MSG_ENTITY, self.angles.y);
1817 WriteCoord(MSG_ENTITY, self.angles.z);
1821 WriteCoord(MSG_ENTITY, self.avelocity.x);
1822 WriteCoord(MSG_ENTITY, self.avelocity.y);
1823 WriteCoord(MSG_ENTITY, self.avelocity.z);
1825 WriteShort(MSG_ENTITY, self.scale * 256.0);
1826 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1827 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1828 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1829 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1834 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)
1839 e.classname = "modeleffect";
1847 e.teleport_time = t1;
1851 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1855 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1858 sz = max(e.scale, e.scale2);
1859 setsize(e, e.mins * sz, e.maxs * sz);
1860 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1863 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1865 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1868 float randombit(float bits)
1870 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1879 for(f = 1; f <= bits; f *= 2)
1888 r = (r - 1) / (n - 1);
1895 float randombits(float bits, float k, float error_return)
1899 while(k > 0 && bits != r)
1901 r += randombit(bits - r);
1910 void randombit_test(float bits, float iter)
1914 LOG_INFO(ftos(randombit(bits)), "\n");
1919 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1921 if(halflifedist > 0)
1922 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1923 else if(halflifedist < 0)
1924 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1930 .string aiment_classname;
1931 .float aiment_deadflag;
1932 void SetMovetypeFollow(entity ent, entity e)
1934 // FIXME this may not be warpzone aware
1935 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1936 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.
1937 ent.aiment = e; // make the hole follow bmodel
1938 ent.punchangle = e.angles; // the original angles of bmodel
1939 ent.view_ofs = ent.origin - e.origin; // relative origin
1940 ent.v_angle = ent.angles - e.angles; // relative angles
1941 ent.aiment_classname = strzone(e.classname);
1942 ent.aiment_deadflag = e.deadflag;
1944 void UnsetMovetypeFollow(entity ent)
1946 ent.movetype = MOVETYPE_FLY;
1947 PROJECTILE_MAKETRIGGER(ent);
1950 float LostMovetypeFollow(entity ent)
1953 if(ent.movetype != MOVETYPE_FOLLOW)
1959 if(ent.aiment.classname != ent.aiment_classname)
1961 if(ent.aiment.deadflag != ent.aiment_deadflag)
1967 float isPushable(entity e)
1976 case "droppedweapon":
1977 case "keepawayball":
1978 case "nexball_basketball":
1979 case "nexball_football":
1981 case "bullet": // antilagged bullets can't hit this either
1984 if (e.projectiledeathtype)