4 #include "miscfunctions.qh"
5 #include "../dpdefs/progsdefs.qc"
6 #include "../dpdefs/dpextensions.qc"
8 #include "../common/playerstats.qh"
9 #include "../warpzonelib/anglestransform.qh"
10 #include "../warpzonelib/server.qh"
11 #include "../common/constants.qh"
12 #include "../common/teams.qh"
13 #include "../common/util.qh"
14 #include "../common/urllib.qh"
15 #include "../common/command/generic.qh"
16 #include "../common/weapons/weapons.qh"
17 #include "weapons/accuracy.qh"
18 #include "weapons/csqcprojectile.qh"
19 #include "weapons/selection.qh"
21 #include "autocvars.qh"
22 #include "constants.qh"
24 #include "../common/notifications.qh"
25 #include "../common/deathtypes.qh"
26 #include "mutators/mutators_include.qh"
27 #include "tturrets/include/turrets_early.qh"
28 #include "../common/mapinfo.qh"
29 #include "command/common.qh"
30 #include "../csqcmodellib/sv_model.qh"
34 void crosshair_trace(entity pl)
36 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));
38 void crosshair_trace_plusvisibletriggers(entity pl)
42 first = findchainfloat(solid, SOLID_TRIGGER);
44 for (e = first; e; e = e.chain)
50 for (e = first; e; e = e.chain)
51 e.solid = SOLID_TRIGGER;
53 void WarpZone_crosshair_trace(entity pl)
55 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));
59 string admin_name(void)
61 if(autocvar_sv_adminnick != "")
62 return autocvar_sv_adminnick;
64 return "SERVER ADMIN";
67 void DistributeEvenly_Init(float amount, float totalweight)
69 if (DistributeEvenly_amount)
71 dprint("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
72 dprint(ftos(DistributeEvenly_totalweight), " left!)\n");
75 DistributeEvenly_amount = 0;
77 DistributeEvenly_amount = amount;
78 DistributeEvenly_totalweight = totalweight;
80 float DistributeEvenly_Get(float weight)
85 f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
86 DistributeEvenly_totalweight -= weight;
87 DistributeEvenly_amount -= f;
90 float DistributeEvenly_GetRandomized(float weight)
95 f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
96 DistributeEvenly_totalweight -= weight;
97 DistributeEvenly_amount -= f;
102 void GameLogEcho(string s)
107 if (autocvar_sv_eventlog_files)
112 matches = autocvar_sv_eventlog_files_counter + 1;
113 cvar_set("sv_eventlog_files_counter", ftos(matches));
116 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
117 fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
118 logfile = fopen(fn, FILE_APPEND);
119 fputs(logfile, ":logversion:3\n");
123 if (autocvar_sv_eventlog_files_timestamps)
124 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
126 fputs(logfile, strcat(s, "\n"));
129 if (autocvar_sv_eventlog_console)
138 // will be opened later
143 if (logfile_open && logfile >= 0)
150 entity findnearest(vector point, .string field, string value, vector axismod)
161 localhead = find(world, field, value);
164 if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
165 dist = localhead.oldorigin;
167 dist = localhead.origin;
169 dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
172 for (i = 0; i < num_nearest; ++i)
174 if (len < nearest_length[i])
178 // now i tells us where to insert at
179 // INSERTION SORT! YOU'VE SEEN IT! RUN!
180 if (i < NUM_NEAREST_ENTITIES)
182 for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
184 nearest_length[j + 1] = nearest_length[j];
185 nearest_entity[j + 1] = nearest_entity[j];
187 nearest_length[i] = len;
188 nearest_entity[i] = localhead;
189 if (num_nearest < NUM_NEAREST_ENTITIES)
190 num_nearest = num_nearest + 1;
193 localhead = find(localhead, field, value);
196 // now use the first one from our list that we can see
197 for (i = 0; i < num_nearest; ++i)
199 traceline(point, nearest_entity[i].origin, true, world);
200 if (trace_fraction == 1)
204 dprint("Nearest point (");
205 dprint(nearest_entity[0].netname);
206 dprint(") is not visible, using a visible one.\n");
208 return nearest_entity[i];
212 if (num_nearest == 0)
215 dprint("Not seeing any location point, using nearest as fallback.\n");
217 dprint("Candidates were: ");
218 for(j = 0; j < num_nearest; ++j)
222 dprint(nearest_entity[j].netname);
227 return nearest_entity[0];
230 void spawnfunc_target_location()
232 self.classname = "target_location";
233 // location name in netname
234 // eventually support: count, teamgame selectors, line of sight?
237 void spawnfunc_info_location()
239 self.classname = "target_location";
240 self.message = self.netname;
243 string NearestLocation(vector p)
248 loc = findnearest(p, classname, "target_location", '1 1 1');
255 loc = findnearest(p, target, "###item###", '1 1 4');
262 string formatmessage(string msg)
273 WarpZone_crosshair_trace(self);
274 cursor = trace_endpos;
275 cursor_ent = trace_ent;
279 break; // too many replacements
282 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
283 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
296 replacement = substring(msg, p, 2);
297 escape = substring(msg, p + 1, 1);
301 else if (escape == "\\")
303 else if (escape == "n")
305 else if (escape == "a")
306 replacement = ftos(floor(self.armorvalue));
307 else if (escape == "h")
308 replacement = ftos(floor(self.health));
309 else if (escape == "l")
310 replacement = NearestLocation(self.origin);
311 else if (escape == "y")
312 replacement = NearestLocation(cursor);
313 else if (escape == "d")
314 replacement = NearestLocation(self.death_origin);
315 else if (escape == "w") {
319 wep = self.switchweapon;
322 replacement = WEP_NAME(wep);
323 } else if (escape == "W") {
324 if (self.items & IT_SHELLS) replacement = "shells";
325 else if (self.items & IT_NAILS) replacement = "bullets";
326 else if (self.items & IT_ROCKETS) replacement = "rockets";
327 else if (self.items & IT_CELLS) replacement = "cells";
328 else if (self.items & IT_PLASMA) replacement = "plasma";
329 else replacement = "batteries"; // ;)
330 } else if (escape == "x") {
331 replacement = cursor_ent.netname;
332 if (replacement == "" || !cursor_ent)
333 replacement = "nothing";
334 } else if (escape == "s")
335 replacement = ftos(vlen(self.velocity - self.velocity.z * '0 0 1'));
336 else if (escape == "S")
337 replacement = ftos(vlen(self.velocity));
339 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
340 p = p + strlen(replacement);
345 float boolean(float value) { // if value is 0 return false (0), otherwise return true (1)
346 return (value == 0) ? false : true;
355 >0: receives a cvar from name=argv(f) value=argv(f+1)
357 void GetCvars_handleString(string thisname, float f, .string field, string name)
362 strunzone(self.field);
363 self.field = string_null;
367 if (thisname == name)
370 strunzone(self.field);
371 self.field = strzone(argv(f + 1));
375 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
377 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
379 GetCvars_handleString(thisname, f, field, name);
380 if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
381 if (thisname == name)
384 s = func(strcat1(self.field));
387 strunzone(self.field);
388 self.field = strzone(s);
392 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
399 if (thisname == name)
400 self.field = stof(argv(f + 1));
403 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
405 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
412 if (thisname == name)
416 self.field = stof(argv(f + 1));
425 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
428 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
431 o = W_FixWeaponOrder_ForceComplete(wo);
432 if(self.weaponorder_byimpulse)
434 strunzone(self.weaponorder_byimpulse);
435 self.weaponorder_byimpulse = string_null;
437 self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
440 void GetCvars(float f)
442 string s = string_null;
445 s = strcat1(argv(f));
450 MUTATOR_CALLHOOK(GetCvars);
452 Notification_GetCvars();
454 GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
455 GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
456 GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
457 GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
458 GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
459 GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
460 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
461 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
462 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
463 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
464 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
465 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
466 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
467 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
468 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
469 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
470 GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
471 GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
472 GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
473 GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
474 GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
475 GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
476 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
477 GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
479 self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
480 self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
482 GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
483 GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
484 GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
485 GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
486 GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
488 // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
491 if (s == "cl_weaponpriority")
492 self.switchweapon = w_getbestweapon(self);
493 if (s == "cl_allow_uidtracking")
494 PlayerStats_GameReport_AddPlayer(self);
498 // decolorizes and team colors the player name when needed
499 string playername(entity p)
502 if (teamplay && !intermission_running && IS_PLAYER(p))
504 t = Team_ColorCode(p.team);
505 return strcat(t, strdecolorize(p.netname));
511 vector randompos(vector m1, vector m2)
515 v_x = m2_x * random() + m1_x;
516 v_y = m2_y * random() + m1_y;
517 v_z = m2_z * random() + m1_z;
523 float sound_allowed(float dest, entity e)
525 // sounds from world may always pass
528 if (e.classname == "body")
530 else if (e.realowner && e.realowner != e)
532 else if (e.owner && e.owner != e)
537 // sounds to self may always pass
541 // sounds by players can be removed
542 if (autocvar_bot_sound_monopoly)
543 if (IS_REAL_CLIENT(e))
545 // anything else may pass
550 void sound(entity e, float chan, string samp, float vol, float atten)
552 if (!sound_allowed(MSG_BROADCAST, e))
554 sound7(e, chan, samp, vol, atten, 0, 0);
557 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
561 if (!sound_allowed(dest, e))
564 entno = num_for_edict(e);
565 idx = precache_sound_index(samp);
570 atten = floor(atten * 64);
571 vol = floor(vol * 255);
574 sflags |= SND_VOLUME;
576 sflags |= SND_ATTENUATION;
577 if (entno >= 8192 || chan < 0 || chan > 7)
578 sflags |= SND_LARGEENTITY;
580 sflags |= SND_LARGESOUND;
582 WriteByte(dest, SVC_SOUND);
583 WriteByte(dest, sflags);
584 if (sflags & SND_VOLUME)
585 WriteByte(dest, vol);
586 if (sflags & SND_ATTENUATION)
587 WriteByte(dest, atten);
588 if (sflags & SND_LARGEENTITY)
590 WriteShort(dest, entno);
591 WriteByte(dest, chan);
595 WriteShort(dest, entno * 8 + chan);
597 if (sflags & SND_LARGESOUND)
598 WriteShort(dest, idx);
600 WriteByte(dest, idx);
602 WriteCoord(dest, o.x);
603 WriteCoord(dest, o.y);
604 WriteCoord(dest, o.z);
606 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
610 if (!sound_allowed(dest, e))
613 o = e.origin + 0.5 * (e.mins + e.maxs);
614 soundtoat(dest, e, o, chan, samp, vol, atten);
616 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
618 soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, atten);
620 void stopsoundto(float dest, entity e, float chan)
624 if (!sound_allowed(dest, e))
627 entno = num_for_edict(e);
629 if (entno >= 8192 || chan < 0 || chan > 7)
632 idx = precache_sound_index("misc/null.wav");
633 sflags = SND_LARGEENTITY;
635 sflags |= SND_LARGESOUND;
636 WriteByte(dest, SVC_SOUND);
637 WriteByte(dest, sflags);
638 WriteShort(dest, entno);
639 WriteByte(dest, chan);
640 if (sflags & SND_LARGESOUND)
641 WriteShort(dest, idx);
643 WriteByte(dest, idx);
644 WriteCoord(dest, e.origin.x);
645 WriteCoord(dest, e.origin.y);
646 WriteCoord(dest, e.origin.z);
650 WriteByte(dest, SVC_STOPSOUND);
651 WriteShort(dest, entno * 8 + chan);
654 void stopsound(entity e, float chan)
656 if (!sound_allowed(MSG_BROADCAST, e))
659 stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
660 stopsoundto(MSG_ALL, e, chan); // in case of packet loss
663 void play2(entity e, string filename)
665 //stuffcmd(e, strcat("play2 ", filename, "\n"));
667 soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
670 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
672 float spamsound(entity e, float chan, string samp, float vol, float atten)
674 if (!sound_allowed(MSG_BROADCAST, e))
677 if (time > e.spamtime)
680 sound(e, chan, samp, vol, atten);
686 void play2team(float t, string filename)
690 if (autocvar_bot_sound_monopoly)
693 FOR_EACH_REALPLAYER(head)
696 play2(head, filename);
700 void play2all(string samp)
702 if (autocvar_bot_sound_monopoly)
705 sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
708 void PrecachePlayerSounds(string f);
709 void precache_playermodel(string m)
711 float globhandle, i, n;
714 if(substring(m, -9,5) == "_lod1")
716 if(substring(m, -9,5) == "_lod2")
719 f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
722 f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
726 globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
729 n = search_getsize(globhandle);
730 for (i = 0; i < n; ++i)
732 //print(search_getfilename(globhandle, i), "\n");
733 f = search_getfilename(globhandle, i);
734 PrecachePlayerSounds(f);
736 search_end(globhandle);
738 void precache_all_playermodels(string pattern)
740 float globhandle, i, n;
743 globhandle = search_begin(pattern, true, false);
746 n = search_getsize(globhandle);
747 for (i = 0; i < n; ++i)
749 //print(search_getfilename(globhandle, i), "\n");
750 f = search_getfilename(globhandle, i);
751 precache_playermodel(f);
753 search_end(globhandle);
758 // gamemode related things
759 precache_model ("models/misc/chatbubble.spr");
760 precache_model("models/ice/ice.md3");
762 #ifdef TTURRETS_ENABLED
763 if (autocvar_g_turrets)
767 // Precache all player models if desired
768 if (autocvar_sv_precacheplayermodels)
770 PrecachePlayerSounds("sound/player/default.sounds");
771 precache_all_playermodels("models/player/*.zym");
772 precache_all_playermodels("models/player/*.dpm");
773 precache_all_playermodels("models/player/*.md3");
774 precache_all_playermodels("models/player/*.psk");
775 precache_all_playermodels("models/player/*.iqm");
778 if (autocvar_sv_defaultcharacter)
781 s = autocvar_sv_defaultplayermodel_red;
783 precache_playermodel(s);
784 s = autocvar_sv_defaultplayermodel_blue;
786 precache_playermodel(s);
787 s = autocvar_sv_defaultplayermodel_yellow;
789 precache_playermodel(s);
790 s = autocvar_sv_defaultplayermodel_pink;
792 precache_playermodel(s);
793 s = autocvar_sv_defaultplayermodel;
795 precache_playermodel(s);
800 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
801 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
804 // gore and miscellaneous sounds
805 //precache_sound ("misc/h2ohit.wav");
806 precache_model ("models/hook.md3");
807 precache_sound ("misc/armorimpact.wav");
808 precache_sound ("misc/bodyimpact1.wav");
809 precache_sound ("misc/bodyimpact2.wav");
810 precache_sound ("misc/gib.wav");
811 precache_sound ("misc/gib_splat01.wav");
812 precache_sound ("misc/gib_splat02.wav");
813 precache_sound ("misc/gib_splat03.wav");
814 precache_sound ("misc/gib_splat04.wav");
815 PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
816 PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
817 precache_sound ("misc/null.wav");
818 precache_sound ("misc/spawn.wav");
819 precache_sound ("misc/talk.wav");
820 precache_sound ("misc/teleport.wav");
821 precache_sound ("misc/poweroff.wav");
822 precache_sound ("player/lava.wav");
823 precache_sound ("player/slime.wav");
825 precache_model ("models/sprites/0.spr32");
826 precache_model ("models/sprites/1.spr32");
827 precache_model ("models/sprites/2.spr32");
828 precache_model ("models/sprites/3.spr32");
829 precache_model ("models/sprites/4.spr32");
830 precache_model ("models/sprites/5.spr32");
831 precache_model ("models/sprites/6.spr32");
832 precache_model ("models/sprites/7.spr32");
833 precache_model ("models/sprites/8.spr32");
834 precache_model ("models/sprites/9.spr32");
835 precache_model ("models/sprites/10.spr32");
837 // common weapon precaches
838 precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound here
839 precache_sound ("weapons/weapon_switch.wav");
840 precache_sound ("weapons/weaponpickup.wav");
841 precache_sound ("weapons/unavailable.wav");
842 precache_sound ("weapons/dryfire.wav");
843 if (g_grappling_hook)
845 precache_sound ("weapons/hook_fire.wav"); // hook
846 precache_sound ("weapons/hook_impact.wav"); // hook
849 precache_model("models/elaser.mdl");
850 precache_model("models/laser.mdl");
851 precache_model("models/ebomb.mdl");
854 // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
856 if (!self.noise && self.music) // quake 3 uses the music field
857 self.noise = self.music;
859 // plays music for the level if there is any
862 precache_sound (self.noise);
863 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
867 #include "precache-for-csqc.inc"
871 void make_safe_for_remove(entity e)
873 if (e.initialize_entity)
875 entity ent, prev = world;
876 for (ent = initialize_entity_first; ent; )
878 if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
880 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
881 // skip it in linked list
884 prev.initialize_entity_next = ent.initialize_entity_next;
885 ent = prev.initialize_entity_next;
889 initialize_entity_first = ent.initialize_entity_next;
890 ent = initialize_entity_first;
896 ent = ent.initialize_entity_next;
902 void objerror(string s)
904 make_safe_for_remove(self);
908 .float remove_except_protected_forbidden;
909 void remove_except_protected(entity e)
911 if(e.remove_except_protected_forbidden)
912 error("not allowed to remove this at this point");
916 void remove_unsafely(entity e)
918 if(e.classname == "spike")
919 error("Removing spikes is forbidden (crylink bug), please report");
923 void remove_safely(entity e)
925 make_safe_for_remove(e);
929 void InitializeEntity(entity e, void(void) func, float order)
933 if (!e || e.initialize_entity)
935 // make a proxy initializer entity
939 e.classname = "initialize_entity";
943 e.initialize_entity = func;
944 e.initialize_entity_order = order;
946 cur = initialize_entity_first;
950 if (!cur || cur.initialize_entity_order > order)
952 // insert between prev and cur
954 prev.initialize_entity_next = e;
956 initialize_entity_first = e;
957 e.initialize_entity_next = cur;
961 cur = cur.initialize_entity_next;
964 void InitializeEntitiesRun()
967 startoflist = initialize_entity_first;
968 initialize_entity_first = world;
969 remove = remove_except_protected;
970 for (self = startoflist; self; self = self.initialize_entity_next)
972 self.remove_except_protected_forbidden = 1;
974 for (self = startoflist; self; )
978 e = self.initialize_entity_next;
979 func = self.initialize_entity;
980 self.initialize_entity_order = 0;
981 self.initialize_entity = func_null;
982 self.initialize_entity_next = world;
983 self.remove_except_protected_forbidden = 0;
984 if (self.classname == "initialize_entity")
988 builtin_remove(self);
991 //dprint("Delayed initialization: ", self.classname, "\n");
997 backtrace(strcat("Null function in: ", self.classname, "\n"));
1001 remove = remove_unsafely;
1004 .float uncustomizeentityforclient_set;
1005 .void(void) uncustomizeentityforclient;
1006 void UncustomizeEntitiesRun()
1010 for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1011 self.uncustomizeentityforclient();
1014 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1016 e.customizeentityforclient = customizer;
1017 e.uncustomizeentityforclient = uncustomizer;
1018 e.uncustomizeentityforclient_set = !!uncustomizer;
1022 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1025 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1029 if (e.classname == "")
1030 e.classname = "net_linked";
1032 if (e.model == "" || self.modelindex == 0)
1036 setmodel(e, "null");
1040 e.SendEntity = sendfunc;
1041 e.SendFlags = 0xFFFFFF;
1044 e.effects |= EF_NODEPTHTEST;
1048 e.nextthink = time + dt;
1049 e.think = SUB_Remove;
1054 entity eliminatedPlayers;
1055 .float(entity) isEliminated;
1056 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1060 WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1061 WriteByte(MSG_ENTITY, sendflags);
1065 for(i = 1; i <= maxclients; i += 8)
1067 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1069 if(eliminatedPlayers.isEliminated(e))
1072 WriteByte(MSG_ENTITY, f);
1079 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1081 if(eliminatedPlayers)
1083 backtrace("Can't spawn eliminatedPlayers again!");
1086 Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1087 eliminatedPlayers.isEliminated = isEliminated_func;
1091 void adaptor_think2touch()
1100 void adaptor_think2use()
1112 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1114 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
1115 self.projectiledeathtype |= HITTYPE_SPLASH;
1116 adaptor_think2use();
1119 // deferred dropping
1120 void DropToFloor_Handler()
1122 builtin_droptofloor();
1123 self.dropped_origin = self.origin;
1128 InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1133 float trace_hits_box_a0, trace_hits_box_a1;
1135 float trace_hits_box_1d(float end, float thmi, float thma)
1139 // just check if x is in range
1147 // do the trace with respect to x
1148 // 0 -> end has to stay in thmi -> thma
1149 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1150 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1151 if (trace_hits_box_a0 > trace_hits_box_a1)
1157 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1162 // now it is a trace from 0 to end
1164 trace_hits_box_a0 = 0;
1165 trace_hits_box_a1 = 1;
1167 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1169 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1171 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1177 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1179 return trace_hits_box(start, end, thmi - ma, thma - mi);
1182 float SUB_NoImpactCheck()
1184 // zero hitcontents = this is not the real impact, but either the
1185 // mirror-impact of something hitting the projectile instead of the
1186 // projectile hitting the something, or a touchareagrid one. Neither of
1187 // these stop the projectile from moving, so...
1188 if(trace_dphitcontents == 0)
1190 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1191 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));
1194 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1196 if (other == world && self.size != '0 0 0')
1199 tic = self.velocity * sys_frametime;
1200 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1201 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1202 if (trace_fraction >= 1)
1204 dprint("Odd... did not hit...?\n");
1206 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1208 dprint("Detected and prevented the sky-grapple bug.\n");
1216 #define SUB_OwnerCheck() (other && (other == self.owner))
1218 void RemoveGrapplingHook(entity pl);
1219 void W_Crylink_Dequeue(entity e);
1220 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1222 if(SUB_OwnerCheck())
1224 if(SUB_NoImpactCheck())
1226 if(self.classname == "nade")
1227 return false; // no checks here
1228 else if(self.classname == "grapplinghook")
1229 RemoveGrapplingHook(self.realowner);
1230 else if(self.classname == "spike")
1232 W_Crylink_Dequeue(self);
1239 if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1240 UpdateCSQCProjectile(self);
1243 #define PROJECTILE_TOUCH if(WarpZone_Projectile_Touch()) return
1245 #define ITEM_TOUCH_NEEDKILL() (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
1246 #define ITEM_DAMAGE_NEEDKILL(dt) (((dt) == DEATH_HURTTRIGGER) || ((dt) == DEATH_SLIME) || ((dt) == DEATH_LAVA) || ((dt) == DEATH_SWAMP))
1248 void URI_Get_Callback(float id, float status, string data)
1250 if(url_URI_Get_Callback(id, status, data))
1254 else if (id == URI_GET_DISCARD)
1258 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1261 Curl_URI_Get_Callback(id, status, data);
1263 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1266 OnlineBanList_URI_Get_Callback(id, status, data);
1270 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1274 string uid2name(string myuid) {
1276 s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1278 // FIXME remove this later after 0.6 release
1279 // convert old style broken records to correct style
1282 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1285 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1286 db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1291 s = "^1Unregistered Player";
1295 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1298 vector start, org, delta, end, enddown, mstart;
1301 m = e.dphitcontentsmask;
1302 e.dphitcontentsmask = goodcontents | badcontents;
1305 delta = world.maxs - world.mins;
1309 for (i = 0; i < attempts; ++i)
1311 start_x = org.x + random() * delta.x;
1312 start_y = org.y + random() * delta.y;
1313 start_z = org.z + random() * delta.z;
1315 // rule 1: start inside world bounds, and outside
1316 // solid, and don't start from somewhere where you can
1317 // fall down to evil
1318 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1319 if (trace_fraction >= 1)
1321 if (trace_startsolid)
1323 if (trace_dphitcontents & badcontents)
1325 if (trace_dphitq3surfaceflags & badsurfaceflags)
1328 // rule 2: if we are too high, lower the point
1329 if (trace_fraction * delta.z > maxaboveground)
1330 start = trace_endpos + '0 0 1' * maxaboveground;
1331 enddown = trace_endpos;
1333 // rule 3: make sure we aren't outside the map. This only works
1334 // for somewhat well formed maps. A good rule of thumb is that
1335 // the map should have a convex outside hull.
1336 // these can be traceLINES as we already verified the starting box
1337 mstart = start + 0.5 * (e.mins + e.maxs);
1338 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1339 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1341 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1342 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1344 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1345 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1347 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1348 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1350 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1351 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1354 // rule 4: we must "see" some spawnpoint or item
1355 for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1356 if(checkpvs(mstart, sp))
1357 if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1361 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1362 if(checkpvs(mstart, sp))
1363 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1369 // find a random vector to "look at"
1370 end_x = org.x + random() * delta.x;
1371 end_y = org.y + random() * delta.y;
1372 end_z = org.z + random() * delta.z;
1373 end = start + normalize(end - start) * vlen(delta);
1375 // rule 4: start TO end must not be too short
1376 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1377 if (trace_startsolid)
1379 if (trace_fraction < minviewdistance / vlen(delta))
1382 // rule 5: don't want to look at sky
1383 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1386 // rule 6: we must not end up in trigger_hurt
1387 if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1393 e.dphitcontentsmask = m;
1397 setorigin(e, start);
1398 e.angles = vectoangles(end - start);
1399 dprint("Needed ", ftos(i + 1), " attempts\n");
1406 void write_recordmarker(entity pl, float tstart, float dt)
1408 GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1410 // also write a marker into demo files for demotc-race-record-extractor to find
1413 strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1414 " ", ftos(tstart), " ", ftos(dt), "\n"));
1417 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1430 if(allowcenter) // 2: allow center handedness
1443 if(allowcenter) // 2: allow center handedness
1459 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1464 if (autocvar_g_shootfromeye)
1468 if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1469 else { vecs_y = 0; vecs.z -= 2; }
1477 else if (autocvar_g_shootfromcenter)
1482 else if ((s = autocvar_g_shootfromfixedorigin) != "")
1492 else if (autocvar_g_shootfromclient)
1494 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1499 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1501 return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1505 void attach_sameorigin(entity e, entity to, string tag)
1507 vector org, t_forward, t_left, t_up, e_forward, e_up;
1510 org = e.origin - gettaginfo(to, gettagindex(to, tag));
1511 tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1512 t_forward = v_forward * tagscale;
1513 t_left = v_right * -tagscale;
1514 t_up = v_up * tagscale;
1516 e.origin_x = org * t_forward;
1517 e.origin_y = org * t_left;
1518 e.origin_z = org * t_up;
1520 // current forward and up directions
1521 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1522 e.angles = AnglesTransform_FromVAngles(e.angles);
1524 e.angles = AnglesTransform_FromAngles(e.angles);
1525 fixedmakevectors(e.angles);
1527 // untransform forward, up!
1528 e_forward_x = v_forward * t_forward;
1529 e_forward_y = v_forward * t_left;
1530 e_forward_z = v_forward * t_up;
1531 e_up_x = v_up * t_forward;
1532 e_up_y = v_up * t_left;
1533 e_up_z = v_up * t_up;
1535 e.angles = fixedvectoangles2(e_forward, e_up);
1536 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1537 e.angles = AnglesTransform_ToVAngles(e.angles);
1539 e.angles = AnglesTransform_ToAngles(e.angles);
1541 setattachment(e, to, tag);
1542 setorigin(e, e.origin);
1545 void detach_sameorigin(entity e)
1548 org = gettaginfo(e, 0);
1549 e.angles = fixedvectoangles2(v_forward, v_up);
1550 if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1551 e.angles = AnglesTransform_ToVAngles(e.angles);
1553 e.angles = AnglesTransform_ToAngles(e.angles);
1555 setattachment(e, world, "");
1556 setorigin(e, e.origin);
1559 void follow_sameorigin(entity e, entity to)
1561 e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1562 e.aiment = to; // make the hole follow bmodel
1563 e.punchangle = to.angles; // the original angles of bmodel
1564 e.view_ofs = e.origin - to.origin; // relative origin
1565 e.v_angle = e.angles - to.angles; // relative angles
1568 void unfollow_sameorigin(entity e)
1570 e.movetype = MOVETYPE_NONE;
1573 entity gettaginfo_relative_ent;
1574 vector gettaginfo_relative(entity e, float tag)
1576 if (!gettaginfo_relative_ent)
1578 gettaginfo_relative_ent = spawn();
1579 gettaginfo_relative_ent.effects = EF_NODRAW;
1581 gettaginfo_relative_ent.model = e.model;
1582 gettaginfo_relative_ent.modelindex = e.modelindex;
1583 gettaginfo_relative_ent.frame = e.frame;
1584 return gettaginfo(gettaginfo_relative_ent, tag);
1589 float modeleffect_SendEntity(entity to, float sf)
1592 WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1595 if(self.velocity != '0 0 0')
1597 if(self.angles != '0 0 0')
1599 if(self.avelocity != '0 0 0')
1602 WriteByte(MSG_ENTITY, f);
1603 WriteShort(MSG_ENTITY, self.modelindex);
1604 WriteByte(MSG_ENTITY, self.skin);
1605 WriteByte(MSG_ENTITY, self.frame);
1606 WriteCoord(MSG_ENTITY, self.origin.x);
1607 WriteCoord(MSG_ENTITY, self.origin.y);
1608 WriteCoord(MSG_ENTITY, self.origin.z);
1611 WriteCoord(MSG_ENTITY, self.velocity.x);
1612 WriteCoord(MSG_ENTITY, self.velocity.y);
1613 WriteCoord(MSG_ENTITY, self.velocity.z);
1617 WriteCoord(MSG_ENTITY, self.angles.x);
1618 WriteCoord(MSG_ENTITY, self.angles.y);
1619 WriteCoord(MSG_ENTITY, self.angles.z);
1623 WriteCoord(MSG_ENTITY, self.avelocity.x);
1624 WriteCoord(MSG_ENTITY, self.avelocity.y);
1625 WriteCoord(MSG_ENTITY, self.avelocity.z);
1627 WriteShort(MSG_ENTITY, self.scale * 256.0);
1628 WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1629 WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1630 WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1631 WriteByte(MSG_ENTITY, self.alpha * 255.0);
1636 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)
1641 e.classname = "modeleffect";
1649 e.teleport_time = t1;
1653 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1657 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1660 sz = max(e.scale, e.scale2);
1661 setsize(e, e.mins * sz, e.maxs * sz);
1662 Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1665 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1667 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1670 float randombit(float bits)
1672 if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1681 for(f = 1; f <= bits; f *= 2)
1690 r = (r - 1) / (n - 1);
1697 float randombits(float bits, float k, float error_return)
1701 while(k > 0 && bits != r)
1703 r += randombit(bits - r);
1712 void randombit_test(float bits, float iter)
1716 print(ftos(randombit(bits)), "\n");
1721 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1723 if(halflifedist > 0)
1724 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1725 else if(halflifedist < 0)
1726 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1735 #define cvar_string_normal builtin_cvar_string
1736 #define cvar_normal builtin_cvar
1738 string cvar_string_normal(string n)
1740 if (!(cvar_type(n) & 1))
1741 backtrace(strcat("Attempt to access undefined cvar: ", n));
1742 return builtin_cvar_string(n);
1745 float cvar_normal(string n)
1747 return stof(cvar_string_normal(n));
1750 #define cvar_set_normal builtin_cvar_set
1758 oself.think = SUB_Remove;
1759 oself.nextthink = time;
1765 Execute func() after time + fdelay.
1766 self when func is executed = self when defer is called
1768 void defer(float fdelay, void() func)
1775 e.think = defer_think;
1776 e.nextthink = time + fdelay;
1779 .string aiment_classname;
1780 .float aiment_deadflag;
1781 void SetMovetypeFollow(entity ent, entity e)
1783 // FIXME this may not be warpzone aware
1784 ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
1785 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.
1786 ent.aiment = e; // make the hole follow bmodel
1787 ent.punchangle = e.angles; // the original angles of bmodel
1788 ent.view_ofs = ent.origin - e.origin; // relative origin
1789 ent.v_angle = ent.angles - e.angles; // relative angles
1790 ent.aiment_classname = strzone(e.classname);
1791 ent.aiment_deadflag = e.deadflag;
1793 void UnsetMovetypeFollow(entity ent)
1795 ent.movetype = MOVETYPE_FLY;
1796 PROJECTILE_MAKETRIGGER(ent);
1799 float LostMovetypeFollow(entity ent)
1802 if(ent.movetype != MOVETYPE_FOLLOW)
1808 if(ent.aiment.classname != ent.aiment_classname)
1810 if(ent.aiment.deadflag != ent.aiment_deadflag)
1816 float isPushable(entity e)
1825 case "droppedweapon":
1826 case "keepawayball":
1827 case "nexball_basketball":
1828 case "nexball_football":
1830 case "bullet": // antilagged bullets can't hit this either
1833 if (e.projectiledeathtype)