4 #include "miscfunctions.qh"
5 #include "../dpdefs/progsdefs.qh"
6 #include "../dpdefs/dpextensions.qh"
7 #include "../common/playerstats.qh"
8 #include "../warpzonelib/anglestransform.qh"
9 #include "../warpzonelib/server.qh"
10 #include "../common/constants.qh"
11 #include "../common/teams.qh"
12 #include "../common/util.qh"
13 #include "../common/urllib.qh"
14 #include "../common/command/generic.qh"
15 #include "../common/weapons/weapons.qh"
16 #include "weapons/accuracy.qh"
17 #include "weapons/csqcprojectile.qh"
18 #include "weapons/selection.qh"
20 #include "autocvars.qh"
21 #include "constants.qh"
23 #include "../common/notifications.qh"
24 #include "../common/deathtypes.qh"
25 #include "mutators/mutators_include.qh"
26 #include "tturrets/include/turrets_early.qh"
27 #include "../common/mapinfo.qh"
28 #include "command/common.qh"
29 #include "../csqcmodellib/sv_model.qh"
33 void crosshair_trace(entity pl)
35 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));
37 void crosshair_trace_plusvisibletriggers(entity pl)
41 first = findchainfloat(solid, SOLID_TRIGGER);
43 for (e = first; e; e = e.chain)
49 for (e = first; e; e = e.chain)
50 e.solid = SOLID_TRIGGER;
52 void WarpZone_crosshair_trace(entity pl)
54 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));
58 string admin_name(void)
60 if(autocvar_sv_adminnick != "")
61 return autocvar_sv_adminnick;
63 return "SERVER ADMIN";
66 void DistributeEvenly_Init(float amount, float totalweight)
68 if (DistributeEvenly_amount)
70 dprint("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
71 dprint(ftos(DistributeEvenly_totalweight), " left!)\n");
74 DistributeEvenly_amount = 0;
76 DistributeEvenly_amount = amount;
77 DistributeEvenly_totalweight = totalweight;
79 float DistributeEvenly_Get(float weight)
84 f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
85 DistributeEvenly_totalweight -= weight;
86 DistributeEvenly_amount -= f;
89 float DistributeEvenly_GetRandomized(float weight)
94 f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
95 DistributeEvenly_totalweight -= weight;
96 DistributeEvenly_amount -= f;
101 void GameLogEcho(string s)
106 if (autocvar_sv_eventlog_files)
111 matches = autocvar_sv_eventlog_files_counter + 1;
112 cvar_set("sv_eventlog_files_counter", ftos(matches));
115 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
116 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
117 logfile = fopen(fn, FILE_APPEND);
118 fputs(logfile, ":logversion:3\n");
122 if (autocvar_sv_eventlog_files_timestamps)
123 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
125 fputs(logfile, strcat(s, "\n"));
128 if (autocvar_sv_eventlog_console)
137 // will be opened later
142 if (logfile_open && logfile >= 0)
149 entity findnearest(vector point, .string field, string value, vector axismod)
160 localhead = find(world, field, value);
163 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
164 dist = localhead.oldorigin;
166 dist = localhead.origin;
168 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
171 for (i = 0; i < num_nearest; ++i)
173 if (len < nearest_length[i])
177 // now i tells us where to insert at
178 // INSERTION SORT! YOU'VE SEEN IT! RUN!
179 if (i < NUM_NEAREST_ENTITIES)
181 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
183 nearest_length[j + 1] = nearest_length[j];
184 nearest_entity[j + 1] = nearest_entity[j];
186 nearest_length[i] = len;
187 nearest_entity[i] = localhead;
188 if (num_nearest < NUM_NEAREST_ENTITIES)
189 num_nearest = num_nearest + 1;
192 localhead = find(localhead, field, value);
195 // now use the first one from our list that we can see
196 for (i = 0; i < num_nearest; ++i)
198 traceline(point, nearest_entity[i].origin, true, world);
199 if (trace_fraction == 1)
203 dprint("Nearest point (");
204 dprint(nearest_entity[0].netname);
205 dprint(") is not visible, using a visible one.\n");
207 return nearest_entity[i];
211 if (num_nearest == 0)
214 dprint("Not seeing any location point, using nearest as fallback.\n");
216 dprint("Candidates were: ");
217 for(j = 0; j < num_nearest; ++j)
221 dprint(nearest_entity[j].netname);
226 return nearest_entity[0];
229 void spawnfunc_target_location()
231 self.classname = "target_location";
232 // location name in netname
233 // eventually support: count, teamgame selectors, line of sight?
236 void spawnfunc_info_location()
238 self.classname = "target_location";
239 self.message = self.netname;
242 string NearestLocation(vector p)
247 loc = findnearest(p, classname, "target_location", '1 1 1');
254 loc = findnearest(p, target, "###item###", '1 1 4');
261 string formatmessage(string msg)
272 WarpZone_crosshair_trace(self);
273 cursor = trace_endpos;
274 cursor_ent = trace_ent;
278 break; // too many replacements
281 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
282 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
295 replacement = substring(msg, p, 2);
296 escape = substring(msg, p + 1, 1);
300 else if (escape == "\\")
302 else if (escape == "n")
304 else if (escape == "a")
305 replacement = ftos(floor(self.armorvalue));
306 else if (escape == "h")
307 replacement = ftos(floor(self.health));
308 else if (escape == "l")
309 replacement = NearestLocation(self.origin);
310 else if (escape == "y")
311 replacement = NearestLocation(cursor);
312 else if (escape == "d")
313 replacement = NearestLocation(self.death_origin);
314 else if (escape == "w") {
318 wep = self.switchweapon;
321 replacement = WEP_NAME(wep);
322 } else if (escape == "W") {
323 if (self.items & IT_SHELLS) replacement = "shells";
324 else if (self.items & IT_NAILS) replacement = "bullets";
325 else if (self.items & IT_ROCKETS) replacement = "rockets";
326 else if (self.items & IT_CELLS) replacement = "cells";
327 else if (self.items & IT_PLASMA) replacement = "plasma";
328 else replacement = "batteries"; // ;)
329 } else if (escape == "x") {
330 replacement = cursor_ent.netname;
331 if (replacement == "" || !cursor_ent)
332 replacement = "nothing";
333 } else if (escape == "s")
334 replacement = ftos(vlen(self.velocity - self.velocity.z * '0 0 1'));
335 else if (escape == "S")
336 replacement = ftos(vlen(self.velocity));
338 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
339 p = p + strlen(replacement);
344 float boolean(float value) { // if value is 0 return false (0), otherwise return true (1)
345 return (value == 0) ? false : true;
354 >0: receives a cvar from name=argv(f) value=argv(f+1)
356 void GetCvars_handleString(string thisname, float f, .string field, string name)
361 strunzone(self.field);
362 self.field = string_null;
366 if (thisname == name)
369 strunzone(self.field);
370 self.field = strzone(argv(f + 1));
374 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
376 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
378 GetCvars_handleString(thisname, f, field, name);
379 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
380 if (thisname == name)
383 s = func(strcat1(self.field));
386 strunzone(self.field);
387 self.field = strzone(s);
391 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
398 if (thisname == name)
399 self.field = stof(argv(f + 1));
402 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
404 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
411 if (thisname == name)
415 self.field = stof(argv(f + 1));
424 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
427 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
430 o = W_FixWeaponOrder_ForceComplete(wo);
431 if(self.weaponorder_byimpulse)
433 strunzone(self.weaponorder_byimpulse);
434 self.weaponorder_byimpulse = string_null;
436 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
439 void GetCvars(float f)
441 string s = string_null;
444 s = strcat1(argv(f));
449 MUTATOR_CALLHOOK(GetCvars);
451 Notification_GetCvars();
453 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
454 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
455 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
456 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
457 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
458 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
459 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
460 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
461 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
462 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
463 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
464 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
465 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
466 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
467 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
468 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
469 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
470 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
471 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
472 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
473 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
474 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
475 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
476 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
478 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
479 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
481 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
482 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
483 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
484 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
485 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
487 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
490 if (s == "cl_weaponpriority")
491 self.switchweapon = w_getbestweapon(self);
492 if (s == "cl_allow_uidtracking")
493 PlayerStats_GameReport_AddPlayer(self);
497 // decolorizes and team colors the player name when needed
498 string playername(entity p)
501 if (teamplay && !intermission_running && IS_PLAYER(p))
503 t = Team_ColorCode(p.team);
504 return strcat(t, strdecolorize(p.netname));
510 vector randompos(vector m1, vector m2)
514 v_x = m2_x * random() + m1_x;
515 v_y = m2_y * random() + m1_y;
516 v_z = m2_z * random() + m1_z;
522 float sound_allowed(float dest, entity e)
524 // sounds from world may always pass
527 if (e.classname == "body")
529 else if (e.realowner && e.realowner != e)
531 else if (e.owner && e.owner != e)
536 // sounds to self may always pass
540 // sounds by players can be removed
541 if (autocvar_bot_sound_monopoly)
542 if (IS_REAL_CLIENT(e))
544 // anything else may pass
549 void sound(entity e, float chan, string samp, float vol, float atten)
551 if (!sound_allowed(MSG_BROADCAST, e))
553 sound7(e, chan, samp, vol, atten, 0, 0);
556 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
560 if (!sound_allowed(dest, e))
563 entno = num_for_edict(e);
564 idx = precache_sound_index(samp);
569 atten = floor(atten * 64);
570 vol = floor(vol * 255);
573 sflags |= SND_VOLUME;
575 sflags |= SND_ATTENUATION;
576 if (entno >= 8192 || chan < 0 || chan > 7)
577 sflags |= SND_LARGEENTITY;
579 sflags |= SND_LARGESOUND;
581 WriteByte(dest, SVC_SOUND);
582 WriteByte(dest, sflags);
583 if (sflags & SND_VOLUME)
584 WriteByte(dest, vol);
585 if (sflags & SND_ATTENUATION)
586 WriteByte(dest, atten);
587 if (sflags & SND_LARGEENTITY)
589 WriteShort(dest, entno);
590 WriteByte(dest, chan);
594 WriteShort(dest, entno * 8 + chan);
596 if (sflags & SND_LARGESOUND)
597 WriteShort(dest, idx);
599 WriteByte(dest, idx);
601 WriteCoord(dest, o.x);
602 WriteCoord(dest, o.y);
603 WriteCoord(dest, o.z);
605 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
609 if (!sound_allowed(dest, e))
612 o = e.origin + 0.5 * (e.mins + e.maxs);
613 soundtoat(dest, e, o, chan, samp, vol, atten);
615 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
617 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, atten);
619 void stopsoundto(float dest, entity e, float chan)
623 if (!sound_allowed(dest, e))
626 entno = num_for_edict(e);
628 if (entno >= 8192 || chan < 0 || chan > 7)
631 idx = precache_sound_index("misc/null.wav");
632 sflags = SND_LARGEENTITY;
634 sflags |= SND_LARGESOUND;
635 WriteByte(dest, SVC_SOUND);
636 WriteByte(dest, sflags);
637 WriteShort(dest, entno);
638 WriteByte(dest, chan);
639 if (sflags & SND_LARGESOUND)
640 WriteShort(dest, idx);
642 WriteByte(dest, idx);
643 WriteCoord(dest, e.origin.x);
644 WriteCoord(dest, e.origin.y);
645 WriteCoord(dest, e.origin.z);
649 WriteByte(dest, SVC_STOPSOUND);
650 WriteShort(dest, entno * 8 + chan);
653 void stopsound(entity e, float chan)
655 if (!sound_allowed(MSG_BROADCAST, e))
658 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
659 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
662 void play2(entity e, string filename)
664 //stuffcmd(e, strcat("play2 ", filename, "\n"));
666 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
669 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
671 float spamsound(entity e, float chan, string samp, float vol, float atten)
673 if (!sound_allowed(MSG_BROADCAST, e))
676 if (time > e.spamtime)
679 sound(e, chan, samp, vol, atten);
685 void play2team(float t, string filename)
689 if (autocvar_bot_sound_monopoly)
692 FOR_EACH_REALPLAYER(head)
695 play2(head, filename);
699 void play2all(string samp)
701 if (autocvar_bot_sound_monopoly)
704 sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
707 void PrecachePlayerSounds(string f);
708 void precache_playermodel(string m)
710 float globhandle, i, n;
713 if(substring(m, -9,5) == "_lod1")
715 if(substring(m, -9,5) == "_lod2")
718 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
721 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
725 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
728 n = search_getsize(globhandle);
729 for (i = 0; i < n; ++i)
731 //print(search_getfilename(globhandle, i), "\n");
732 f = search_getfilename(globhandle, i);
733 PrecachePlayerSounds(f);
735 search_end(globhandle);
737 void precache_all_playermodels(string pattern)
739 float globhandle, i, n;
742 globhandle = search_begin(pattern, true, false);
745 n = search_getsize(globhandle);
746 for (i = 0; i < n; ++i)
748 //print(search_getfilename(globhandle, i), "\n");
749 f = search_getfilename(globhandle, i);
750 precache_playermodel(f);
752 search_end(globhandle);
757 // gamemode related things
758 precache_model ("models/misc/chatbubble.spr");
759 precache_model("models/ice/ice.md3");
761 #ifdef TTURRETS_ENABLED
762 if (autocvar_g_turrets)
766 // Precache all player models if desired
767 if (autocvar_sv_precacheplayermodels)
769 PrecachePlayerSounds("sound/player/default.sounds");
770 precache_all_playermodels("models/player/*.zym");
771 precache_all_playermodels("models/player/*.dpm");
772 precache_all_playermodels("models/player/*.md3");
773 precache_all_playermodels("models/player/*.psk");
774 precache_all_playermodels("models/player/*.iqm");
777 if (autocvar_sv_defaultcharacter)
780 s = autocvar_sv_defaultplayermodel_red;
782 precache_playermodel(s);
783 s = autocvar_sv_defaultplayermodel_blue;
785 precache_playermodel(s);
786 s = autocvar_sv_defaultplayermodel_yellow;
788 precache_playermodel(s);
789 s = autocvar_sv_defaultplayermodel_pink;
791 precache_playermodel(s);
792 s = autocvar_sv_defaultplayermodel;
794 precache_playermodel(s);
799 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
800 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
803 // gore and miscellaneous sounds
804 //precache_sound ("misc/h2ohit.wav");
805 precache_model ("models/hook.md3");
806 precache_sound ("misc/armorimpact.wav");
807 precache_sound ("misc/bodyimpact1.wav");
808 precache_sound ("misc/bodyimpact2.wav");
809 precache_sound ("misc/gib.wav");
810 precache_sound ("misc/gib_splat01.wav");
811 precache_sound ("misc/gib_splat02.wav");
812 precache_sound ("misc/gib_splat03.wav");
813 precache_sound ("misc/gib_splat04.wav");
814 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
815 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
816 precache_sound ("misc/null.wav");
817 precache_sound ("misc/spawn.wav");
818 precache_sound ("misc/talk.wav");
819 precache_sound ("misc/teleport.wav");
820 precache_sound ("misc/poweroff.wav");
821 precache_sound ("player/lava.wav");
822 precache_sound ("player/slime.wav");
824 precache_model ("models/sprites/0.spr32");
825 precache_model ("models/sprites/1.spr32");
826 precache_model ("models/sprites/2.spr32");
827 precache_model ("models/sprites/3.spr32");
828 precache_model ("models/sprites/4.spr32");
829 precache_model ("models/sprites/5.spr32");
830 precache_model ("models/sprites/6.spr32");
831 precache_model ("models/sprites/7.spr32");
832 precache_model ("models/sprites/8.spr32");
833 precache_model ("models/sprites/9.spr32");
834 precache_model ("models/sprites/10.spr32");
836 // common weapon precaches
837 precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound here
838 precache_sound ("weapons/weapon_switch.wav");
839 precache_sound ("weapons/weaponpickup.wav");
840 precache_sound ("weapons/unavailable.wav");
841 precache_sound ("weapons/dryfire.wav");
842 if (g_grappling_hook)
844 precache_sound ("weapons/hook_fire.wav"); // hook
845 precache_sound ("weapons/hook_impact.wav"); // hook
848 precache_model("models/elaser.mdl");
849 precache_model("models/laser.mdl");
850 precache_model("models/ebomb.mdl");
853 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
855 if (!self.noise && self.music) // quake 3 uses the music field
856 self.noise = self.music;
858 // plays music for the level if there is any
861 precache_sound (self.noise);
862 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
866 #include "precache-for-csqc.inc"
870 void make_safe_for_remove(entity e)
872 if (e.initialize_entity)
874 entity ent, prev = world;
875 for (ent = initialize_entity_first; ent; )
877 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
879 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
880 // skip it in linked list
883 prev.initialize_entity_next = ent.initialize_entity_next;
884 ent = prev.initialize_entity_next;
888 initialize_entity_first = ent.initialize_entity_next;
889 ent = initialize_entity_first;
895 ent = ent.initialize_entity_next;
901 void objerror(string s)
903 make_safe_for_remove(self);
907 .float remove_except_protected_forbidden;
908 void remove_except_protected(entity e)
910 if(e.remove_except_protected_forbidden)
911 error("not allowed to remove this at this point");
915 void remove_unsafely(entity e)
917 if(e.classname == "spike")
918 error("Removing spikes is forbidden (crylink bug), please report");
922 void remove_safely(entity e)
924 make_safe_for_remove(e);
928 void InitializeEntity(entity e, void(void) func, float order)
932 if (!e || e.initialize_entity)
934 // make a proxy initializer entity
938 e.classname = "initialize_entity";
942 e.initialize_entity = func;
943 e.initialize_entity_order = order;
945 cur = initialize_entity_first;
949 if (!cur || cur.initialize_entity_order > order)
951 // insert between prev and cur
953 prev.initialize_entity_next = e;
955 initialize_entity_first = e;
956 e.initialize_entity_next = cur;
960 cur = cur.initialize_entity_next;
963 void InitializeEntitiesRun()
966 startoflist = initialize_entity_first;
967 initialize_entity_first = world;
968 remove = remove_except_protected;
969 for (self = startoflist; self; self = self.initialize_entity_next)
971 self.remove_except_protected_forbidden = 1;
973 for (self = startoflist; self; )
977 e = self.initialize_entity_next;
978 func = self.initialize_entity;
979 self.initialize_entity_order = 0;
980 self.initialize_entity = func_null;
981 self.initialize_entity_next = world;
982 self.remove_except_protected_forbidden = 0;
983 if (self.classname == "initialize_entity")
987 builtin_remove(self);
990 //dprint("Delayed initialization: ", self.classname, "\n");
996 backtrace(strcat("Null function in: ", self.classname, "\n"));
1000 remove = remove_unsafely;
1003 .float uncustomizeentityforclient_set;
1004 .void(void) uncustomizeentityforclient;
1005 void UncustomizeEntitiesRun()
1009 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1010 self.uncustomizeentityforclient();
1013 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1015 e.customizeentityforclient = customizer;
1016 e.uncustomizeentityforclient = uncustomizer;
1017 e.uncustomizeentityforclient_set = !!uncustomizer;
1021 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1024 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1028 if (e.classname == "")
1029 e.classname = "net_linked";
1031 if (e.model == "" || self.modelindex == 0)
1035 setmodel(e, "null");
1039 e.SendEntity = sendfunc;
1040 e.SendFlags = 0xFFFFFF;
1043 e.effects |= EF_NODEPTHTEST;
1047 e.nextthink = time + dt;
1048 e.think = SUB_Remove;
1053 entity eliminatedPlayers;
1054 .float(entity) isEliminated;
1055 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1059 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1060 WriteByte(MSG_ENTITY, sendflags);
1064 for(i = 1; i <= maxclients; i += 8)
1066 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1068 if(eliminatedPlayers.isEliminated(e))
1071 WriteByte(MSG_ENTITY, f);
1078 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1080 if(eliminatedPlayers)
1082 backtrace("Can't spawn eliminatedPlayers again!");
1085 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1086 eliminatedPlayers.isEliminated = isEliminated_func;
1090 void adaptor_think2touch()
1099 void adaptor_think2use()
1111 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1113 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
1114 self.projectiledeathtype |= HITTYPE_SPLASH;
1115 adaptor_think2use();
1118 // deferred dropping
1119 void DropToFloor_Handler()
1121 builtin_droptofloor();
1122 self.dropped_origin = self.origin;
1127 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1132 float trace_hits_box_a0, trace_hits_box_a1;
1134 float trace_hits_box_1d(float end, float thmi, float thma)
1138 // just check if x is in range
1146 // do the trace with respect to x
1147 // 0 -> end has to stay in thmi -> thma
1148 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1149 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1150 if (trace_hits_box_a0 > trace_hits_box_a1)
1156 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1161 // now it is a trace from 0 to end
1163 trace_hits_box_a0 = 0;
1164 trace_hits_box_a1 = 1;
1166 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1168 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1170 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1176 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1178 return trace_hits_box(start, end, thmi - ma, thma - mi);
1181 float SUB_NoImpactCheck()
1183 // zero hitcontents = this is not the real impact, but either the
1184 // mirror-impact of something hitting the projectile instead of the
1185 // projectile hitting the something, or a touchareagrid one. Neither of
1186 // these stop the projectile from moving, so...
1187 if(trace_dphitcontents == 0)
1189 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1190 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));
1193 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1195 if (other == world && self.size != '0 0 0')
1198 tic = self.velocity * sys_frametime;
1199 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1200 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1201 if (trace_fraction >= 1)
1203 dprint("Odd... did not hit...?\n");
1205 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1207 dprint("Detected and prevented the sky-grapple bug.\n");
1215 #define SUB_OwnerCheck() (other && (other == self.owner))
1217 void RemoveGrapplingHook(entity pl);
1218 void W_Crylink_Dequeue(entity e);
1219 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1221 if(SUB_OwnerCheck())
1223 if(SUB_NoImpactCheck())
1225 if(self.classname == "nade")
1226 return false; // no checks here
1227 else if(self.classname == "grapplinghook")
1228 RemoveGrapplingHook(self.realowner);
1229 else if(self.classname == "spike")
1231 W_Crylink_Dequeue(self);
1238 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1239 UpdateCSQCProjectile(self);
1242 #define PROJECTILE_TOUCH if(WarpZone_Projectile_Touch()) return
1244 #define ITEM_TOUCH_NEEDKILL() (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
1245 #define ITEM_DAMAGE_NEEDKILL(dt) (((dt) == DEATH_HURTTRIGGER) || ((dt) == DEATH_SLIME) || ((dt) == DEATH_LAVA) || ((dt) == DEATH_SWAMP))
1247 void URI_Get_Callback(float id, float status, string data)
1249 if(url_URI_Get_Callback(id, status, data))
1253 else if (id == URI_GET_DISCARD)
1257 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1260 Curl_URI_Get_Callback(id, status, data);
1262 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1265 OnlineBanList_URI_Get_Callback(id, status, data);
1269 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1273 string uid2name(string myuid) {
1275 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1277 // FIXME remove this later after 0.6 release
1278 // convert old style broken records to correct style
1281 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1284 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1285 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1290 s = "^1Unregistered Player";
1294 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1297 vector start, org, delta, end, enddown, mstart;
1300 m = e.dphitcontentsmask;
1301 e.dphitcontentsmask = goodcontents | badcontents;
1304 delta = world.maxs - world.mins;
1308 for (i = 0; i < attempts; ++i)
1310 start_x = org.x + random() * delta.x;
1311 start_y = org.y + random() * delta.y;
1312 start_z = org.z + random() * delta.z;
1314 // rule 1: start inside world bounds, and outside
1315 // solid, and don't start from somewhere where you can
1316 // fall down to evil
1317 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1318 if (trace_fraction >= 1)
1320 if (trace_startsolid)
1322 if (trace_dphitcontents & badcontents)
1324 if (trace_dphitq3surfaceflags & badsurfaceflags)
1327 // rule 2: if we are too high, lower the point
1328 if (trace_fraction * delta.z > maxaboveground)
1329 start = trace_endpos + '0 0 1' * maxaboveground;
1330 enddown = trace_endpos;
1332 // rule 3: make sure we aren't outside the map. This only works
1333 // for somewhat well formed maps. A good rule of thumb is that
1334 // the map should have a convex outside hull.
1335 // these can be traceLINES as we already verified the starting box
1336 mstart = start + 0.5 * (e.mins + e.maxs);
1337 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1338 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1340 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1341 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1343 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1344 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1346 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1347 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1349 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1350 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1353 // rule 4: we must "see" some spawnpoint or item
1354 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1355 if(checkpvs(mstart, sp))
1356 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1360 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1361 if(checkpvs(mstart, sp))
1362 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1368 // find a random vector to "look at"
1369 end_x = org.x + random() * delta.x;
1370 end_y = org.y + random() * delta.y;
1371 end_z = org.z + random() * delta.z;
1372 end = start + normalize(end - start) * vlen(delta);
1374 // rule 4: start TO end must not be too short
1375 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1376 if (trace_startsolid)
1378 if (trace_fraction < minviewdistance / vlen(delta))
1381 // rule 5: don't want to look at sky
1382 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1385 // rule 6: we must not end up in trigger_hurt
1386 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1392 e.dphitcontentsmask = m;
1396 setorigin(e, start);
1397 e.angles = vectoangles(end - start);
1398 dprint("Needed ", ftos(i + 1), " attempts\n");
1405 void write_recordmarker(entity pl, float tstart, float dt)
1407 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1409 // also write a marker into demo files for demotc-race-record-extractor to find
1412 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1413 " ", ftos(tstart), " ", ftos(dt), "\n"));
1416 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1429 if(allowcenter) // 2: allow center handedness
1442 if(allowcenter) // 2: allow center handedness
1458 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1463 if (autocvar_g_shootfromeye)
1467 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1468 else { vecs_y = 0; vecs.z -= 2; }
1476 else if (autocvar_g_shootfromcenter)
1481 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1491 else if (autocvar_g_shootfromclient)
1493 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1498 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1500 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1504 void attach_sameorigin(entity e, entity to, string tag)
1506 vector org, t_forward, t_left, t_up, e_forward, e_up;
1509 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1510 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1511 t_forward = v_forward * tagscale;
1512 t_left = v_right * -tagscale;
1513 t_up = v_up * tagscale;
1515 e.origin_x = org * t_forward;
1516 e.origin_y = org * t_left;
1517 e.origin_z = org * t_up;
1519 // current forward and up directions
1520 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1521 e.angles = AnglesTransform_FromVAngles(e.angles);
1523 e.angles = AnglesTransform_FromAngles(e.angles);
1524 fixedmakevectors(e.angles);
1526 // untransform forward, up!
1527 e_forward_x = v_forward * t_forward;
1528 e_forward_y = v_forward * t_left;
1529 e_forward_z = v_forward * t_up;
1530 e_up_x = v_up * t_forward;
1531 e_up_y = v_up * t_left;
1532 e_up_z = v_up * t_up;
1534 e.angles = fixedvectoangles2(e_forward, e_up);
1535 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1536 e.angles = AnglesTransform_ToVAngles(e.angles);
1538 e.angles = AnglesTransform_ToAngles(e.angles);
1540 setattachment(e, to, tag);
1541 setorigin(e, e.origin);
1544 void detach_sameorigin(entity e)
1547 org = gettaginfo(e, 0);
1548 e.angles = fixedvectoangles2(v_forward, v_up);
1549 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1550 e.angles = AnglesTransform_ToVAngles(e.angles);
1552 e.angles = AnglesTransform_ToAngles(e.angles);
1554 setattachment(e, world, "");
1555 setorigin(e, e.origin);
1558 void follow_sameorigin(entity e, entity to)
1560 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1561 e.aiment = to; // make the hole follow bmodel
1562 e.punchangle = to.angles; // the original angles of bmodel
1563 e.view_ofs = e.origin - to.origin; // relative origin
1564 e.v_angle = e.angles - to.angles; // relative angles
1567 void unfollow_sameorigin(entity e)
1569 e.movetype = MOVETYPE_NONE;
1572 entity gettaginfo_relative_ent;
1573 vector gettaginfo_relative(entity e, float tag)
1575 if (!gettaginfo_relative_ent)
1577 gettaginfo_relative_ent = spawn();
1578 gettaginfo_relative_ent.effects = EF_NODRAW;
1580 gettaginfo_relative_ent.model = e.model;
1581 gettaginfo_relative_ent.modelindex = e.modelindex;
1582 gettaginfo_relative_ent.frame = e.frame;
1583 return gettaginfo(gettaginfo_relative_ent, tag);
1588 float modeleffect_SendEntity(entity to, float sf)
1591 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1594 if(self.velocity != '0 0 0')
1596 if(self.angles != '0 0 0')
1598 if(self.avelocity != '0 0 0')
1601 WriteByte(MSG_ENTITY, f);
1602 WriteShort(MSG_ENTITY, self.modelindex);
1603 WriteByte(MSG_ENTITY, self.skin);
1604 WriteByte(MSG_ENTITY, self.frame);
1605 WriteCoord(MSG_ENTITY, self.origin.x);
1606 WriteCoord(MSG_ENTITY, self.origin.y);
1607 WriteCoord(MSG_ENTITY, self.origin.z);
1610 WriteCoord(MSG_ENTITY, self.velocity.x);
1611 WriteCoord(MSG_ENTITY, self.velocity.y);
1612 WriteCoord(MSG_ENTITY, self.velocity.z);
1616 WriteCoord(MSG_ENTITY, self.angles.x);
1617 WriteCoord(MSG_ENTITY, self.angles.y);
1618 WriteCoord(MSG_ENTITY, self.angles.z);
1622 WriteCoord(MSG_ENTITY, self.avelocity.x);
1623 WriteCoord(MSG_ENTITY, self.avelocity.y);
1624 WriteCoord(MSG_ENTITY, self.avelocity.z);
1626 WriteShort(MSG_ENTITY, self.scale * 256.0);
1627 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1628 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1629 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1630 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1635 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)
1640 e.classname = "modeleffect";
1648 e.teleport_time = t1;
1652 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1656 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1659 sz = max(e.scale, e.scale2);
1660 setsize(e, e.mins * sz, e.maxs * sz);
1661 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1664 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1666 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1669 float randombit(float bits)
1671 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1680 for(f = 1; f <= bits; f *= 2)
1689 r = (r - 1) / (n - 1);
1696 float randombits(float bits, float k, float error_return)
1700 while(k > 0 && bits != r)
1702 r += randombit(bits - r);
1711 void randombit_test(float bits, float iter)
1715 print(ftos(randombit(bits)), "\n");
1720 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1722 if(halflifedist > 0)
1723 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1724 else if(halflifedist < 0)
1725 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1734 #define cvar_string_normal builtin_cvar_string
1735 #define cvar_normal builtin_cvar
1737 string cvar_string_normal(string n)
1739 if (!(cvar_type(n) & 1))
1740 backtrace(strcat("Attempt to access undefined cvar: ", n));
1741 return builtin_cvar_string(n);
1744 float cvar_normal(string n)
1746 return stof(cvar_string_normal(n));
1749 #define cvar_set_normal builtin_cvar_set
1757 oself.think = SUB_Remove;
1758 oself.nextthink = time;
1764 Execute func() after time + fdelay.
1765 self when func is executed = self when defer is called
1767 void defer(float fdelay, void() func)
1774 e.think = defer_think;
1775 e.nextthink = time + fdelay;
1778 .string aiment_classname;
1779 .float aiment_deadflag;
1780 void SetMovetypeFollow(entity ent, entity e)
1782 // FIXME this may not be warpzone aware
1783 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1784 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.
1785 ent.aiment = e; // make the hole follow bmodel
1786 ent.punchangle = e.angles; // the original angles of bmodel
1787 ent.view_ofs = ent.origin - e.origin; // relative origin
1788 ent.v_angle = ent.angles - e.angles; // relative angles
1789 ent.aiment_classname = strzone(e.classname);
1790 ent.aiment_deadflag = e.deadflag;
1792 void UnsetMovetypeFollow(entity ent)
1794 ent.movetype = MOVETYPE_FLY;
1795 PROJECTILE_MAKETRIGGER(ent);
1798 float LostMovetypeFollow(entity ent)
1801 if(ent.movetype != MOVETYPE_FOLLOW)
1807 if(ent.aiment.classname != ent.aiment_classname)
1809 if(ent.aiment.deadflag != ent.aiment_deadflag)
1815 float isPushable(entity e)
1824 case "droppedweapon":
1825 case "keepawayball":
1826 case "nexball_basketball":
1827 case "nexball_football":
1829 case "bullet": // antilagged bullets can't hit this either
1832 if (e.projectiledeathtype)