1 #include "miscfunctions.qh"
3 #include "command/common.qh"
4 #include "constants.qh"
7 #include "mutators/all.qh"
9 #include "weapons/accuracy.qh"
10 #include "weapons/csqcprojectile.qh"
11 #include "weapons/selection.qh"
12 #include "../common/command/generic.qh"
13 #include "../common/constants.qh"
14 #include "../common/deathtypes/all.qh"
15 #include "../common/mapinfo.qh"
16 #include "../common/notifications.qh"
17 #include "../common/playerstats.qh"
18 #include "../common/teams.qh"
19 #include "../common/triggers/subs.qh"
20 #include "../common/util.qh"
21 #include "../common/turrets/sv_turrets.qh"
22 #include "../common/weapons/all.qh"
23 #include "../common/vehicles/sv_vehicles.qh"
24 #include "../common/vehicles/vehicle.qh"
25 #include "../common/items/all.qc"
26 #include "../lib/csqcmodel/sv_model.qh"
27 #include "../lib/warpzone/anglestransform.qh"
28 #include "../lib/warpzone/server.qh"
30 void crosshair_trace(entity pl)
32 traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
34 void crosshair_trace_plusvisibletriggers(entity pl)
38 first = findchainfloat(solid, SOLID_TRIGGER);
40 for (e = first; e; e = e.chain)
46 for (e = first; e; e = e.chain)
47 e.solid = SOLID_TRIGGER;
49 void WarpZone_crosshair_trace(entity pl)
51 WarpZone_traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
57 if(autocvar_sv_adminnick != "")
58 return autocvar_sv_adminnick;
60 return "SERVER ADMIN";
64 void GameLogEcho(string s)
69 if (autocvar_sv_eventlog_files)
74 matches = autocvar_sv_eventlog_files_counter + 1;
75 cvar_set("sv_eventlog_files_counter", itos(matches));
78 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
79 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
80 logfile = fopen(fn, FILE_APPEND);
81 fputs(logfile, ":logversion:3\n");
85 if (autocvar_sv_eventlog_files_timestamps)
86 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
88 fputs(logfile, strcat(s, "\n"));
91 if (autocvar_sv_eventlog_console)
100 // will be opened later
105 if (logfile_open && logfile >= 0)
112 entity findnearest(vector point, .string field, string value, vector axismod)
123 localhead = find(world, field, value);
126 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
127 dist = localhead.oldorigin;
129 dist = localhead.origin;
131 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
134 for (i = 0; i < num_nearest; ++i)
136 if (len < nearest_length[i])
140 // now i tells us where to insert at
141 // INSERTION SORT! YOU'VE SEEN IT! RUN!
142 if (i < NUM_NEAREST_ENTITIES)
144 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
146 nearest_length[j + 1] = nearest_length[j];
147 nearest_entity[j + 1] = nearest_entity[j];
149 nearest_length[i] = len;
150 nearest_entity[i] = localhead;
151 if (num_nearest < NUM_NEAREST_ENTITIES)
152 num_nearest = num_nearest + 1;
155 localhead = find(localhead, field, value);
158 // now use the first one from our list that we can see
159 for (i = 0; i < num_nearest; ++i)
161 traceline(point, nearest_entity[i].origin, true, world);
162 if (trace_fraction == 1)
166 LOG_TRACE("Nearest point (");
167 LOG_TRACE(nearest_entity[0].netname);
168 LOG_TRACE(") is not visible, using a visible one.\n");
170 return nearest_entity[i];
174 if (num_nearest == 0)
177 LOG_TRACE("Not seeing any location point, using nearest as fallback.\n");
179 dprint("Candidates were: ");
180 for(j = 0; j < num_nearest; ++j)
184 dprint(nearest_entity[j].netname);
189 return nearest_entity[0];
192 string NearestLocation(vector p)
197 loc = findnearest(p, classname, "target_location", '1 1 1');
204 loc = findnearest(p, target, "###item###", '1 1 4');
211 string formatmessage(string msg)
223 ammoitems = "batteries";
224 if(self.items & ITEM_Plasma.m_itemid) ammoitems = ITEM_Plasma.m_name;
225 if(self.items & ITEM_Cells.m_itemid) ammoitems = ITEM_Cells.m_name;
226 if(self.items & ITEM_Rockets.m_itemid) ammoitems = ITEM_Rockets.m_name;
227 if(self.items & ITEM_Shells.m_itemid) ammoitems = ITEM_Shells.m_name;
229 WarpZone_crosshair_trace(self);
230 cursor = trace_endpos;
231 cursor_ent = trace_ent;
235 break; // too many replacements
238 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
239 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
252 replacement = substring(msg, p, 2);
253 escape = substring(msg, p + 1, 1);
257 case "%": replacement = "%"; break;
258 case "\\":replacement = "\\"; break;
259 case "n": replacement = "\n"; break;
260 case "a": replacement = ftos(floor(self.armorvalue)); break;
261 case "h": replacement = ftos(floor(self.health)); break;
262 case "l": replacement = NearestLocation(self.origin); break;
263 case "y": replacement = NearestLocation(cursor); break;
264 case "d": replacement = NearestLocation(self.death_origin); break;
265 case "w": replacement = WEP_NAME(((!self.weapon) ? (!self.switchweapon ? self.cnt : self.switchweapon) : self.weapon)); break;
266 case "W": replacement = ammoitems; break;
267 case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
268 case "s": replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); break;
269 case "S": replacement = ftos(vlen(self.velocity)); break;
270 case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break;
271 case "T": replacement = seconds_tostring(floor(time - game_starttime)); break;
274 MUTATOR_CALLHOOK(FormatMessage, escape, replacement, msg);
275 escape = format_escape;
276 replacement = format_replacement;
281 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
282 p = p + strlen(replacement);
293 >0: receives a cvar from name=argv(f) value=argv(f+1)
295 void GetCvars_handleString(string thisname, float f, .string field, string name)
300 strunzone(self.(field));
301 self.(field) = string_null;
305 if (thisname == name)
308 strunzone(self.(field));
309 self.(field) = strzone(argv(f + 1));
313 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
315 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
317 GetCvars_handleString(thisname, f, field, name);
318 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
319 if (thisname == name)
321 string s = func(strcat1(self.(field)));
322 if (s != self.(field))
324 strunzone(self.(field));
325 self.(field) = strzone(s);
329 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
336 if (thisname == name)
337 self.(field) = stof(argv(f + 1));
340 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
342 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
349 if (thisname == name)
353 self.(field) = stof(argv(f + 1));
362 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
365 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
368 o = W_FixWeaponOrder_ForceComplete(wo);
369 if(self.weaponorder_byimpulse)
371 strunzone(self.weaponorder_byimpulse);
372 self.weaponorder_byimpulse = string_null;
374 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
377 void GetCvars(float f)
379 string s = string_null;
382 s = strcat1(argv(f));
386 MUTATOR_CALLHOOK(GetCvars);
388 Notification_GetCvars();
390 ReplicateVars(this, s, f);
392 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
393 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
394 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
395 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
396 GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
397 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
398 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
399 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
400 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
401 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
402 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
403 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
404 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
405 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
406 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
407 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
408 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
409 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
410 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
411 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
412 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
413 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
414 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
416 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
417 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
418 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
419 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
420 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
422 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
425 if (s == "cl_weaponpriority")
426 self.switchweapon = w_getbestweapon(self);
427 if (s == "cl_allow_uidtracking")
428 PlayerStats_GameReport_AddPlayer(self);
432 // decolorizes and team colors the player name when needed
433 string playername(entity p)
436 if (teamplay && !intermission_running && IS_PLAYER(p))
438 t = Team_ColorCode(p.team);
439 return strcat(t, strdecolorize(p.netname));
445 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
447 int i = weaponinfo.weapon;
449 bool allow_mutatorblocked = false;
454 bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked);
456 allguns = want_allguns;
457 allow_mutatorblocked = false;
461 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
466 else if(!mutator_returnvalue)
467 d = !(!weaponinfo.weaponstart);
469 if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
472 float t = weaponinfo.weaponstartoverride;
474 //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
479 // 4: is set by default?
488 void readplayerstartcvars()
494 // initialize starting values for players
495 start_weapons = '0 0 0';
496 start_weapons_default = '0 0 0';
497 start_weapons_defaultmask = '0 0 0';
499 start_ammo_shells = 0;
500 start_ammo_nails = 0;
501 start_ammo_rockets = 0;
502 start_ammo_cells = 0;
503 start_ammo_plasma = 0;
504 start_health = cvar("g_balance_health_start");
505 start_armorvalue = cvar("g_balance_armor_start");
508 g_weaponarena_weapons = '0 0 0';
510 s = cvar_string("g_weaponarena");
512 MUTATOR_CALLHOOK(SetWeaponArena, s);
515 if (s == "0" || s == "")
521 // forcibly turn off weaponarena
523 else if (s == "all" || s == "1")
526 g_weaponarena_list = "All Weapons";
527 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
529 e = get_weaponinfo(j);
530 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
531 g_weaponarena_weapons |= WepSet_FromWeapon(j);
534 else if (s == "most")
537 g_weaponarena_list = "Most Weapons";
538 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
540 e = get_weaponinfo(j);
541 if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
542 if (e.spawnflags & WEP_FLAG_NORMAL)
543 g_weaponarena_weapons |= WepSet_FromWeapon(j);
546 else if (s == "none")
549 g_weaponarena_list = "No Weapons";
554 t = tokenize_console(s);
555 g_weaponarena_list = "";
556 for (i = 0; i < t; ++i)
559 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
561 e = get_weaponinfo(j);
564 g_weaponarena_weapons |= WepSet_FromWeapon(j);
565 g_weaponarena_list = strcat(g_weaponarena_list, e.m_name, " & ");
571 LOG_INFO("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
574 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
578 g_weaponarena_random = cvar("g_weaponarena_random");
580 g_weaponarena_random = 0;
581 g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
585 g_weapon_stay = 0; // incompatible
586 start_weapons = g_weaponarena_weapons;
587 start_items |= IT_UNLIMITED_AMMO;
591 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
593 e = get_weaponinfo(i);
594 int w = want_weapon(e, false);
596 start_weapons |= WepSet_FromWeapon(i);
598 start_weapons_default |= WepSet_FromWeapon(i);
600 start_weapons_defaultmask |= WepSet_FromWeapon(i);
604 if(!cvar("g_use_ammunition"))
605 start_items |= IT_UNLIMITED_AMMO;
607 if(start_items & IT_UNLIMITED_WEAPON_AMMO)
609 start_ammo_shells = 999;
610 start_ammo_nails = 999;
611 start_ammo_rockets = 999;
612 start_ammo_cells = 999;
613 start_ammo_plasma = 999;
614 start_ammo_fuel = 999;
618 start_ammo_shells = cvar("g_start_ammo_shells");
619 start_ammo_nails = cvar("g_start_ammo_nails");
620 start_ammo_rockets = cvar("g_start_ammo_rockets");
621 start_ammo_cells = cvar("g_start_ammo_cells");
622 start_ammo_plasma = cvar("g_start_ammo_plasma");
623 start_ammo_fuel = cvar("g_start_ammo_fuel");
628 warmup_start_ammo_shells = start_ammo_shells;
629 warmup_start_ammo_nails = start_ammo_nails;
630 warmup_start_ammo_rockets = start_ammo_rockets;
631 warmup_start_ammo_cells = start_ammo_cells;
632 warmup_start_ammo_plasma = start_ammo_plasma;
633 warmup_start_ammo_fuel = start_ammo_fuel;
634 warmup_start_health = start_health;
635 warmup_start_armorvalue = start_armorvalue;
636 warmup_start_weapons = start_weapons;
637 warmup_start_weapons_default = start_weapons_default;
638 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
640 if (!g_weaponarena && !g_ca && !g_freezetag)
642 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
643 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
644 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
645 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
646 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
647 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
648 warmup_start_health = cvar("g_warmup_start_health");
649 warmup_start_armorvalue = cvar("g_warmup_start_armor");
650 warmup_start_weapons = '0 0 0';
651 warmup_start_weapons_default = '0 0 0';
652 warmup_start_weapons_defaultmask = '0 0 0';
653 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
655 e = get_weaponinfo(i);
656 int w = want_weapon(e, g_warmup_allguns);
658 warmup_start_weapons |= WepSet_FromWeapon(i);
660 warmup_start_weapons_default |= WepSet_FromWeapon(i);
662 warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
668 start_items |= ITEM_Jetpack.m_itemid;
670 MUTATOR_CALLHOOK(SetStartItems);
672 if (start_items & ITEM_Jetpack.m_itemid)
674 start_items |= ITEM_JetpackRegen.m_itemid;
675 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
676 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
679 WepSet precache_weapons = start_weapons;
680 if (g_warmup_allguns != 1)
681 precache_weapons |= warmup_start_weapons;
682 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
684 e = get_weaponinfo(i);
685 if(precache_weapons & WepSet_FromWeapon(i)) {
686 Weapon w = get_weaponinfo(i);
691 start_ammo_shells = max(0, start_ammo_shells);
692 start_ammo_nails = max(0, start_ammo_nails);
693 start_ammo_rockets = max(0, start_ammo_rockets);
694 start_ammo_cells = max(0, start_ammo_cells);
695 start_ammo_plasma = max(0, start_ammo_plasma);
696 start_ammo_fuel = max(0, start_ammo_fuel);
698 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
699 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
700 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
701 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
702 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
703 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
706 float sound_allowed(float destin, entity e)
708 // sounds from world may always pass
711 if (e.classname == "body")
713 else if (e.realowner && e.realowner != e)
715 else if (e.owner && e.owner != e)
720 // sounds to self may always pass
721 if (destin == MSG_ONE)
724 // sounds by players can be removed
725 if (autocvar_bot_sound_monopoly)
726 if (IS_REAL_CLIENT(e))
728 // anything else may pass
732 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
736 if (!sound_allowed(_dest, e))
739 entno = num_for_edict(e);
740 idx = precache_sound_index(samp);
745 attenu = floor(attenu * 64);
746 vol = floor(vol * 255);
749 sflags |= SND_VOLUME;
751 sflags |= SND_ATTENUATION;
752 if (entno >= 8192 || chan < 0 || chan > 7)
753 sflags |= SND_LARGEENTITY;
755 sflags |= SND_LARGESOUND;
757 WriteByte(_dest, SVC_SOUND);
758 WriteByte(_dest, sflags);
759 if (sflags & SND_VOLUME)
760 WriteByte(_dest, vol);
761 if (sflags & SND_ATTENUATION)
762 WriteByte(_dest, attenu);
763 if (sflags & SND_LARGEENTITY)
765 WriteShort(_dest, entno);
766 WriteByte(_dest, chan);
770 WriteShort(_dest, entno * 8 + chan);
772 if (sflags & SND_LARGESOUND)
773 WriteShort(_dest, idx);
775 WriteByte(_dest, idx);
777 WriteCoord(_dest, o.x);
778 WriteCoord(_dest, o.y);
779 WriteCoord(_dest, o.z);
781 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
785 if (!sound_allowed(_dest, e))
788 o = e.origin + 0.5 * (e.mins + e.maxs);
789 soundtoat(_dest, e, o, chan, samp, vol, _atten);
791 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
793 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
795 void stopsoundto(float _dest, entity e, float chan)
799 if (!sound_allowed(_dest, e))
802 entno = num_for_edict(e);
804 if (entno >= 8192 || chan < 0 || chan > 7)
807 idx = precache_sound_index(SND(Null));
808 sflags = SND_LARGEENTITY;
810 sflags |= SND_LARGESOUND;
811 WriteByte(_dest, SVC_SOUND);
812 WriteByte(_dest, sflags);
813 WriteShort(_dest, entno);
814 WriteByte(_dest, chan);
815 if (sflags & SND_LARGESOUND)
816 WriteShort(_dest, idx);
818 WriteByte(_dest, idx);
819 WriteCoord(_dest, e.origin.x);
820 WriteCoord(_dest, e.origin.y);
821 WriteCoord(_dest, e.origin.z);
825 WriteByte(_dest, SVC_STOPSOUND);
826 WriteShort(_dest, entno * 8 + chan);
829 void stopsound(entity e, float chan)
831 if (!sound_allowed(MSG_BROADCAST, e))
834 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
835 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
838 void play2(entity e, string filename)
840 //stuffcmd(e, strcat("play2 ", filename, "\n"));
842 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
845 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
847 float spamsound(entity e, float chan, string samp, float vol, float _atten)
849 if (!sound_allowed(MSG_BROADCAST, e))
852 if (time > e.spamtime)
855 _sound(e, chan, samp, vol, _atten);
861 void play2team(float t, string filename)
865 if (autocvar_bot_sound_monopoly)
868 FOR_EACH_REALPLAYER(head)
871 play2(head, filename);
875 void play2all(string samp)
877 if (autocvar_bot_sound_monopoly)
880 _sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
883 void PrecachePlayerSounds(string f);
884 void precache_playermodel(string m)
886 float globhandle, i, n;
889 if(substring(m, -9,5) == "_lod1")
891 if(substring(m, -9,5) == "_lod2")
894 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
897 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
901 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
904 n = search_getsize(globhandle);
905 for (i = 0; i < n; ++i)
907 //print(search_getfilename(globhandle, i), "\n");
908 f = search_getfilename(globhandle, i);
909 PrecachePlayerSounds(f);
911 search_end(globhandle);
913 void precache_all_playermodels(string pattern)
915 float globhandle, i, n;
918 globhandle = search_begin(pattern, true, false);
921 n = search_getsize(globhandle);
922 for (i = 0; i < n; ++i)
924 //print(search_getfilename(globhandle, i), "\n");
925 f = search_getfilename(globhandle, i);
926 precache_playermodel(f);
928 search_end(globhandle);
931 void precache_playermodels(string s)
935 int n = tokenize_console(s);
936 precache_playermodel(argv(0));
938 for (int i = 1; i < n; ++i)
939 precache_model(argv(i));
945 // gamemode related things
947 // Precache all player models if desired
948 if (autocvar_sv_precacheplayermodels)
950 PrecachePlayerSounds("sound/player/default.sounds");
951 precache_all_playermodels("models/player/*.zym");
952 precache_all_playermodels("models/player/*.dpm");
953 precache_all_playermodels("models/player/*.md3");
954 precache_all_playermodels("models/player/*.psk");
955 precache_all_playermodels("models/player/*.iqm");
958 if (autocvar_sv_defaultcharacter)
960 precache_playermodels(autocvar_sv_defaultplayermodel_red);
961 precache_playermodels(autocvar_sv_defaultplayermodel_blue);
962 precache_playermodels(autocvar_sv_defaultplayermodel_yellow);
963 precache_playermodels(autocvar_sv_defaultplayermodel_pink);
964 precache_playermodels(autocvar_sv_defaultplayermodel);
969 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
970 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
973 // gore and miscellaneous sounds
974 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
975 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
978 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
980 if (!self.noise && self.music) // quake 3 uses the music field
981 self.noise = self.music;
983 // plays music for the level if there is any
986 precache_sound (self.noise);
987 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
993 void make_safe_for_remove(entity e)
995 if (e.initialize_entity)
997 entity ent, prev = world;
998 for (ent = initialize_entity_first; ent; )
1000 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1002 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1003 // skip it in linked list
1006 prev.initialize_entity_next = ent.initialize_entity_next;
1007 ent = prev.initialize_entity_next;
1011 initialize_entity_first = ent.initialize_entity_next;
1012 ent = initialize_entity_first;
1018 ent = ent.initialize_entity_next;
1024 void objerror(string s)
1026 make_safe_for_remove(self);
1027 builtin_objerror(s);
1030 .float remove_except_protected_forbidden;
1031 void remove_except_protected(entity e)
1033 if(e.remove_except_protected_forbidden)
1034 error("not allowed to remove this at this point");
1038 void remove_unsafely(entity e)
1040 if(e.classname == "spike")
1041 error("Removing spikes is forbidden (crylink bug), please report");
1045 void remove_safely(entity e)
1047 make_safe_for_remove(e);
1051 void InitializeEntity(entity e, void() func, float order)
1055 if (!e || e.initialize_entity)
1057 // make a proxy initializer entity
1059 e = new(initialize_entity);
1063 e.initialize_entity = func;
1064 e.initialize_entity_order = order;
1066 cur = initialize_entity_first;
1070 if (!cur || cur.initialize_entity_order > order)
1072 // insert between prev and cur
1074 prev.initialize_entity_next = e;
1076 initialize_entity_first = e;
1077 e.initialize_entity_next = cur;
1081 cur = cur.initialize_entity_next;
1084 void InitializeEntitiesRun()
1086 entity startoflist = initialize_entity_first;
1087 initialize_entity_first = NULL;
1088 remove = remove_except_protected;
1089 for (entity e = startoflist; e; e = e.initialize_entity_next)
1091 e.remove_except_protected_forbidden = 1;
1093 for (entity e = startoflist; e; )
1095 e.remove_except_protected_forbidden = 0;
1096 e.initialize_entity_order = 0;
1097 entity next = e.initialize_entity_next;
1098 e.initialize_entity_next = NULL;
1099 var void() func = e.initialize_entity;
1100 e.initialize_entity = func_null;
1101 if (e.classname == "initialize_entity")
1103 entity wrappee = e.enemy;
1107 //dprint("Delayed initialization: ", e.classname, "\n");
1110 WITH(entity, self, e, func());
1115 backtrace(strcat("Null function in: ", e.classname, "\n"));
1119 remove = remove_unsafely;
1122 .float(entity) isEliminated;
1123 bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags)
1127 WriteHeader(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1128 WriteByte(MSG_ENTITY, sendflags);
1132 for(i = 1; i <= maxclients; i += 8)
1134 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1136 if(eliminatedPlayers.isEliminated(e))
1139 WriteByte(MSG_ENTITY, f);
1146 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1148 if(eliminatedPlayers)
1150 backtrace("Can't spawn eliminatedPlayers again!");
1153 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1154 eliminatedPlayers.isEliminated = isEliminated_func;
1158 void adaptor_think2touch()
1167 void adaptor_think2use()
1179 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1181 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
1182 self.projectiledeathtype |= HITTYPE_SPLASH;
1183 adaptor_think2use();
1186 // deferred dropping
1187 void DropToFloor_Handler()
1189 builtin_droptofloor();
1190 self.dropped_origin = self.origin;
1195 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1200 float trace_hits_box_a0, trace_hits_box_a1;
1202 float trace_hits_box_1d(float end, float thmi, float thma)
1206 // just check if x is in range
1214 // do the trace with respect to x
1215 // 0 -> end has to stay in thmi -> thma
1216 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1217 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1218 if (trace_hits_box_a0 > trace_hits_box_a1)
1224 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1229 // now it is a trace from 0 to end
1231 trace_hits_box_a0 = 0;
1232 trace_hits_box_a1 = 1;
1234 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1236 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1238 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1244 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1246 return trace_hits_box(start, end, thmi - ma, thma - mi);
1249 float SUB_NoImpactCheck()
1251 // zero hitcontents = this is not the real impact, but either the
1252 // mirror-impact of something hitting the projectile instead of the
1253 // projectile hitting the something, or a touchareagrid one. Neither of
1254 // these stop the projectile from moving, so...
1255 if(trace_dphitcontents == 0)
1257 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1258 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));
1261 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1263 if (other == world && self.size != '0 0 0')
1266 tic = self.velocity * sys_frametime;
1267 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1268 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1269 if (trace_fraction >= 1)
1271 LOG_TRACE("Odd... did not hit...?\n");
1273 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1275 LOG_TRACE("Detected and prevented the sky-grapple bug.\n");
1283 #define SUB_OwnerCheck() (other && (other == self.owner))
1285 void W_Crylink_Dequeue(entity e);
1286 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1288 if(SUB_OwnerCheck())
1290 if(SUB_NoImpactCheck())
1292 if(self.classname == "nade")
1293 return false; // no checks here
1294 else if(self.classname == "grapplinghook")
1295 RemoveGrapplingHook(self.realowner);
1296 else if(self.classname == "spike")
1298 W_Crylink_Dequeue(self);
1305 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1306 UpdateCSQCProjectile(self);
1310 /** engine callback */
1311 void URI_Get_Callback(float id, float status, string data)
1313 if(url_URI_Get_Callback(id, status, data))
1317 else if (id == URI_GET_DISCARD)
1321 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1324 Curl_URI_Get_Callback(id, status, data);
1326 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1329 OnlineBanList_URI_Get_Callback(id, status, data);
1333 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1337 string uid2name(string myuid) {
1339 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1341 // FIXME remove this later after 0.6 release
1342 // convert old style broken records to correct style
1345 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1348 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1349 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1354 s = "^1Unregistered Player";
1358 float MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1361 vector start, org, delta, end, enddown, mstart;
1364 m = e.dphitcontentsmask;
1365 e.dphitcontentsmask = goodcontents | badcontents;
1368 delta = boundmax - boundmin;
1372 for (i = 0; i < attempts; ++i)
1374 start.x = org.x + random() * delta.x;
1375 start.y = org.y + random() * delta.y;
1376 start.z = org.z + random() * delta.z;
1378 // rule 1: start inside world bounds, and outside
1379 // solid, and don't start from somewhere where you can
1380 // fall down to evil
1381 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1382 if (trace_fraction >= 1)
1384 if (trace_startsolid)
1386 if (trace_dphitcontents & badcontents)
1388 if (trace_dphitq3surfaceflags & badsurfaceflags)
1391 // rule 2: if we are too high, lower the point
1392 if (trace_fraction * delta.z > maxaboveground)
1393 start = trace_endpos + '0 0 1' * maxaboveground;
1394 enddown = trace_endpos;
1396 // rule 3: make sure we aren't outside the map. This only works
1397 // for somewhat well formed maps. A good rule of thumb is that
1398 // the map should have a convex outside hull.
1399 // these can be traceLINES as we already verified the starting box
1400 mstart = start + 0.5 * (e.mins + e.maxs);
1401 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1402 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1404 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1405 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1407 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1408 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1410 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1411 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1413 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1414 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1417 // rule 4: we must "see" some spawnpoint or item
1418 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1419 if(checkpvs(mstart, sp))
1420 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1424 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1425 if(checkpvs(mstart, sp))
1426 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1432 // find a random vector to "look at"
1433 end.x = org.x + random() * delta.x;
1434 end.y = org.y + random() * delta.y;
1435 end.z = org.z + random() * delta.z;
1436 end = start + normalize(end - start) * vlen(delta);
1438 // rule 4: start TO end must not be too short
1439 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1440 if (trace_startsolid)
1442 if (trace_fraction < minviewdistance / vlen(delta))
1445 // rule 5: don't want to look at sky
1446 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1449 // rule 6: we must not end up in trigger_hurt
1450 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1456 e.dphitcontentsmask = m;
1460 setorigin(e, start);
1461 e.angles = vectoangles(end - start);
1462 LOG_TRACE("Needed ", ftos(i + 1), " attempts\n");
1469 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1471 return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
1474 void write_recordmarker(entity pl, float tstart, float dt)
1476 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1478 // also write a marker into demo files for demotc-race-record-extractor to find
1481 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1482 " ", ftos(tstart), " ", ftos(dt), "\n"));
1485 void attach_sameorigin(entity e, entity to, string tag)
1487 vector org, t_forward, t_left, t_up, e_forward, e_up;
1490 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1491 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1492 t_forward = v_forward * tagscale;
1493 t_left = v_right * -tagscale;
1494 t_up = v_up * tagscale;
1496 e.origin_x = org * t_forward;
1497 e.origin_y = org * t_left;
1498 e.origin_z = org * t_up;
1500 // current forward and up directions
1501 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1502 e.angles = AnglesTransform_FromVAngles(e.angles);
1504 e.angles = AnglesTransform_FromAngles(e.angles);
1505 fixedmakevectors(e.angles);
1507 // untransform forward, up!
1508 e_forward.x = v_forward * t_forward;
1509 e_forward.y = v_forward * t_left;
1510 e_forward.z = v_forward * t_up;
1511 e_up.x = v_up * t_forward;
1512 e_up.y = v_up * t_left;
1513 e_up.z = v_up * t_up;
1515 e.angles = fixedvectoangles2(e_forward, e_up);
1516 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1517 e.angles = AnglesTransform_ToVAngles(e.angles);
1519 e.angles = AnglesTransform_ToAngles(e.angles);
1521 setattachment(e, to, tag);
1522 setorigin(e, e.origin);
1525 void detach_sameorigin(entity e)
1528 org = gettaginfo(e, 0);
1529 e.angles = fixedvectoangles2(v_forward, v_up);
1530 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1531 e.angles = AnglesTransform_ToVAngles(e.angles);
1533 e.angles = AnglesTransform_ToAngles(e.angles);
1535 setattachment(e, world, "");
1536 setorigin(e, e.origin);
1539 void follow_sameorigin(entity e, entity to)
1541 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1542 e.aiment = to; // make the hole follow bmodel
1543 e.punchangle = to.angles; // the original angles of bmodel
1544 e.view_ofs = e.origin - to.origin; // relative origin
1545 e.v_angle = e.angles - to.angles; // relative angles
1548 void unfollow_sameorigin(entity e)
1550 e.movetype = MOVETYPE_NONE;
1553 entity gettaginfo_relative_ent;
1554 vector gettaginfo_relative(entity e, float tag)
1556 if (!gettaginfo_relative_ent)
1558 gettaginfo_relative_ent = spawn();
1559 gettaginfo_relative_ent.effects = EF_NODRAW;
1561 gettaginfo_relative_ent.model = e.model;
1562 gettaginfo_relative_ent.modelindex = e.modelindex;
1563 gettaginfo_relative_ent.frame = e.frame;
1564 return gettaginfo(gettaginfo_relative_ent, tag);
1567 .string aiment_classname;
1568 .float aiment_deadflag;
1569 void SetMovetypeFollow(entity ent, entity e)
1571 // FIXME this may not be warpzone aware
1572 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1573 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.
1574 ent.aiment = e; // make the hole follow bmodel
1575 ent.punchangle = e.angles; // the original angles of bmodel
1576 ent.view_ofs = ent.origin - e.origin; // relative origin
1577 ent.v_angle = ent.angles - e.angles; // relative angles
1578 ent.aiment_classname = strzone(e.classname);
1579 ent.aiment_deadflag = e.deadflag;
1581 void UnsetMovetypeFollow(entity ent)
1583 ent.movetype = MOVETYPE_FLY;
1584 PROJECTILE_MAKETRIGGER(ent);
1587 float LostMovetypeFollow(entity ent)
1590 if(ent.movetype != MOVETYPE_FOLLOW)
1596 if(ent.aiment.classname != ent.aiment_classname)
1598 if(ent.aiment.deadflag != ent.aiment_deadflag)
1604 float isPushable(entity e)
1615 case "droppedweapon":
1616 case "keepawayball":
1617 case "nexball_basketball":
1618 case "nexball_football":
1620 case "bullet": // antilagged bullets can't hit this either
1623 if (e.projectiledeathtype)