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
537 if (_dest == MSG_ONE)
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 void UncustomizeEntitiesRun()
1007 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1008 self.uncustomizeentityforclient();
1011 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1013 e.customizeentityforclient = customizer;
1014 e.uncustomizeentityforclient = uncustomizer;
1015 e.uncustomizeentityforclient_set = !!uncustomizer;
1019 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1023 if (e.classname == "")
1024 e.classname = "net_linked";
1026 if (e.model == "" || self.modelindex == 0)
1030 setmodel(e, "null");
1034 e.SendEntity = sendfunc;
1035 e.SendFlags = 0xFFFFFF;
1038 e.effects |= EF_NODEPTHTEST;
1042 e.nextthink = time + dt;
1043 e.think = SUB_Remove;
1048 entity eliminatedPlayers;
1049 .float(entity) isEliminated;
1050 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1054 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1055 WriteByte(MSG_ENTITY, sendflags);
1059 for(i = 1; i <= maxclients; i += 8)
1061 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1063 if(eliminatedPlayers.isEliminated(e))
1066 WriteByte(MSG_ENTITY, f);
1073 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1075 if(eliminatedPlayers)
1077 backtrace("Can't spawn eliminatedPlayers again!");
1080 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1081 eliminatedPlayers.isEliminated = isEliminated_func;
1085 void adaptor_think2touch()
1094 void adaptor_think2use()
1106 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1108 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
1109 self.projectiledeathtype |= HITTYPE_SPLASH;
1110 adaptor_think2use();
1113 // deferred dropping
1114 void DropToFloor_Handler()
1116 builtin_droptofloor();
1117 self.dropped_origin = self.origin;
1122 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1127 float trace_hits_box_a0, trace_hits_box_a1;
1129 float trace_hits_box_1d(float end, float thmi, float thma)
1133 // just check if x is in range
1141 // do the trace with respect to x
1142 // 0 -> end has to stay in thmi -> thma
1143 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1144 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1145 if (trace_hits_box_a0 > trace_hits_box_a1)
1151 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1156 // now it is a trace from 0 to end
1158 trace_hits_box_a0 = 0;
1159 trace_hits_box_a1 = 1;
1161 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1163 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1165 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1171 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1173 return trace_hits_box(start, end, thmi - ma, thma - mi);
1176 float SUB_NoImpactCheck()
1178 // zero hitcontents = this is not the real impact, but either the
1179 // mirror-impact of something hitting the projectile instead of the
1180 // projectile hitting the something, or a touchareagrid one. Neither of
1181 // these stop the projectile from moving, so...
1182 if(trace_dphitcontents == 0)
1184 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1185 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));
1188 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1190 if (other == world && self.size != '0 0 0')
1193 tic = self.velocity * sys_frametime;
1194 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1195 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1196 if (trace_fraction >= 1)
1198 dprint("Odd... did not hit...?\n");
1200 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1202 dprint("Detected and prevented the sky-grapple bug.\n");
1210 #define SUB_OwnerCheck() (other && (other == self.owner))
1212 void RemoveGrapplingHook(entity pl);
1213 void W_Crylink_Dequeue(entity e);
1214 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1216 if(SUB_OwnerCheck())
1218 if(SUB_NoImpactCheck())
1220 if(self.classname == "nade")
1221 return false; // no checks here
1222 else if(self.classname == "grapplinghook")
1223 RemoveGrapplingHook(self.realowner);
1224 else if(self.classname == "spike")
1226 W_Crylink_Dequeue(self);
1233 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1234 UpdateCSQCProjectile(self);
1239 void URI_Get_Callback(float id, float status, string data)
1241 if(url_URI_Get_Callback(id, status, data))
1245 else if (id == URI_GET_DISCARD)
1249 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1252 Curl_URI_Get_Callback(id, status, data);
1254 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1257 OnlineBanList_URI_Get_Callback(id, status, data);
1261 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1265 string uid2name(string myuid) {
1267 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1269 // FIXME remove this later after 0.6 release
1270 // convert old style broken records to correct style
1273 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1276 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1277 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1282 s = "^1Unregistered Player";
1286 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1289 vector start, org, delta, end, enddown, mstart;
1292 m = e.dphitcontentsmask;
1293 e.dphitcontentsmask = goodcontents | badcontents;
1296 delta = world.maxs - world.mins;
1300 for (i = 0; i < attempts; ++i)
1302 start_x = org.x + random() * delta.x;
1303 start_y = org.y + random() * delta.y;
1304 start_z = org.z + random() * delta.z;
1306 // rule 1: start inside world bounds, and outside
1307 // solid, and don't start from somewhere where you can
1308 // fall down to evil
1309 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1310 if (trace_fraction >= 1)
1312 if (trace_startsolid)
1314 if (trace_dphitcontents & badcontents)
1316 if (trace_dphitq3surfaceflags & badsurfaceflags)
1319 // rule 2: if we are too high, lower the point
1320 if (trace_fraction * delta.z > maxaboveground)
1321 start = trace_endpos + '0 0 1' * maxaboveground;
1322 enddown = trace_endpos;
1324 // rule 3: make sure we aren't outside the map. This only works
1325 // for somewhat well formed maps. A good rule of thumb is that
1326 // the map should have a convex outside hull.
1327 // these can be traceLINES as we already verified the starting box
1328 mstart = start + 0.5 * (e.mins + e.maxs);
1329 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1330 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1332 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1333 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1335 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1336 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1338 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1339 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1341 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1342 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1345 // rule 4: we must "see" some spawnpoint or item
1346 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1347 if(checkpvs(mstart, sp))
1348 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1352 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1353 if(checkpvs(mstart, sp))
1354 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1360 // find a random vector to "look at"
1361 end_x = org.x + random() * delta.x;
1362 end_y = org.y + random() * delta.y;
1363 end_z = org.z + random() * delta.z;
1364 end = start + normalize(end - start) * vlen(delta);
1366 // rule 4: start TO end must not be too short
1367 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1368 if (trace_startsolid)
1370 if (trace_fraction < minviewdistance / vlen(delta))
1373 // rule 5: don't want to look at sky
1374 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1377 // rule 6: we must not end up in trigger_hurt
1378 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1384 e.dphitcontentsmask = m;
1388 setorigin(e, start);
1389 e.angles = vectoangles(end - start);
1390 dprint("Needed ", ftos(i + 1), " attempts\n");
1397 void write_recordmarker(entity pl, float tstart, float dt)
1399 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1401 // also write a marker into demo files for demotc-race-record-extractor to find
1404 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1405 " ", ftos(tstart), " ", ftos(dt), "\n"));
1408 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1421 if(allowcenter) // 2: allow center handedness
1434 if(allowcenter) // 2: allow center handedness
1450 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1455 if (autocvar_g_shootfromeye)
1459 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1460 else { vecs_y = 0; vecs.z -= 2; }
1468 else if (autocvar_g_shootfromcenter)
1473 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1483 else if (autocvar_g_shootfromclient)
1485 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1490 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1492 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1496 void attach_sameorigin(entity e, entity to, string tag)
1498 vector org, t_forward, t_left, t_up, e_forward, e_up;
1501 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1502 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1503 t_forward = v_forward * tagscale;
1504 t_left = v_right * -tagscale;
1505 t_up = v_up * tagscale;
1507 e.origin_x = org * t_forward;
1508 e.origin_y = org * t_left;
1509 e.origin_z = org * t_up;
1511 // current forward and up directions
1512 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1513 e.angles = AnglesTransform_FromVAngles(e.angles);
1515 e.angles = AnglesTransform_FromAngles(e.angles);
1516 fixedmakevectors(e.angles);
1518 // untransform forward, up!
1519 e_forward_x = v_forward * t_forward;
1520 e_forward_y = v_forward * t_left;
1521 e_forward_z = v_forward * t_up;
1522 e_up_x = v_up * t_forward;
1523 e_up_y = v_up * t_left;
1524 e_up_z = v_up * t_up;
1526 e.angles = fixedvectoangles2(e_forward, e_up);
1527 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1528 e.angles = AnglesTransform_ToVAngles(e.angles);
1530 e.angles = AnglesTransform_ToAngles(e.angles);
1532 setattachment(e, to, tag);
1533 setorigin(e, e.origin);
1536 void detach_sameorigin(entity e)
1539 org = gettaginfo(e, 0);
1540 e.angles = fixedvectoangles2(v_forward, v_up);
1541 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1542 e.angles = AnglesTransform_ToVAngles(e.angles);
1544 e.angles = AnglesTransform_ToAngles(e.angles);
1546 setattachment(e, world, "");
1547 setorigin(e, e.origin);
1550 void follow_sameorigin(entity e, entity to)
1552 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1553 e.aiment = to; // make the hole follow bmodel
1554 e.punchangle = to.angles; // the original angles of bmodel
1555 e.view_ofs = e.origin - to.origin; // relative origin
1556 e.v_angle = e.angles - to.angles; // relative angles
1559 void unfollow_sameorigin(entity e)
1561 e.movetype = MOVETYPE_NONE;
1564 entity gettaginfo_relative_ent;
1565 vector gettaginfo_relative(entity e, float tag)
1567 if (!gettaginfo_relative_ent)
1569 gettaginfo_relative_ent = spawn();
1570 gettaginfo_relative_ent.effects = EF_NODRAW;
1572 gettaginfo_relative_ent.model = e.model;
1573 gettaginfo_relative_ent.modelindex = e.modelindex;
1574 gettaginfo_relative_ent.frame = e.frame;
1575 return gettaginfo(gettaginfo_relative_ent, tag);
1580 float modeleffect_SendEntity(entity to, float sf)
1583 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1586 if(self.velocity != '0 0 0')
1588 if(self.angles != '0 0 0')
1590 if(self.avelocity != '0 0 0')
1593 WriteByte(MSG_ENTITY, f);
1594 WriteShort(MSG_ENTITY, self.modelindex);
1595 WriteByte(MSG_ENTITY, self.skin);
1596 WriteByte(MSG_ENTITY, self.frame);
1597 WriteCoord(MSG_ENTITY, self.origin.x);
1598 WriteCoord(MSG_ENTITY, self.origin.y);
1599 WriteCoord(MSG_ENTITY, self.origin.z);
1602 WriteCoord(MSG_ENTITY, self.velocity.x);
1603 WriteCoord(MSG_ENTITY, self.velocity.y);
1604 WriteCoord(MSG_ENTITY, self.velocity.z);
1608 WriteCoord(MSG_ENTITY, self.angles.x);
1609 WriteCoord(MSG_ENTITY, self.angles.y);
1610 WriteCoord(MSG_ENTITY, self.angles.z);
1614 WriteCoord(MSG_ENTITY, self.avelocity.x);
1615 WriteCoord(MSG_ENTITY, self.avelocity.y);
1616 WriteCoord(MSG_ENTITY, self.avelocity.z);
1618 WriteShort(MSG_ENTITY, self.scale * 256.0);
1619 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1620 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1621 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1622 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1627 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)
1632 e.classname = "modeleffect";
1640 e.teleport_time = t1;
1644 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1648 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1651 sz = max(e.scale, e.scale2);
1652 setsize(e, e.mins * sz, e.maxs * sz);
1653 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1656 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1658 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1661 float randombit(float bits)
1663 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1672 for(f = 1; f <= bits; f *= 2)
1681 r = (r - 1) / (n - 1);
1688 float randombits(float bits, float k, float error_return)
1692 while(k > 0 && bits != r)
1694 r += randombit(bits - r);
1703 void randombit_test(float bits, float iter)
1707 print(ftos(randombit(bits)), "\n");
1712 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1714 if(halflifedist > 0)
1715 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1716 else if(halflifedist < 0)
1717 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1729 oself.think = SUB_Remove;
1730 oself.nextthink = time;
1736 Execute func() after time + fdelay.
1737 self when func is executed = self when defer is called
1739 void defer(float fdelay, void() func)
1746 e.think = defer_think;
1747 e.nextthink = time + fdelay;
1750 .string aiment_classname;
1751 .float aiment_deadflag;
1752 void SetMovetypeFollow(entity ent, entity e)
1754 // FIXME this may not be warpzone aware
1755 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1756 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.
1757 ent.aiment = e; // make the hole follow bmodel
1758 ent.punchangle = e.angles; // the original angles of bmodel
1759 ent.view_ofs = ent.origin - e.origin; // relative origin
1760 ent.v_angle = ent.angles - e.angles; // relative angles
1761 ent.aiment_classname = strzone(e.classname);
1762 ent.aiment_deadflag = e.deadflag;
1764 void UnsetMovetypeFollow(entity ent)
1766 ent.movetype = MOVETYPE_FLY;
1767 PROJECTILE_MAKETRIGGER(ent);
1770 float LostMovetypeFollow(entity ent)
1773 if(ent.movetype != MOVETYPE_FOLLOW)
1779 if(ent.aiment.classname != ent.aiment_classname)
1781 if(ent.aiment.deadflag != ent.aiment_deadflag)
1787 float isPushable(entity e)
1796 case "droppedweapon":
1797 case "keepawayball":
1798 case "nexball_basketball":
1799 case "nexball_football":
1801 case "bullet": // antilagged bullets can't hit this either
1804 if (e.projectiledeathtype)