]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/miscfunctions.qc
b26c42d0caff72b5636a8c1b716590176ec3bc83
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / miscfunctions.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "miscfunctions.qh"
5         #include "../dpdefs/progsdefs.qh"
6     #include "../dpdefs/dpextensions.qh"
7     #include "sys-post.qh"
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"
20     #include "t_items.qh"
21     #include "autocvars.qh"
22     #include "constants.qh"
23     #include "defs.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"
31     #include "ipban.qh"
32 #endif
33
34 void crosshair_trace(entity pl)
35 {
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));
37 }
38 void crosshair_trace_plusvisibletriggers(entity pl)
39 {
40         entity first;
41         entity e;
42         first = findchainfloat(solid, SOLID_TRIGGER);
43
44         for (e = first; e; e = e.chain)
45                 if (e.model != "")
46                         e.solid = SOLID_BSP;
47
48         crosshair_trace(pl);
49
50         for (e = first; e; e = e.chain)
51                 e.solid = SOLID_TRIGGER;
52 }
53 void WarpZone_crosshair_trace(entity pl)
54 {
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));
56 }
57
58
59 string admin_name(void)
60 {
61         if(autocvar_sv_adminnick != "")
62                 return autocvar_sv_adminnick;
63         else
64                 return "SERVER ADMIN";
65 }
66
67 void DistributeEvenly_Init(float amount, float totalweight)
68 {
69     if (DistributeEvenly_amount)
70     {
71         dprint("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
72         dprint(ftos(DistributeEvenly_totalweight), " left!)\n");
73     }
74     if (totalweight == 0)
75         DistributeEvenly_amount = 0;
76     else
77         DistributeEvenly_amount = amount;
78     DistributeEvenly_totalweight = totalweight;
79 }
80 float DistributeEvenly_Get(float weight)
81 {
82     float f;
83     if (weight <= 0)
84         return 0;
85     f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
86     DistributeEvenly_totalweight -= weight;
87     DistributeEvenly_amount -= f;
88     return f;
89 }
90 float DistributeEvenly_GetRandomized(float weight)
91 {
92     float f;
93     if (weight <= 0)
94         return 0;
95     f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
96     DistributeEvenly_totalweight -= weight;
97     DistributeEvenly_amount -= f;
98     return f;
99 }
100
101
102 void GameLogEcho(string s)
103 {
104     string fn;
105     float matches;
106
107     if (autocvar_sv_eventlog_files)
108     {
109         if (!logfile_open)
110         {
111             logfile_open = true;
112             matches = autocvar_sv_eventlog_files_counter + 1;
113             cvar_set("sv_eventlog_files_counter", ftos(matches));
114             fn = ftos(matches);
115             if (strlen(fn) < 8)
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");
120         }
121         if (logfile >= 0)
122         {
123             if (autocvar_sv_eventlog_files_timestamps)
124                 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
125             else
126                 fputs(logfile, strcat(s, "\n"));
127         }
128     }
129     if (autocvar_sv_eventlog_console)
130     {
131         print(s, "\n");
132     }
133 }
134
135 void GameLogInit()
136 {
137     logfile_open = 0;
138     // will be opened later
139 }
140
141 void GameLogClose()
142 {
143     if (logfile_open && logfile >= 0)
144     {
145         fclose(logfile);
146         logfile = -1;
147     }
148 }
149
150 entity findnearest(vector point, .string field, string value, vector axismod)
151 {
152     entity localhead;
153     float i;
154     float j;
155     float len;
156     vector dist;
157
158     float num_nearest;
159     num_nearest = 0;
160
161     localhead = find(world, field, value);
162     while (localhead)
163     {
164         if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
165             dist = localhead.oldorigin;
166         else
167             dist = localhead.origin;
168         dist = dist - point;
169         dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
170         len = vlen(dist);
171
172         for (i = 0; i < num_nearest; ++i)
173         {
174             if (len < nearest_length[i])
175                 break;
176         }
177
178         // now i tells us where to insert at
179         //   INSERTION SORT! YOU'VE SEEN IT! RUN!
180         if (i < NUM_NEAREST_ENTITIES)
181         {
182             for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
183             {
184                 nearest_length[j + 1] = nearest_length[j];
185                 nearest_entity[j + 1] = nearest_entity[j];
186             }
187             nearest_length[i] = len;
188             nearest_entity[i] = localhead;
189             if (num_nearest < NUM_NEAREST_ENTITIES)
190                 num_nearest = num_nearest + 1;
191         }
192
193         localhead = find(localhead, field, value);
194     }
195
196     // now use the first one from our list that we can see
197     for (i = 0; i < num_nearest; ++i)
198     {
199         traceline(point, nearest_entity[i].origin, true, world);
200         if (trace_fraction == 1)
201         {
202             if (i != 0)
203             {
204                 dprint("Nearest point (");
205                 dprint(nearest_entity[0].netname);
206                 dprint(") is not visible, using a visible one.\n");
207             }
208             return nearest_entity[i];
209         }
210     }
211
212     if (num_nearest == 0)
213         return world;
214
215     dprint("Not seeing any location point, using nearest as fallback.\n");
216     /* DEBUGGING CODE:
217     dprint("Candidates were: ");
218     for(j = 0; j < num_nearest; ++j)
219     {
220         if(j != 0)
221                 dprint(", ");
222         dprint(nearest_entity[j].netname);
223     }
224     dprint("\n");
225     */
226
227     return nearest_entity[0];
228 }
229
230 void spawnfunc_target_location()
231 {
232     self.classname = "target_location";
233     // location name in netname
234     // eventually support: count, teamgame selectors, line of sight?
235 }
236
237 void spawnfunc_info_location()
238 {
239     self.classname = "target_location";
240     self.message = self.netname;
241 }
242
243 string NearestLocation(vector p)
244 {
245     entity loc;
246     string ret;
247     ret = "somewhere";
248     loc = findnearest(p, classname, "target_location", '1 1 1');
249     if (loc)
250     {
251         ret = loc.message;
252     }
253     else
254     {
255         loc = findnearest(p, target, "###item###", '1 1 4');
256         if (loc)
257             ret = loc.netname;
258     }
259     return ret;
260 }
261
262 string formatmessage(string msg)
263 {
264         float p, p1, p2;
265         float n;
266         vector cursor;
267         entity cursor_ent;
268         string escape;
269         string replacement;
270         p = 0;
271         n = 7;
272
273         WarpZone_crosshair_trace(self);
274         cursor = trace_endpos;
275         cursor_ent = trace_ent;
276
277         while (1) {
278                 if (n < 1)
279                         break; // too many replacements
280
281                 n = n - 1;
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!
284
285                 if (p1 < 0)
286                         p1 = p2;
287
288                 if (p2 < 0)
289                         p2 = p1;
290
291                 p = min(p1, p2);
292
293                 if (p < 0)
294                         break;
295
296                 replacement = substring(msg, p, 2);
297                 escape = substring(msg, p + 1, 1);
298
299                 if (escape == "%")
300                         replacement = "%";
301                 else if (escape == "\\")
302                         replacement = "\\";
303                 else if (escape == "n")
304                         replacement = "\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") {
316                         float wep;
317                         wep = self.weapon;
318                         if (!wep)
319                                 wep = self.switchweapon;
320                         if (!wep)
321                                 wep = self.cnt;
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));
338
339                 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
340                 p = p + strlen(replacement);
341         }
342         return msg;
343 }
344
345 float boolean(float value) { // if value is 0 return false (0), otherwise return true (1)
346         return (value == 0) ? false : true;
347 }
348
349 /*
350 =============
351 GetCvars
352 =============
353 Called with:
354   0:  sends the request
355   >0: receives a cvar from name=argv(f) value=argv(f+1)
356 */
357 void GetCvars_handleString(string thisname, float f, .string field, string name)
358 {
359         if (f < 0)
360         {
361                 if (self.field)
362                         strunzone(self.field);
363                 self.field = string_null;
364         }
365         else if (f > 0)
366         {
367                 if (thisname == name)
368                 {
369                         if (self.field)
370                                 strunzone(self.field);
371                         self.field = strzone(argv(f + 1));
372                 }
373         }
374         else
375                 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
376 }
377 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
378 {
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)
382                 {
383                         string s;
384                         s = func(strcat1(self.field));
385                         if (s != self.field)
386                         {
387                                 strunzone(self.field);
388                                 self.field = strzone(s);
389                         }
390                 }
391 }
392 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
393 {
394         if (f < 0)
395         {
396         }
397         else if (f > 0)
398         {
399                 if (thisname == name)
400                         self.field = stof(argv(f + 1));
401         }
402         else
403                 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
404 }
405 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
406 {
407         if (f < 0)
408         {
409         }
410         else if (f > 0)
411         {
412                 if (thisname == name)
413                 {
414                         if(!self.field)
415                         {
416                                 self.field = stof(argv(f + 1));
417                                 if(!self.field)
418                                         self.field = -1;
419                         }
420                 }
421         }
422         else
423         {
424                 if(!self.field)
425                         stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
426         }
427 }
428 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
429 {
430         string o;
431         o = W_FixWeaponOrder_ForceComplete(wo);
432         if(self.weaponorder_byimpulse)
433         {
434                 strunzone(self.weaponorder_byimpulse);
435                 self.weaponorder_byimpulse = string_null;
436         }
437         self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
438         return o;
439 }
440 void GetCvars(float f)
441 {
442         string s = string_null;
443
444         if (f > 0)
445                 s = strcat1(argv(f));
446
447         get_cvars_f = f;
448         get_cvars_s = s;
449
450         MUTATOR_CALLHOOK(GetCvars);
451
452         Notification_GetCvars();
453
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");
478
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);
481
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");
487
488         // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
489         if (f > 0)
490         {
491                 if (s == "cl_weaponpriority")
492                         self.switchweapon = w_getbestweapon(self);
493                 if (s == "cl_allow_uidtracking")
494                         PlayerStats_GameReport_AddPlayer(self);
495         }
496 }
497
498 // decolorizes and team colors the player name when needed
499 string playername(entity p)
500 {
501     string t;
502     if (teamplay && !intermission_running && IS_PLAYER(p))
503     {
504         t = Team_ColorCode(p.team);
505         return strcat(t, strdecolorize(p.netname));
506     }
507     else
508         return p.netname;
509 }
510
511 vector randompos(vector m1, vector m2)
512 {
513     vector v;
514     m2 = m2 - m1;
515     v_x = m2_x * random() + m1_x;
516     v_y = m2_y * random() + m1_y;
517     v_z = m2_z * random() + m1_z;
518     return  v;
519 }
520
521
522
523 float sound_allowed(float dest, entity e)
524 {
525     // sounds from world may always pass
526     for(0;;)
527     {
528         if (e.classname == "body")
529             e = e.enemy;
530         else if (e.realowner && e.realowner != e)
531             e = e.realowner;
532         else if (e.owner && e.owner != e)
533             e = e.owner;
534         else
535             break;
536     }
537     // sounds to self may always pass
538     if (dest == MSG_ONE)
539         if (e == msg_entity)
540             return true;
541     // sounds by players can be removed
542     if (autocvar_bot_sound_monopoly)
543         if (IS_REAL_CLIENT(e))
544             return false;
545     // anything else may pass
546     return true;
547 }
548
549 #undef sound
550 void sound(entity e, float chan, string samp, float vol, float atten)
551 {
552     if (!sound_allowed(MSG_BROADCAST, e))
553         return;
554     sound7(e, chan, samp, vol, atten, 0, 0);
555 }
556
557 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
558 {
559     float entno, idx;
560
561     if (!sound_allowed(dest, e))
562         return;
563
564     entno = num_for_edict(e);
565     idx = precache_sound_index(samp);
566
567     float sflags;
568     sflags = 0;
569
570     atten = floor(atten * 64);
571     vol = floor(vol * 255);
572
573     if (vol != 255)
574         sflags |= SND_VOLUME;
575     if (atten != 64)
576         sflags |= SND_ATTENUATION;
577     if (entno >= 8192 || chan < 0 || chan > 7)
578         sflags |= SND_LARGEENTITY;
579     if (idx >= 256)
580         sflags |= SND_LARGESOUND;
581
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)
589     {
590         WriteShort(dest, entno);
591         WriteByte(dest, chan);
592     }
593     else
594     {
595         WriteShort(dest, entno * 8 + chan);
596     }
597     if (sflags & SND_LARGESOUND)
598         WriteShort(dest, idx);
599     else
600         WriteByte(dest, idx);
601
602     WriteCoord(dest, o.x);
603     WriteCoord(dest, o.y);
604     WriteCoord(dest, o.z);
605 }
606 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
607 {
608     vector o;
609
610     if (!sound_allowed(dest, e))
611         return;
612
613     o = e.origin + 0.5 * (e.mins + e.maxs);
614     soundtoat(dest, e, o, chan, samp, vol, atten);
615 }
616 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
617 {
618     soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, atten);
619 }
620 void stopsoundto(float dest, entity e, float chan)
621 {
622     float entno;
623
624     if (!sound_allowed(dest, e))
625         return;
626
627     entno = num_for_edict(e);
628
629     if (entno >= 8192 || chan < 0 || chan > 7)
630     {
631         float idx, sflags;
632         idx = precache_sound_index("misc/null.wav");
633         sflags = SND_LARGEENTITY;
634         if (idx >= 256)
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);
642         else
643             WriteByte(dest, idx);
644         WriteCoord(dest, e.origin.x);
645         WriteCoord(dest, e.origin.y);
646         WriteCoord(dest, e.origin.z);
647     }
648     else
649     {
650         WriteByte(dest, SVC_STOPSOUND);
651         WriteShort(dest, entno * 8 + chan);
652     }
653 }
654 void stopsound(entity e, float chan)
655 {
656     if (!sound_allowed(MSG_BROADCAST, e))
657         return;
658
659     stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
660     stopsoundto(MSG_ALL, e, chan); // in case of packet loss
661 }
662
663 void play2(entity e, string filename)
664 {
665     //stuffcmd(e, strcat("play2 ", filename, "\n"));
666     msg_entity = e;
667     soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
668 }
669
670 // 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 spamtime;
672 float spamsound(entity e, float chan, string samp, float vol, float atten)
673 {
674     if (!sound_allowed(MSG_BROADCAST, e))
675         return false;
676
677     if (time > e.spamtime)
678     {
679         e.spamtime = time;
680         sound(e, chan, samp, vol, atten);
681         return true;
682     }
683     return false;
684 }
685
686 void play2team(float t, string filename)
687 {
688     entity head;
689
690     if (autocvar_bot_sound_monopoly)
691         return;
692
693     FOR_EACH_REALPLAYER(head)
694     {
695         if (head.team == t)
696             play2(head, filename);
697     }
698 }
699
700 void play2all(string samp)
701 {
702     if (autocvar_bot_sound_monopoly)
703         return;
704
705     sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
706 }
707
708 void PrecachePlayerSounds(string f);
709 void precache_playermodel(string m)
710 {
711         float globhandle, i, n;
712         string f;
713
714         if(substring(m, -9,5) == "_lod1")
715                 return;
716         if(substring(m, -9,5) == "_lod2")
717                 return;
718         precache_model(m);
719         f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
720         if(fexists(f))
721                 precache_model(f);
722         f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
723         if(fexists(f))
724                 precache_model(f);
725
726         globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
727         if (globhandle < 0)
728                 return;
729         n = search_getsize(globhandle);
730         for (i = 0; i < n; ++i)
731         {
732                 //print(search_getfilename(globhandle, i), "\n");
733                 f = search_getfilename(globhandle, i);
734                 PrecachePlayerSounds(f);
735         }
736         search_end(globhandle);
737 }
738 void precache_all_playermodels(string pattern)
739 {
740         float globhandle, i, n;
741         string f;
742
743         globhandle = search_begin(pattern, true, false);
744         if (globhandle < 0)
745                 return;
746         n = search_getsize(globhandle);
747         for (i = 0; i < n; ++i)
748         {
749                 //print(search_getfilename(globhandle, i), "\n");
750                 f = search_getfilename(globhandle, i);
751                 precache_playermodel(f);
752         }
753         search_end(globhandle);
754 }
755
756 void precache()
757 {
758     // gamemode related things
759     precache_model ("models/misc/chatbubble.spr");
760         precache_model("models/ice/ice.md3");
761
762 #ifdef TTURRETS_ENABLED
763     if (autocvar_g_turrets)
764         turrets_precash();
765 #endif
766
767     // Precache all player models if desired
768     if (autocvar_sv_precacheplayermodels)
769     {
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");
776     }
777
778     if (autocvar_sv_defaultcharacter)
779     {
780         string s;
781         s = autocvar_sv_defaultplayermodel_red;
782         if (s != "")
783             precache_playermodel(s);
784         s = autocvar_sv_defaultplayermodel_blue;
785         if (s != "")
786             precache_playermodel(s);
787         s = autocvar_sv_defaultplayermodel_yellow;
788         if (s != "")
789             precache_playermodel(s);
790         s = autocvar_sv_defaultplayermodel_pink;
791         if (s != "")
792             precache_playermodel(s);
793         s = autocvar_sv_defaultplayermodel;
794         if (s != "")
795             precache_playermodel(s);
796     }
797
798     if (g_footsteps)
799     {
800         PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
801         PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
802     }
803
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");
824
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");
836
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)
844     {
845         precache_sound ("weapons/hook_fire.wav"); // hook
846         precache_sound ("weapons/hook_impact.wav"); // hook
847     }
848
849     precache_model("models/elaser.mdl");
850     precache_model("models/laser.mdl");
851     precache_model("models/ebomb.mdl");
852
853 #if 0
854     // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
855
856     if (!self.noise && self.music) // quake 3 uses the music field
857         self.noise = self.music;
858
859     // plays music for the level if there is any
860     if (self.noise)
861     {
862         precache_sound (self.noise);
863         ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
864     }
865 #endif
866
867 #include "precache-for-csqc.inc"
868 }
869
870
871 void make_safe_for_remove(entity e)
872 {
873     if (e.initialize_entity)
874     {
875         entity ent, prev = world;
876         for (ent = initialize_entity_first; ent; )
877         {
878             if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
879             {
880                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
881                 // skip it in linked list
882                 if (prev)
883                 {
884                     prev.initialize_entity_next = ent.initialize_entity_next;
885                     ent = prev.initialize_entity_next;
886                 }
887                 else
888                 {
889                     initialize_entity_first = ent.initialize_entity_next;
890                     ent = initialize_entity_first;
891                 }
892             }
893             else
894             {
895                 prev = ent;
896                 ent = ent.initialize_entity_next;
897             }
898         }
899     }
900 }
901
902 void objerror(string s)
903 {
904     make_safe_for_remove(self);
905     builtin_objerror(s);
906 }
907
908 .float remove_except_protected_forbidden;
909 void remove_except_protected(entity e)
910 {
911         if(e.remove_except_protected_forbidden)
912                 error("not allowed to remove this at this point");
913         builtin_remove(e);
914 }
915
916 void remove_unsafely(entity e)
917 {
918     if(e.classname == "spike")
919         error("Removing spikes is forbidden (crylink bug), please report");
920     builtin_remove(e);
921 }
922
923 void remove_safely(entity e)
924 {
925     make_safe_for_remove(e);
926     builtin_remove(e);
927 }
928
929 void InitializeEntity(entity e, void(void) func, float order)
930 {
931     entity prev, cur;
932
933     if (!e || e.initialize_entity)
934     {
935         // make a proxy initializer entity
936         entity e_old;
937         e_old = e;
938         e = spawn();
939         e.classname = "initialize_entity";
940         e.enemy = e_old;
941     }
942
943     e.initialize_entity = func;
944     e.initialize_entity_order = order;
945
946     cur = initialize_entity_first;
947     prev = world;
948     for(0;;)
949     {
950         if (!cur || cur.initialize_entity_order > order)
951         {
952             // insert between prev and cur
953             if (prev)
954                 prev.initialize_entity_next = e;
955             else
956                 initialize_entity_first = e;
957             e.initialize_entity_next = cur;
958             return;
959         }
960         prev = cur;
961         cur = cur.initialize_entity_next;
962     }
963 }
964 void InitializeEntitiesRun()
965 {
966     entity startoflist;
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)
971     {
972         self.remove_except_protected_forbidden = 1;
973     }
974     for (self = startoflist; self; )
975     {
976         entity e;
977         var void(void) func;
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")
985         {
986             entity e_old;
987             e_old = self.enemy;
988             builtin_remove(self);
989             self = e_old;
990         }
991         //dprint("Delayed initialization: ", self.classname, "\n");
992         if(func)
993             func();
994         else
995         {
996             eprint(self);
997             backtrace(strcat("Null function in: ", self.classname, "\n"));
998         }
999         self = e;
1000     }
1001     remove = remove_unsafely;
1002 }
1003
1004 .float uncustomizeentityforclient_set;
1005 .void(void) uncustomizeentityforclient;
1006 void UncustomizeEntitiesRun()
1007 {
1008     entity oldself;
1009     oldself = self;
1010     for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1011         self.uncustomizeentityforclient();
1012     self = oldself;
1013 }
1014 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1015 {
1016     e.customizeentityforclient = customizer;
1017     e.uncustomizeentityforclient = uncustomizer;
1018     e.uncustomizeentityforclient_set = !!uncustomizer;
1019 }
1020
1021 .float nottargeted;
1022 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1023
1024 void() SUB_Remove;
1025 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1026 {
1027     vector mi, ma;
1028
1029     if (e.classname == "")
1030         e.classname = "net_linked";
1031
1032     if (e.model == "" || self.modelindex == 0)
1033     {
1034         mi = e.mins;
1035         ma = e.maxs;
1036         setmodel(e, "null");
1037         setsize(e, mi, ma);
1038     }
1039
1040     e.SendEntity = sendfunc;
1041     e.SendFlags = 0xFFFFFF;
1042
1043     if (!docull)
1044         e.effects |= EF_NODEPTHTEST;
1045
1046     if (dt)
1047     {
1048         e.nextthink = time + dt;
1049         e.think = SUB_Remove;
1050     }
1051 }
1052
1053
1054 entity eliminatedPlayers;
1055 .float(entity) isEliminated;
1056 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1057 {
1058         float i, f, b;
1059         entity e;
1060         WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1061         WriteByte(MSG_ENTITY, sendflags);
1062
1063         if(sendflags & 1)
1064         {
1065                 for(i = 1; i <= maxclients; i += 8)
1066                 {
1067                         for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1068                         {
1069                                 if(eliminatedPlayers.isEliminated(e))
1070                                         f |= b;
1071                         }
1072                         WriteByte(MSG_ENTITY, f);
1073                 }
1074         }
1075
1076         return true;
1077 }
1078
1079 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1080 {
1081         if(eliminatedPlayers)
1082         {
1083                 backtrace("Can't spawn eliminatedPlayers again!");
1084                 return;
1085         }
1086         Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1087         eliminatedPlayers.isEliminated = isEliminated_func;
1088 }
1089
1090
1091 void adaptor_think2touch()
1092 {
1093     entity o;
1094     o = other;
1095     other = world;
1096     self.touch();
1097     other = o;
1098 }
1099
1100 void adaptor_think2use()
1101 {
1102     entity o, a;
1103     o = other;
1104     a = activator;
1105     activator = world;
1106     other = world;
1107     self.use();
1108     other = o;
1109     activator = a;
1110 }
1111
1112 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1113 {
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();
1117 }
1118
1119 // deferred dropping
1120 void DropToFloor_Handler()
1121 {
1122     builtin_droptofloor();
1123     self.dropped_origin = self.origin;
1124 }
1125
1126 void droptofloor()
1127 {
1128     InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1129 }
1130
1131
1132
1133 float trace_hits_box_a0, trace_hits_box_a1;
1134
1135 float trace_hits_box_1d(float end, float thmi, float thma)
1136 {
1137     if (end == 0)
1138     {
1139         // just check if x is in range
1140         if (0 < thmi)
1141             return false;
1142         if (0 > thma)
1143             return false;
1144     }
1145     else
1146     {
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)
1152             return false;
1153     }
1154     return true;
1155 }
1156
1157 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1158 {
1159     end -= start;
1160     thmi -= start;
1161     thma -= start;
1162     // now it is a trace from 0 to end
1163
1164     trace_hits_box_a0 = 0;
1165     trace_hits_box_a1 = 1;
1166
1167     if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1168         return false;
1169     if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1170         return false;
1171     if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1172         return false;
1173
1174     return true;
1175 }
1176
1177 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1178 {
1179     return trace_hits_box(start, end, thmi - ma, thma - mi);
1180 }
1181
1182 float SUB_NoImpactCheck()
1183 {
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)
1189         {
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));
1192                 checkclient();
1193         }
1194     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1195         return 1;
1196     if (other == world && self.size != '0 0 0')
1197     {
1198         vector tic;
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)
1203         {
1204             dprint("Odd... did not hit...?\n");
1205         }
1206         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1207         {
1208             dprint("Detected and prevented the sky-grapple bug.\n");
1209             return 1;
1210         }
1211     }
1212
1213     return 0;
1214 }
1215
1216 #define SUB_OwnerCheck() (other && (other == self.owner))
1217
1218 void RemoveGrapplingHook(entity pl);
1219 void W_Crylink_Dequeue(entity e);
1220 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1221 {
1222         if(SUB_OwnerCheck())
1223                 return true;
1224         if(SUB_NoImpactCheck())
1225         {
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")
1231                 {
1232                         W_Crylink_Dequeue(self);
1233                         remove(self);
1234                 }
1235                 else
1236                         remove(self);
1237                 return true;
1238         }
1239         if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1240                 UpdateCSQCProjectile(self);
1241         return false;
1242 }
1243 #define PROJECTILE_TOUCH if(WarpZone_Projectile_Touch()) return
1244
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))
1247
1248 void URI_Get_Callback(float id, float status, string data)
1249 {
1250         if(url_URI_Get_Callback(id, status, data))
1251         {
1252                 // handled
1253         }
1254         else if (id == URI_GET_DISCARD)
1255         {
1256                 // discard
1257         }
1258         else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1259         {
1260                 // sv_cmd curl
1261                 Curl_URI_Get_Callback(id, status, data);
1262         }
1263         else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1264         {
1265                 // online ban list
1266                 OnlineBanList_URI_Get_Callback(id, status, data);
1267         }
1268         else
1269         {
1270                 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1271         }
1272 }
1273
1274 string uid2name(string myuid) {
1275         string s;
1276         s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1277
1278         // FIXME remove this later after 0.6 release
1279         // convert old style broken records to correct style
1280         if(s == "")
1281         {
1282                 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1283                 if(s != "")
1284                 {
1285                         db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1286                         db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1287                 }
1288         }
1289
1290         if(s == "")
1291                 s = "^1Unregistered Player";
1292         return s;
1293 }
1294
1295 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1296 {
1297     float m, i;
1298     vector start, org, delta, end, enddown, mstart;
1299     entity sp;
1300
1301     m = e.dphitcontentsmask;
1302     e.dphitcontentsmask = goodcontents | badcontents;
1303
1304     org = world.mins;
1305     delta = world.maxs - world.mins;
1306
1307     start = end = org;
1308
1309     for (i = 0; i < attempts; ++i)
1310     {
1311         start_x = org.x + random() * delta.x;
1312         start_y = org.y + random() * delta.y;
1313         start_z = org.z + random() * delta.z;
1314
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)
1320             continue;
1321         if (trace_startsolid)
1322             continue;
1323         if (trace_dphitcontents & badcontents)
1324             continue;
1325         if (trace_dphitq3surfaceflags & badsurfaceflags)
1326             continue;
1327
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;
1332
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")
1340             continue;
1341         traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1342         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1343             continue;
1344         traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1345         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1346             continue;
1347         traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1348         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1349             continue;
1350         traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1351         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1352             continue;
1353
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)
1358                                 break;
1359         if(!sp)
1360         {
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)
1364                                         break;
1365                 if(!sp)
1366                         continue;
1367         }
1368
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);
1374
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)
1378             continue;
1379         if (trace_fraction < minviewdistance / vlen(delta))
1380             continue;
1381
1382         // rule 5: don't want to look at sky
1383         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1384             continue;
1385
1386         // rule 6: we must not end up in trigger_hurt
1387         if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1388             continue;
1389
1390         break;
1391     }
1392
1393     e.dphitcontentsmask = m;
1394
1395     if (i < attempts)
1396     {
1397         setorigin(e, start);
1398         e.angles = vectoangles(end - start);
1399         dprint("Needed ", ftos(i + 1), " attempts\n");
1400         return true;
1401     }
1402     else
1403         return false;
1404 }
1405
1406 void write_recordmarker(entity pl, float tstart, float dt)
1407 {
1408     GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1409
1410     // also write a marker into demo files for demotc-race-record-extractor to find
1411     stuffcmd(pl,
1412              strcat(
1413                  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1414                  " ", ftos(tstart), " ", ftos(dt), "\n"));
1415 }
1416
1417 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1418 {
1419         switch(algn)
1420         {
1421                 default:
1422                 case 3: // right
1423                         break;
1424
1425                 case 4: // left
1426                         vecs_y = -vecs.y;
1427                         break;
1428
1429                 case 1:
1430                         if(allowcenter) // 2: allow center handedness
1431                         {
1432                                 // center
1433                                 vecs_y = 0;
1434                                 vecs.z -= 2;
1435                         }
1436                         else
1437                         {
1438                                 // right
1439                         }
1440                         break;
1441
1442                 case 2:
1443                         if(allowcenter) // 2: allow center handedness
1444                         {
1445                                 // center
1446                                 vecs_y = 0;
1447                                 vecs.z -= 2;
1448                         }
1449                         else
1450                         {
1451                                 // left
1452                                 vecs_y = -vecs.y;
1453                         }
1454                         break;
1455         }
1456         return vecs;
1457 }
1458
1459 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1460 {
1461         string s;
1462         vector v;
1463
1464         if (autocvar_g_shootfromeye)
1465         {
1466                 if (visual)
1467                 {
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; }
1470                 }
1471                 else
1472                 {
1473                         vecs_y = 0;
1474                         vecs_z = 0;
1475                 }
1476         }
1477         else if (autocvar_g_shootfromcenter)
1478         {
1479                 vecs_y = 0;
1480                 vecs.z -= 2;
1481         }
1482         else if ((s = autocvar_g_shootfromfixedorigin) != "")
1483         {
1484                 v = stov(s);
1485                 if (y_is_right)
1486                         v_y = -v.y;
1487                 if (v.x != 0)
1488                         vecs_x = v.x;
1489                 vecs_y = v.y;
1490                 vecs_z = v.z;
1491         }
1492         else if (autocvar_g_shootfromclient)
1493         {
1494                 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1495         }
1496         return vecs;
1497 }
1498
1499 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1500 {
1501         return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1502 }
1503
1504
1505 void attach_sameorigin(entity e, entity to, string tag)
1506 {
1507     vector org, t_forward, t_left, t_up, e_forward, e_up;
1508     float tagscale;
1509
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;
1515
1516     e.origin_x = org * t_forward;
1517     e.origin_y = org * t_left;
1518     e.origin_z = org * t_up;
1519
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);
1523         else
1524                 e.angles = AnglesTransform_FromAngles(e.angles);
1525     fixedmakevectors(e.angles);
1526
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;
1534
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);
1538         else
1539                 e.angles = AnglesTransform_ToAngles(e.angles);
1540
1541     setattachment(e, to, tag);
1542     setorigin(e, e.origin);
1543 }
1544
1545 void detach_sameorigin(entity e)
1546 {
1547     vector org;
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);
1552         else
1553                 e.angles = AnglesTransform_ToAngles(e.angles);
1554     setorigin(e, org);
1555     setattachment(e, world, "");
1556     setorigin(e, e.origin);
1557 }
1558
1559 void follow_sameorigin(entity e, entity to)
1560 {
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
1566 }
1567
1568 void unfollow_sameorigin(entity e)
1569 {
1570     e.movetype = MOVETYPE_NONE;
1571 }
1572
1573 entity gettaginfo_relative_ent;
1574 vector gettaginfo_relative(entity e, float tag)
1575 {
1576     if (!gettaginfo_relative_ent)
1577     {
1578         gettaginfo_relative_ent = spawn();
1579         gettaginfo_relative_ent.effects = EF_NODRAW;
1580     }
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);
1585 }
1586
1587 .float scale2;
1588
1589 float modeleffect_SendEntity(entity to, float sf)
1590 {
1591         float f;
1592         WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1593
1594         f = 0;
1595         if(self.velocity != '0 0 0')
1596                 f |= 1;
1597         if(self.angles != '0 0 0')
1598                 f |= 2;
1599         if(self.avelocity != '0 0 0')
1600                 f |= 4;
1601
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);
1609         if(f & 1)
1610         {
1611                 WriteCoord(MSG_ENTITY, self.velocity.x);
1612                 WriteCoord(MSG_ENTITY, self.velocity.y);
1613                 WriteCoord(MSG_ENTITY, self.velocity.z);
1614         }
1615         if(f & 2)
1616         {
1617                 WriteCoord(MSG_ENTITY, self.angles.x);
1618                 WriteCoord(MSG_ENTITY, self.angles.y);
1619                 WriteCoord(MSG_ENTITY, self.angles.z);
1620         }
1621         if(f & 4)
1622         {
1623                 WriteCoord(MSG_ENTITY, self.avelocity.x);
1624                 WriteCoord(MSG_ENTITY, self.avelocity.y);
1625                 WriteCoord(MSG_ENTITY, self.avelocity.z);
1626         }
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);
1632
1633         return true;
1634 }
1635
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)
1637 {
1638         entity e;
1639         float sz;
1640         e = spawn();
1641         e.classname = "modeleffect";
1642         setmodel(e, m);
1643         e.frame = f;
1644         setorigin(e, o);
1645         e.velocity = v;
1646         e.angles = ang;
1647         e.avelocity = angv;
1648         e.alpha = a;
1649         e.teleport_time = t1;
1650         e.fade_time = t2;
1651         e.skin = s;
1652         if(s0 >= 0)
1653                 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1654         else
1655                 e.scale = -s0;
1656         if(s2 >= 0)
1657                 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1658         else
1659                 e.scale2 = -s2;
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);
1663 }
1664
1665 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1666 {
1667         return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1668 }
1669
1670 float randombit(float bits)
1671 {
1672         if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1673                 return bits;
1674
1675         float n, f, b, r;
1676
1677         r = random();
1678         b = 0;
1679         n = 0;
1680
1681         for(f = 1; f <= bits; f *= 2)
1682         {
1683                 if(bits & f)
1684                 {
1685                         ++n;
1686                         r *= n;
1687                         if(r <= 1)
1688                                 b = f;
1689                         else
1690                                 r = (r - 1) / (n - 1);
1691                 }
1692         }
1693
1694         return b;
1695 }
1696
1697 float randombits(float bits, float k, float error_return)
1698 {
1699         float r;
1700         r = 0;
1701         while(k > 0 && bits != r)
1702         {
1703                 r += randombit(bits - r);
1704                 --k;
1705         }
1706         if(error_return)
1707                 if(k > 0)
1708                         return -1; // all
1709         return r;
1710 }
1711
1712 void randombit_test(float bits, float iter)
1713 {
1714         while(iter > 0)
1715         {
1716                 print(ftos(randombit(bits)), "\n");
1717                 --iter;
1718         }
1719 }
1720
1721 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1722 {
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);
1727         else
1728                 return 1;
1729 }
1730
1731
1732
1733
1734 #ifdef RELEASE
1735 #define cvar_string_normal builtin_cvar_string
1736 #define cvar_normal builtin_cvar
1737 #else
1738 string cvar_string_normal(string n)
1739 {
1740         if (!(cvar_type(n) & 1))
1741                 backtrace(strcat("Attempt to access undefined cvar: ", n));
1742         return builtin_cvar_string(n);
1743 }
1744
1745 float cvar_normal(string n)
1746 {
1747         return stof(cvar_string_normal(n));
1748 }
1749 #endif
1750 #define cvar_set_normal builtin_cvar_set
1751
1752 void defer_think()
1753 {
1754     entity oself;
1755
1756     oself           = self;
1757     self            = self.owner;
1758     oself.think     = SUB_Remove;
1759     oself.nextthink = time;
1760
1761     oself.use();
1762 }
1763
1764 /*
1765     Execute func() after time + fdelay.
1766     self when func is executed = self when defer is called
1767 */
1768 void defer(float fdelay, void() func)
1769 {
1770     entity e;
1771
1772     e           = spawn();
1773     e.owner     = self;
1774     e.use       = func;
1775     e.think     = defer_think;
1776     e.nextthink = time + fdelay;
1777 }
1778
1779 .string aiment_classname;
1780 .float aiment_deadflag;
1781 void SetMovetypeFollow(entity ent, entity e)
1782 {
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;
1792 }
1793 void UnsetMovetypeFollow(entity ent)
1794 {
1795         ent.movetype = MOVETYPE_FLY;
1796         PROJECTILE_MAKETRIGGER(ent);
1797         ent.aiment = world;
1798 }
1799 float LostMovetypeFollow(entity ent)
1800 {
1801 /*
1802         if(ent.movetype != MOVETYPE_FOLLOW)
1803                 if(ent.aiment)
1804                         error("???");
1805 */
1806         if(ent.aiment)
1807         {
1808                 if(ent.aiment.classname != ent.aiment_classname)
1809                         return 1;
1810                 if(ent.aiment.deadflag != ent.aiment_deadflag)
1811                         return 1;
1812         }
1813         return 0;
1814 }
1815
1816 float isPushable(entity e)
1817 {
1818         if(e.iscreature)
1819                 return true;
1820         if(e.pushable)
1821                 return true;
1822         switch(e.classname)
1823         {
1824                 case "body":
1825                 case "droppedweapon":
1826                 case "keepawayball":
1827                 case "nexball_basketball":
1828                 case "nexball_football":
1829                         return true;
1830                 case "bullet": // antilagged bullets can't hit this either
1831                         return false;
1832         }
1833         if (e.projectiledeathtype)
1834                 return true;
1835         return false;
1836 }