]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/miscfunctions.qc
Make most server includes order insensitive
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / miscfunctions.qc
1 #include "miscfunctions.qh"
2 #include "_.qh"
3
4 #include "antilag.qh"
5 #include "command/common.qh"
6 #include "constants.qh"
7 #include "ipban.qh"
8 #include "mutators/mutators_include.qh"
9 #include "tturrets/include/turrets_early.qh"
10 #include "t_items.qh"
11 #include "weapons/accuracy.qh"
12 #include "weapons/csqcprojectile.qh"
13 #include "weapons/selection.qh"
14 #include "../common/command/generic.qh"
15 #include "../common/constants.qh"
16 #include "../common/deathtypes.qh"
17 #include "../common/mapinfo.qh"
18 #include "../common/notifications.qh"
19 #include "../common/playerstats.qh"
20 #include "../common/teams.qh"
21 #include "../common/urllib.qh"
22 #include "../common/util.qh"
23 #include "../common/weapons/weapons.qh"
24 #include "../csqcmodellib/sv_model.qh"
25 #include "../warpzonelib/anglestransform.qh"
26 #include "../warpzonelib/server.qh"
27
28 void crosshair_trace(entity pl)
29 {
30         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));
31 }
32 void crosshair_trace_plusvisibletriggers(entity pl)
33 {
34         entity first;
35         entity e;
36         first = findchainfloat(solid, SOLID_TRIGGER);
37
38         for (e = first; e; e = e.chain)
39                 if (e.model != "")
40                         e.solid = SOLID_BSP;
41
42         crosshair_trace(pl);
43
44         for (e = first; e; e = e.chain)
45                 e.solid = SOLID_TRIGGER;
46 }
47 void WarpZone_crosshair_trace(entity pl)
48 {
49         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));
50 }
51
52
53 string admin_name(void)
54 {
55         if(autocvar_sv_adminnick != "")
56                 return autocvar_sv_adminnick;
57         else
58                 return "SERVER ADMIN";
59 }
60
61 void DistributeEvenly_Init(float amount, float totalweight)
62 {
63     if (DistributeEvenly_amount)
64     {
65         dprint("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
66         dprint(ftos(DistributeEvenly_totalweight), " left!)\n");
67     }
68     if (totalweight == 0)
69         DistributeEvenly_amount = 0;
70     else
71         DistributeEvenly_amount = amount;
72     DistributeEvenly_totalweight = totalweight;
73 }
74 float DistributeEvenly_Get(float weight)
75 {
76     float f;
77     if (weight <= 0)
78         return 0;
79     f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
80     DistributeEvenly_totalweight -= weight;
81     DistributeEvenly_amount -= f;
82     return f;
83 }
84 float DistributeEvenly_GetRandomized(float weight)
85 {
86     float f;
87     if (weight <= 0)
88         return 0;
89     f = floor(random() + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
90     DistributeEvenly_totalweight -= weight;
91     DistributeEvenly_amount -= f;
92     return f;
93 }
94
95
96 void GameLogEcho(string s)
97 {
98     string fn;
99     int matches;
100
101     if (autocvar_sv_eventlog_files)
102     {
103         if (!logfile_open)
104         {
105             logfile_open = true;
106             matches = autocvar_sv_eventlog_files_counter + 1;
107             cvar_set("sv_eventlog_files_counter", itos(matches));
108             fn = ftos(matches);
109             if (strlen(fn) < 8)
110                 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
111             fn = strcat(autocvar_sv_eventlog_files_nameprefix, fn, autocvar_sv_eventlog_files_namesuffix);
112             logfile = fopen(fn, FILE_APPEND);
113             fputs(logfile, ":logversion:3\n");
114         }
115         if (logfile >= 0)
116         {
117             if (autocvar_sv_eventlog_files_timestamps)
118                 fputs(logfile, strcat(":time:", strftime(true, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
119             else
120                 fputs(logfile, strcat(s, "\n"));
121         }
122     }
123     if (autocvar_sv_eventlog_console)
124     {
125         print(s, "\n");
126     }
127 }
128
129 void GameLogInit()
130 {
131     logfile_open = 0;
132     // will be opened later
133 }
134
135 void GameLogClose()
136 {
137     if (logfile_open && logfile >= 0)
138     {
139         fclose(logfile);
140         logfile = -1;
141     }
142 }
143
144 entity findnearest(vector point, .string field, string value, vector axismod)
145 {
146     entity localhead;
147     float i;
148     float j;
149     float len;
150     vector dist;
151
152     float num_nearest;
153     num_nearest = 0;
154
155     localhead = find(world, field, value);
156     while (localhead)
157     {
158         if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
159             dist = localhead.oldorigin;
160         else
161             dist = localhead.origin;
162         dist = dist - point;
163         dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
164         len = vlen(dist);
165
166         for (i = 0; i < num_nearest; ++i)
167         {
168             if (len < nearest_length[i])
169                 break;
170         }
171
172         // now i tells us where to insert at
173         //   INSERTION SORT! YOU'VE SEEN IT! RUN!
174         if (i < NUM_NEAREST_ENTITIES)
175         {
176             for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
177             {
178                 nearest_length[j + 1] = nearest_length[j];
179                 nearest_entity[j + 1] = nearest_entity[j];
180             }
181             nearest_length[i] = len;
182             nearest_entity[i] = localhead;
183             if (num_nearest < NUM_NEAREST_ENTITIES)
184                 num_nearest = num_nearest + 1;
185         }
186
187         localhead = find(localhead, field, value);
188     }
189
190     // now use the first one from our list that we can see
191     for (i = 0; i < num_nearest; ++i)
192     {
193         traceline(point, nearest_entity[i].origin, true, world);
194         if (trace_fraction == 1)
195         {
196             if (i != 0)
197             {
198                 dprint("Nearest point (");
199                 dprint(nearest_entity[0].netname);
200                 dprint(") is not visible, using a visible one.\n");
201             }
202             return nearest_entity[i];
203         }
204     }
205
206     if (num_nearest == 0)
207         return world;
208
209     dprint("Not seeing any location point, using nearest as fallback.\n");
210     /* DEBUGGING CODE:
211     dprint("Candidates were: ");
212     for(j = 0; j < num_nearest; ++j)
213     {
214         if(j != 0)
215                 dprint(", ");
216         dprint(nearest_entity[j].netname);
217     }
218     dprint("\n");
219     */
220
221     return nearest_entity[0];
222 }
223
224 void spawnfunc_target_location()
225 {
226     self.classname = "target_location";
227     // location name in netname
228     // eventually support: count, teamgame selectors, line of sight?
229 }
230
231 void spawnfunc_info_location()
232 {
233     self.classname = "target_location";
234     self.message = self.netname;
235 }
236
237 string NearestLocation(vector p)
238 {
239     entity loc;
240     string ret;
241     ret = "somewhere";
242     loc = findnearest(p, classname, "target_location", '1 1 1');
243     if (loc)
244     {
245         ret = loc.message;
246     }
247     else
248     {
249         loc = findnearest(p, target, "###item###", '1 1 4');
250         if (loc)
251             ret = loc.netname;
252     }
253     return ret;
254 }
255
256 string formatmessage(string msg)
257 {
258         float p, p1, p2;
259         float n;
260         vector cursor;
261         entity cursor_ent;
262         string escape;
263         string replacement;
264         p = 0;
265         n = 7;
266
267         WarpZone_crosshair_trace(self);
268         cursor = trace_endpos;
269         cursor_ent = trace_ent;
270
271         while (1) {
272                 if (n < 1)
273                         break; // too many replacements
274
275                 n = n - 1;
276                 p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
277                 p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
278
279                 if (p1 < 0)
280                         p1 = p2;
281
282                 if (p2 < 0)
283                         p2 = p1;
284
285                 p = min(p1, p2);
286
287                 if (p < 0)
288                         break;
289
290                 replacement = substring(msg, p, 2);
291                 escape = substring(msg, p + 1, 1);
292
293                 if (escape == "%")
294                         replacement = "%";
295                 else if (escape == "\\")
296                         replacement = "\\";
297                 else if (escape == "n")
298                         replacement = "\n";
299                 else if (escape == "a")
300                         replacement = ftos(floor(self.armorvalue));
301                 else if (escape == "h")
302                         replacement = ftos(floor(self.health));
303                 else if (escape == "l")
304                         replacement = NearestLocation(self.origin);
305                 else if (escape == "y")
306                         replacement = NearestLocation(cursor);
307                 else if (escape == "d")
308                         replacement = NearestLocation(self.death_origin);
309                 else if (escape == "w") {
310                         float wep;
311                         wep = self.weapon;
312                         if (!wep)
313                                 wep = self.switchweapon;
314                         if (!wep)
315                                 wep = self.cnt;
316                         replacement = WEP_NAME(wep);
317                 } else if (escape == "W") {
318                         if (self.items & IT_SHELLS) replacement = "shells";
319                         else if (self.items & IT_NAILS) replacement = "bullets";
320                         else if (self.items & IT_ROCKETS) replacement = "rockets";
321                         else if (self.items & IT_CELLS) replacement = "cells";
322                         else if (self.items & IT_PLASMA) replacement = "plasma";
323                         else replacement = "batteries"; // ;)
324                 } else if (escape == "x") {
325                         replacement = cursor_ent.netname;
326                         if (replacement == "" || !cursor_ent)
327                                 replacement = "nothing";
328                 } else if (escape == "s")
329                         replacement = ftos(vlen(self.velocity - self.velocity.z * '0 0 1'));
330                 else if (escape == "S")
331                         replacement = ftos(vlen(self.velocity));
332
333                 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
334                 p = p + strlen(replacement);
335         }
336         return msg;
337 }
338
339 float boolean(float value) { // if value is 0 return false (0), otherwise return true (1)
340         return (value == 0) ? false : true;
341 }
342
343 /*
344 =============
345 GetCvars
346 =============
347 Called with:
348   0:  sends the request
349   >0: receives a cvar from name=argv(f) value=argv(f+1)
350 */
351 void GetCvars_handleString(string thisname, float f, .string field, string name)
352 {
353         if (f < 0)
354         {
355                 if (self.(field))
356                         strunzone(self.(field));
357                 self.(field) = string_null;
358         }
359         else if (f > 0)
360         {
361                 if (thisname == name)
362                 {
363                         if (self.(field))
364                                 strunzone(self.(field));
365                         self.(field) = strzone(argv(f + 1));
366                 }
367         }
368         else
369                 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
370 }
371 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
372 {
373         GetCvars_handleString(thisname, f, field, name);
374         if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
375                 if (thisname == name)
376                 {
377                         string s = func(strcat1(self.(field)));
378                         if (s != self.(field))
379                         {
380                                 strunzone(self.(field));
381                                 self.(field) = strzone(s);
382                         }
383                 }
384 }
385 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
386 {
387         if (f < 0)
388         {
389         }
390         else if (f > 0)
391         {
392                 if (thisname == name)
393                         self.(field) = stof(argv(f + 1));
394         }
395         else
396                 stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
397 }
398 void GetCvars_handleFloatOnce(string thisname, float f, .float field, string name)
399 {
400         if (f < 0)
401         {
402         }
403         else if (f > 0)
404         {
405                 if (thisname == name)
406                 {
407                         if (!self.(field))
408                         {
409                                 self.(field) = stof(argv(f + 1));
410                                 if (!self.(field))
411                                         self.(field) = -1;
412                         }
413                 }
414         }
415         else
416         {
417                 if (!self.(field))
418                         stuffcmd(self, strcat("cl_cmd sendcvar ", name, "\n"));
419         }
420 }
421 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(string wo)
422 {
423         string o;
424         o = W_FixWeaponOrder_ForceComplete(wo);
425         if(self.weaponorder_byimpulse)
426         {
427                 strunzone(self.weaponorder_byimpulse);
428                 self.weaponorder_byimpulse = string_null;
429         }
430         self.weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(o));
431         return o;
432 }
433 void GetCvars(float f)
434 {
435         string s = string_null;
436
437         if (f > 0)
438                 s = strcat1(argv(f));
439
440         get_cvars_f = f;
441         get_cvars_s = s;
442
443         MUTATOR_CALLHOOK(GetCvars);
444
445         Notification_GetCvars();
446
447         GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
448         GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
449         GetCvars_handleFloat(s, f, cvar_cl_jetpack_jump, "cl_jetpack_jump");
450         GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
451         GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
452         GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
453         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
454         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
455         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
456         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
457         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
458         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
459         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
460         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
461         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
462         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
463         GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
464         GetCvars_handleFloat(s, f, cvar_cl_weaponimpulsemode, "cl_weaponimpulsemode");
465         GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
466         GetCvars_handleFloat(s, f, cvar_cl_noantilag, "cl_noantilag");
467         GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
468         GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
469         GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_share, "cl_accuracy_data_share");
470         GetCvars_handleFloat(s, f, cvar_cl_accuracy_data_receive, "cl_accuracy_data_receive");
471
472         self.cvar_cl_accuracy_data_share = boolean(self.cvar_cl_accuracy_data_share);
473         self.cvar_cl_accuracy_data_receive = boolean(self.cvar_cl_accuracy_data_receive);
474
475         GetCvars_handleFloatOnce(s, f, cvar_cl_gunalign, "cl_gunalign");
476         GetCvars_handleFloat(s, f, cvar_cl_allow_uid2name, "cl_allow_uid2name");
477         GetCvars_handleFloat(s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
478         GetCvars_handleFloat(s, f, cvar_cl_movement_track_canjump, "cl_movement_track_canjump");
479         GetCvars_handleFloat(s, f, cvar_cl_newusekeysupported, "cl_newusekeysupported");
480
481         // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
482         if (f > 0)
483         {
484                 if (s == "cl_weaponpriority")
485                         self.switchweapon = w_getbestweapon(self);
486                 if (s == "cl_allow_uidtracking")
487                         PlayerStats_GameReport_AddPlayer(self);
488         }
489 }
490
491 // decolorizes and team colors the player name when needed
492 string playername(entity p)
493 {
494     string t;
495     if (teamplay && !intermission_running && IS_PLAYER(p))
496     {
497         t = Team_ColorCode(p.team);
498         return strcat(t, strdecolorize(p.netname));
499     }
500     else
501         return p.netname;
502 }
503
504 vector randompos(vector m1, vector m2)
505 {
506     vector v;
507     m2 = m2 - m1;
508     v.x = m2_x * random() + m1_x;
509     v.y = m2_y * random() + m1_y;
510     v.z = m2_z * random() + m1_z;
511     return  v;
512 }
513
514 float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done?
515 {
516         int i = weaponinfo.weapon;
517         int d = 0;
518
519         if (!i)
520                 return 0;
521
522         if (g_lms || g_ca || allguns)
523         {
524                 if(weaponinfo.spawnflags & WEP_FLAG_NORMAL)
525                         d = true;
526                 else
527                         d = false;
528         }
529         else if (g_cts)
530                 d = (i == WEP_SHOTGUN);
531         else if (g_nexball)
532                 d = 0; // weapon is set a few lines later
533         else
534                 d = !(!weaponinfo.weaponstart);
535
536         if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
537                 d |= (i == WEP_HOOK);
538         if(!g_cts && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
539                 d = 0;
540
541         float t = weaponinfo.weaponstartoverride;
542
543         //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n"));
544
545         // bit order in t:
546         // 1: want or not
547         // 2: is default?
548         // 4: is set by default?
549         if(t < 0)
550                 t = 4 | (3 * d);
551         else
552                 t |= (2 * d);
553
554         return t;
555 }
556
557 void readplayerstartcvars()
558 {
559         entity e;
560         float i, j, t;
561         string s;
562
563         // initialize starting values for players
564         start_weapons = '0 0 0';
565         start_weapons_default = '0 0 0';
566         start_weapons_defaultmask = '0 0 0';
567         start_items = 0;
568         start_ammo_shells = 0;
569         start_ammo_nails = 0;
570         start_ammo_rockets = 0;
571         start_ammo_cells = 0;
572         start_ammo_plasma = 0;
573         start_health = cvar("g_balance_health_start");
574         start_armorvalue = cvar("g_balance_armor_start");
575
576         g_weaponarena = 0;
577         g_weaponarena_weapons = '0 0 0';
578
579         s = cvar_string("g_weaponarena");
580         if (s == "0" || s == "")
581         {
582                 if(g_ca)
583                         s = "most";
584         }
585
586         if (s == "0" || s == "")
587         {
588                 // no arena
589         }
590         else if (s == "off")
591         {
592                 // forcibly turn off weaponarena
593         }
594         else if (s == "all" || s == "1")
595         {
596                 g_weaponarena = 1;
597                 g_weaponarena_list = "All Weapons";
598                 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
599                 {
600                         e = get_weaponinfo(j);
601                         if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
602                                 g_weaponarena_weapons |= WepSet_FromWeapon(j);
603                 }
604         }
605         else if (s == "most")
606         {
607                 g_weaponarena = 1;
608                 g_weaponarena_list = "Most Weapons";
609                 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
610                 {
611                         e = get_weaponinfo(j);
612                         if (!(e.spawnflags & WEP_FLAG_MUTATORBLOCKED))
613                                 if (e.spawnflags & WEP_FLAG_NORMAL)
614                                         g_weaponarena_weapons |= WepSet_FromWeapon(j);
615                 }
616         }
617         else if (s == "none")
618         {
619                 g_weaponarena = 1;
620                 g_weaponarena_list = "No Weapons";
621         }
622         else
623         {
624                 g_weaponarena = 1;
625                 t = tokenize_console(s);
626                 g_weaponarena_list = "";
627                 for (i = 0; i < t; ++i)
628                 {
629                         s = argv(i);
630                         for (j = WEP_FIRST; j <= WEP_LAST; ++j)
631                         {
632                                 e = get_weaponinfo(j);
633                                 if (e.netname == s)
634                                 {
635                                         g_weaponarena_weapons |= WepSet_FromWeapon(j);
636                                         g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
637                                         break;
638                                 }
639                         }
640                         if (j > WEP_LAST)
641                         {
642                                 print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
643                         }
644                 }
645                 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
646         }
647
648         if(g_weaponarena)
649                 g_weaponarena_random = cvar("g_weaponarena_random");
650         else
651                 g_weaponarena_random = 0;
652         g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster");
653
654         if (g_weaponarena)
655         {
656                 g_weapon_stay = 0; // incompatible
657                 start_weapons = g_weaponarena_weapons;
658                 start_items |= IT_UNLIMITED_AMMO;
659         }
660         else
661         {
662                 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
663                 {
664                         e = get_weaponinfo(i);
665                         int w = want_weapon(e, false);
666                         if(w & 1)
667                                 start_weapons |= WepSet_FromWeapon(i);
668                         if(w & 2)
669                                 start_weapons_default |= WepSet_FromWeapon(i);
670                         if(w & 4)
671                                 start_weapons_defaultmask |= WepSet_FromWeapon(i);
672                 }
673         }
674
675         if(!cvar("g_use_ammunition"))
676                 start_items |= IT_UNLIMITED_AMMO;
677
678         if(start_items & IT_UNLIMITED_WEAPON_AMMO)
679         {
680                 start_ammo_shells = 999;
681                 start_ammo_nails = 999;
682                 start_ammo_rockets = 999;
683                 start_ammo_cells = 999;
684                 start_ammo_plasma = 999;
685                 start_ammo_fuel = 999;
686         }
687         else
688         {
689                 start_ammo_shells = cvar("g_start_ammo_shells");
690                 start_ammo_nails = cvar("g_start_ammo_nails");
691                 start_ammo_rockets = cvar("g_start_ammo_rockets");
692                 start_ammo_cells = cvar("g_start_ammo_cells");
693                 start_ammo_plasma = cvar("g_start_ammo_plasma");
694                 start_ammo_fuel = cvar("g_start_ammo_fuel");
695         }
696
697         if (warmup_stage)
698         {
699                 warmup_start_ammo_shells = start_ammo_shells;
700                 warmup_start_ammo_nails = start_ammo_nails;
701                 warmup_start_ammo_rockets = start_ammo_rockets;
702                 warmup_start_ammo_cells = start_ammo_cells;
703                 warmup_start_ammo_plasma = start_ammo_plasma;
704                 warmup_start_ammo_fuel = start_ammo_fuel;
705                 warmup_start_health = start_health;
706                 warmup_start_armorvalue = start_armorvalue;
707                 warmup_start_weapons = start_weapons;
708                 warmup_start_weapons_default = start_weapons_default;
709                 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
710
711                 if (!g_weaponarena && !g_ca)
712                 {
713                         warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
714                         warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
715                         warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
716                         warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
717                         warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
718                         warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
719                         warmup_start_health = cvar("g_warmup_start_health");
720                         warmup_start_armorvalue = cvar("g_warmup_start_armor");
721                         warmup_start_weapons = '0 0 0';
722                         warmup_start_weapons_default = '0 0 0';
723                         warmup_start_weapons_defaultmask = '0 0 0';
724                         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
725                         {
726                                 e = get_weaponinfo(i);
727                                 int w = want_weapon(e, g_warmup_allguns);
728                                 if(w & 1)
729                                         warmup_start_weapons |= WepSet_FromWeapon(i);
730                                 if(w & 2)
731                                         warmup_start_weapons_default |= WepSet_FromWeapon(i);
732                                 if(w & 4)
733                                         warmup_start_weapons_defaultmask |= WepSet_FromWeapon(i);
734                         }
735                 }
736         }
737
738         if (g_jetpack)
739                 start_items |= IT_JETPACK;
740
741         MUTATOR_CALLHOOK(SetStartItems);
742
743         if ((start_items & IT_JETPACK) || (g_grappling_hook && (start_weapons & WEPSET_HOOK)))
744         {
745                 start_items |= IT_FUEL_REGEN;
746                 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
747                 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
748         }
749
750         WepSet precache_weapons = start_weapons;
751         if (g_warmup_allguns != 1)
752                 precache_weapons |= warmup_start_weapons;
753         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
754         {
755                 e = get_weaponinfo(i);
756                 if(precache_weapons & WepSet_FromWeapon(i))
757                         WEP_ACTION(i, WR_INIT);
758         }
759
760         start_ammo_shells = max(0, start_ammo_shells);
761         start_ammo_nails = max(0, start_ammo_nails);
762         start_ammo_rockets = max(0, start_ammo_rockets);
763         start_ammo_cells = max(0, start_ammo_cells);
764         start_ammo_plasma = max(0, start_ammo_plasma);
765         start_ammo_fuel = max(0, start_ammo_fuel);
766
767         warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
768         warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
769         warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
770         warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
771         warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
772         warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
773 }
774
775 float sound_allowed(float _dest, entity e)
776 {
777     // sounds from world may always pass
778     for (;;)
779     {
780         if (e.classname == "body")
781             e = e.enemy;
782         else if (e.realowner && e.realowner != e)
783             e = e.realowner;
784         else if (e.owner && e.owner != e)
785             e = e.owner;
786         else
787             break;
788     }
789     // sounds to self may always pass
790     if (_dest == MSG_ONE)
791         if (e == msg_entity)
792             return true;
793     // sounds by players can be removed
794     if (autocvar_bot_sound_monopoly)
795         if (IS_REAL_CLIENT(e))
796             return false;
797     // anything else may pass
798     return true;
799 }
800
801 #undef sound
802 void sound(entity e, float chan, string samp, float vol, float _atten)
803 {
804     if (!sound_allowed(MSG_BROADCAST, e))
805         return;
806     sound7(e, chan, samp, vol, _atten, 0, 0);
807 }
808
809 void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float _atten)
810 {
811     float entno, idx;
812
813     if (!sound_allowed(_dest, e))
814         return;
815
816     entno = num_for_edict(e);
817     idx = precache_sound_index(samp);
818
819     int sflags;
820     sflags = 0;
821
822     _atten = floor(_atten * 64);
823     vol = floor(vol * 255);
824
825     if (vol != 255)
826         sflags |= SND_VOLUME;
827     if (_atten != 64)
828         sflags |= SND_ATTENUATION;
829     if (entno >= 8192 || chan < 0 || chan > 7)
830         sflags |= SND_LARGEENTITY;
831     if (idx >= 256)
832         sflags |= SND_LARGESOUND;
833
834     WriteByte(_dest, SVC_SOUND);
835     WriteByte(_dest, sflags);
836     if (sflags & SND_VOLUME)
837         WriteByte(_dest, vol);
838     if (sflags & SND_ATTENUATION)
839         WriteByte(_dest, _atten);
840     if (sflags & SND_LARGEENTITY)
841     {
842         WriteShort(_dest, entno);
843         WriteByte(_dest, chan);
844     }
845     else
846     {
847         WriteShort(_dest, entno * 8 + chan);
848     }
849     if (sflags & SND_LARGESOUND)
850         WriteShort(_dest, idx);
851     else
852         WriteByte(_dest, idx);
853
854     WriteCoord(_dest, o.x);
855     WriteCoord(_dest, o.y);
856     WriteCoord(_dest, o.z);
857 }
858 void soundto(float _dest, entity e, float chan, string samp, float vol, float _atten)
859 {
860     vector o;
861
862     if (!sound_allowed(_dest, e))
863         return;
864
865     o = e.origin + 0.5 * (e.mins + e.maxs);
866     soundtoat(_dest, e, o, chan, samp, vol, _atten);
867 }
868 void soundat(entity e, vector o, float chan, string samp, float vol, float _atten)
869 {
870     soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, _atten);
871 }
872 void stopsoundto(float _dest, entity e, float chan)
873 {
874     float entno;
875
876     if (!sound_allowed(_dest, e))
877         return;
878
879     entno = num_for_edict(e);
880
881     if (entno >= 8192 || chan < 0 || chan > 7)
882     {
883         float idx, sflags;
884         idx = precache_sound_index("misc/null.wav");
885         sflags = SND_LARGEENTITY;
886         if (idx >= 256)
887             sflags |= SND_LARGESOUND;
888         WriteByte(_dest, SVC_SOUND);
889         WriteByte(_dest, sflags);
890         WriteShort(_dest, entno);
891         WriteByte(_dest, chan);
892         if (sflags & SND_LARGESOUND)
893             WriteShort(_dest, idx);
894         else
895             WriteByte(_dest, idx);
896         WriteCoord(_dest, e.origin.x);
897         WriteCoord(_dest, e.origin.y);
898         WriteCoord(_dest, e.origin.z);
899     }
900     else
901     {
902         WriteByte(_dest, SVC_STOPSOUND);
903         WriteShort(_dest, entno * 8 + chan);
904     }
905 }
906 void stopsound(entity e, float chan)
907 {
908     if (!sound_allowed(MSG_BROADCAST, e))
909         return;
910
911     stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
912     stopsoundto(MSG_ALL, e, chan); // in case of packet loss
913 }
914
915 void play2(entity e, string filename)
916 {
917     //stuffcmd(e, strcat("play2 ", filename, "\n"));
918     msg_entity = e;
919     soundtoat(MSG_ONE, world, '0 0 0', CH_INFO, filename, VOL_BASE, ATTEN_NONE);
920 }
921
922 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
923 .float spamtime;
924 float spamsound(entity e, float chan, string samp, float vol, float _atten)
925 {
926     if (!sound_allowed(MSG_BROADCAST, e))
927         return false;
928
929     if (time > e.spamtime)
930     {
931         e.spamtime = time;
932         sound(e, chan, samp, vol, _atten);
933         return true;
934     }
935     return false;
936 }
937
938 void play2team(float t, string filename)
939 {
940     entity head;
941
942     if (autocvar_bot_sound_monopoly)
943         return;
944
945     FOR_EACH_REALPLAYER(head)
946     {
947         if (head.team == t)
948             play2(head, filename);
949     }
950 }
951
952 void play2all(string samp)
953 {
954     if (autocvar_bot_sound_monopoly)
955         return;
956
957     sound(world, CH_INFO, samp, VOL_BASE, ATTEN_NONE);
958 }
959
960 void PrecachePlayerSounds(string f);
961 void precache_playermodel(string m)
962 {
963         float globhandle, i, n;
964         string f;
965
966         if(substring(m, -9,5) == "_lod1")
967                 return;
968         if(substring(m, -9,5) == "_lod2")
969                 return;
970         precache_model(m);
971         f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
972         if(fexists(f))
973                 precache_model(f);
974         f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
975         if(fexists(f))
976                 precache_model(f);
977
978         globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
979         if (globhandle < 0)
980                 return;
981         n = search_getsize(globhandle);
982         for (i = 0; i < n; ++i)
983         {
984                 //print(search_getfilename(globhandle, i), "\n");
985                 f = search_getfilename(globhandle, i);
986                 PrecachePlayerSounds(f);
987         }
988         search_end(globhandle);
989 }
990 void precache_all_playermodels(string pattern)
991 {
992         float globhandle, i, n;
993         string f;
994
995         globhandle = search_begin(pattern, true, false);
996         if (globhandle < 0)
997                 return;
998         n = search_getsize(globhandle);
999         for (i = 0; i < n; ++i)
1000         {
1001                 //print(search_getfilename(globhandle, i), "\n");
1002                 f = search_getfilename(globhandle, i);
1003                 precache_playermodel(f);
1004         }
1005         search_end(globhandle);
1006 }
1007
1008 void precache()
1009 {
1010     // gamemode related things
1011     precache_model ("models/misc/chatbubble.spr");
1012         precache_model("models/ice/ice.md3");
1013
1014 #ifdef TTURRETS_ENABLED
1015     if (autocvar_g_turrets)
1016         turrets_precash();
1017 #endif
1018
1019     // Precache all player models if desired
1020     if (autocvar_sv_precacheplayermodels)
1021     {
1022         PrecachePlayerSounds("sound/player/default.sounds");
1023         precache_all_playermodels("models/player/*.zym");
1024         precache_all_playermodels("models/player/*.dpm");
1025         precache_all_playermodels("models/player/*.md3");
1026         precache_all_playermodels("models/player/*.psk");
1027         precache_all_playermodels("models/player/*.iqm");
1028     }
1029
1030     if (autocvar_sv_defaultcharacter)
1031     {
1032         string s;
1033         s = autocvar_sv_defaultplayermodel_red;
1034         if (s != "")
1035             precache_playermodel(s);
1036         s = autocvar_sv_defaultplayermodel_blue;
1037         if (s != "")
1038             precache_playermodel(s);
1039         s = autocvar_sv_defaultplayermodel_yellow;
1040         if (s != "")
1041             precache_playermodel(s);
1042         s = autocvar_sv_defaultplayermodel_pink;
1043         if (s != "")
1044             precache_playermodel(s);
1045         s = autocvar_sv_defaultplayermodel;
1046         if (s != "")
1047             precache_playermodel(s);
1048     }
1049
1050     if (g_footsteps)
1051     {
1052         PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1053         PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1054     }
1055
1056     // gore and miscellaneous sounds
1057     //precache_sound ("misc/h2ohit.wav");
1058     precache_model ("models/hook.md3");
1059     precache_sound ("misc/armorimpact.wav");
1060     precache_sound ("misc/bodyimpact1.wav");
1061     precache_sound ("misc/bodyimpact2.wav");
1062     precache_sound ("misc/gib.wav");
1063     precache_sound ("misc/gib_splat01.wav");
1064     precache_sound ("misc/gib_splat02.wav");
1065     precache_sound ("misc/gib_splat03.wav");
1066     precache_sound ("misc/gib_splat04.wav");
1067     PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1068     PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1069     precache_sound ("misc/null.wav");
1070     precache_sound ("misc/spawn.wav");
1071     precache_sound ("misc/talk.wav");
1072     precache_sound ("misc/teleport.wav");
1073     precache_sound ("misc/poweroff.wav");
1074     precache_sound ("player/lava.wav");
1075     precache_sound ("player/slime.wav");
1076
1077     precache_model ("models/sprites/0.spr32");
1078     precache_model ("models/sprites/1.spr32");
1079     precache_model ("models/sprites/2.spr32");
1080     precache_model ("models/sprites/3.spr32");
1081     precache_model ("models/sprites/4.spr32");
1082     precache_model ("models/sprites/5.spr32");
1083     precache_model ("models/sprites/6.spr32");
1084     precache_model ("models/sprites/7.spr32");
1085     precache_model ("models/sprites/8.spr32");
1086     precache_model ("models/sprites/9.spr32");
1087     precache_model ("models/sprites/10.spr32");
1088
1089     // common weapon precaches
1090         precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound here
1091     precache_sound ("weapons/weapon_switch.wav");
1092     precache_sound ("weapons/weaponpickup.wav");
1093     precache_sound ("weapons/unavailable.wav");
1094     precache_sound ("weapons/dryfire.wav");
1095     if (g_grappling_hook)
1096     {
1097         precache_sound ("weapons/hook_fire.wav"); // hook
1098         precache_sound ("weapons/hook_impact.wav"); // hook
1099     }
1100
1101     precache_model("models/elaser.mdl");
1102     precache_model("models/laser.mdl");
1103     precache_model("models/ebomb.mdl");
1104
1105 #if 0
1106     // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1107
1108     if (!self.noise && self.music) // quake 3 uses the music field
1109         self.noise = self.music;
1110
1111     // plays music for the level if there is any
1112     if (self.noise)
1113     {
1114         precache_sound (self.noise);
1115         ambientsound ('0 0 0', self.noise, VOL_BASE, ATTEN_NONE);
1116     }
1117 #endif
1118
1119 #include "precache-for-csqc.inc"
1120 }
1121
1122
1123 void make_safe_for_remove(entity e)
1124 {
1125     if (e.initialize_entity)
1126     {
1127         entity ent, prev = world;
1128         for (ent = initialize_entity_first; ent; )
1129         {
1130             if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1131             {
1132                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1133                 // skip it in linked list
1134                 if (prev)
1135                 {
1136                     prev.initialize_entity_next = ent.initialize_entity_next;
1137                     ent = prev.initialize_entity_next;
1138                 }
1139                 else
1140                 {
1141                     initialize_entity_first = ent.initialize_entity_next;
1142                     ent = initialize_entity_first;
1143                 }
1144             }
1145             else
1146             {
1147                 prev = ent;
1148                 ent = ent.initialize_entity_next;
1149             }
1150         }
1151     }
1152 }
1153
1154 void objerror(string s)
1155 {
1156     make_safe_for_remove(self);
1157     builtin_objerror(s);
1158 }
1159
1160 .float remove_except_protected_forbidden;
1161 void remove_except_protected(entity e)
1162 {
1163         if(e.remove_except_protected_forbidden)
1164                 error("not allowed to remove this at this point");
1165         builtin_remove(e);
1166 }
1167
1168 void remove_unsafely(entity e)
1169 {
1170     if(e.classname == "spike")
1171         error("Removing spikes is forbidden (crylink bug), please report");
1172     builtin_remove(e);
1173 }
1174
1175 void remove_safely(entity e)
1176 {
1177     make_safe_for_remove(e);
1178     builtin_remove(e);
1179 }
1180
1181 void InitializeEntity(entity e, void(void) func, float order)
1182 {
1183     entity prev, cur;
1184
1185     if (!e || e.initialize_entity)
1186     {
1187         // make a proxy initializer entity
1188         entity e_old;
1189         e_old = e;
1190         e = spawn();
1191         e.classname = "initialize_entity";
1192         e.enemy = e_old;
1193     }
1194
1195     e.initialize_entity = func;
1196     e.initialize_entity_order = order;
1197
1198     cur = initialize_entity_first;
1199     prev = world;
1200     for (;;)
1201     {
1202         if (!cur || cur.initialize_entity_order > order)
1203         {
1204             // insert between prev and cur
1205             if (prev)
1206                 prev.initialize_entity_next = e;
1207             else
1208                 initialize_entity_first = e;
1209             e.initialize_entity_next = cur;
1210             return;
1211         }
1212         prev = cur;
1213         cur = cur.initialize_entity_next;
1214     }
1215 }
1216 void InitializeEntitiesRun()
1217 {
1218     entity startoflist;
1219     startoflist = initialize_entity_first;
1220     initialize_entity_first = world;
1221     remove = remove_except_protected;
1222     for (self = startoflist; self; self = self.initialize_entity_next)
1223     {
1224         self.remove_except_protected_forbidden = 1;
1225     }
1226     for (self = startoflist; self; )
1227     {
1228         entity e;
1229         var void(void) func;
1230         e = self.initialize_entity_next;
1231         func = self.initialize_entity;
1232         self.initialize_entity_order = 0;
1233         self.initialize_entity = func_null;
1234         self.initialize_entity_next = world;
1235         self.remove_except_protected_forbidden = 0;
1236         if (self.classname == "initialize_entity")
1237         {
1238             entity e_old;
1239             e_old = self.enemy;
1240             builtin_remove(self);
1241             self = e_old;
1242         }
1243         //dprint("Delayed initialization: ", self.classname, "\n");
1244         if(func)
1245             func();
1246         else
1247         {
1248             eprint(self);
1249             backtrace(strcat("Null function in: ", self.classname, "\n"));
1250         }
1251         self = e;
1252     }
1253     remove = remove_unsafely;
1254 }
1255
1256 void UncustomizeEntitiesRun()
1257 {
1258     entity oldself;
1259     oldself = self;
1260     for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1261         self.uncustomizeentityforclient();
1262     self = oldself;
1263 }
1264 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1265 {
1266     e.customizeentityforclient = customizer;
1267     e.uncustomizeentityforclient = uncustomizer;
1268     e.uncustomizeentityforclient_set = !!uncustomizer;
1269 }
1270
1271
1272 void Net_LinkEntity(entity e, float docull, float dt, bool(entity, int) sendfunc)
1273 {
1274     vector mi, ma;
1275
1276     if (e.classname == "")
1277         e.classname = "net_linked";
1278
1279     if (e.model == "" || self.modelindex == 0)
1280     {
1281         mi = e.mins;
1282         ma = e.maxs;
1283         setmodel(e, "null");
1284         setsize(e, mi, ma);
1285     }
1286
1287     e.SendEntity = sendfunc;
1288     e.SendFlags = 0xFFFFFF;
1289
1290     if (!docull)
1291         e.effects |= EF_NODEPTHTEST;
1292
1293     if (dt)
1294     {
1295         e.nextthink = time + dt;
1296         e.think = SUB_Remove;
1297     }
1298 }
1299
1300
1301 .float(entity) isEliminated;
1302 float EliminatedPlayers_SendEntity(entity to, float sendflags)
1303 {
1304         float i, f, b;
1305         entity e;
1306         WriteByte(MSG_ENTITY, ENT_CLIENT_ELIMINATEDPLAYERS);
1307         WriteByte(MSG_ENTITY, sendflags);
1308
1309         if(sendflags & 1)
1310         {
1311                 for(i = 1; i <= maxclients; i += 8)
1312                 {
1313                         for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
1314                         {
1315                                 if(eliminatedPlayers.isEliminated(e))
1316                                         f |= b;
1317                         }
1318                         WriteByte(MSG_ENTITY, f);
1319                 }
1320         }
1321
1322         return true;
1323 }
1324
1325 void EliminatedPlayers_Init(float(entity) isEliminated_func)
1326 {
1327         if(eliminatedPlayers)
1328         {
1329                 backtrace("Can't spawn eliminatedPlayers again!");
1330                 return;
1331         }
1332         Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1333         eliminatedPlayers.isEliminated = isEliminated_func;
1334 }
1335
1336
1337 void adaptor_think2touch()
1338 {
1339     entity o;
1340     o = other;
1341     other = world;
1342     self.touch();
1343     other = o;
1344 }
1345
1346 void adaptor_think2use()
1347 {
1348     entity o, a;
1349     o = other;
1350     a = activator;
1351     activator = world;
1352     other = world;
1353     self.use();
1354     other = o;
1355     activator = a;
1356 }
1357
1358 void adaptor_think2use_hittype_splash() // for timed projectile detonation
1359 {
1360         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
1361                 self.projectiledeathtype |= HITTYPE_SPLASH;
1362         adaptor_think2use();
1363 }
1364
1365 // deferred dropping
1366 void DropToFloor_Handler()
1367 {
1368     builtin_droptofloor();
1369     self.dropped_origin = self.origin;
1370 }
1371
1372 void droptofloor()
1373 {
1374     InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1375 }
1376
1377
1378
1379 float trace_hits_box_a0, trace_hits_box_a1;
1380
1381 float trace_hits_box_1d(float end, float thmi, float thma)
1382 {
1383     if (end == 0)
1384     {
1385         // just check if x is in range
1386         if (0 < thmi)
1387             return false;
1388         if (0 > thma)
1389             return false;
1390     }
1391     else
1392     {
1393         // do the trace with respect to x
1394         // 0 -> end has to stay in thmi -> thma
1395         trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1396         trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1397         if (trace_hits_box_a0 > trace_hits_box_a1)
1398             return false;
1399     }
1400     return true;
1401 }
1402
1403 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1404 {
1405     end -= start;
1406     thmi -= start;
1407     thma -= start;
1408     // now it is a trace from 0 to end
1409
1410     trace_hits_box_a0 = 0;
1411     trace_hits_box_a1 = 1;
1412
1413     if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1414         return false;
1415     if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1416         return false;
1417     if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1418         return false;
1419
1420     return true;
1421 }
1422
1423 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1424 {
1425     return trace_hits_box(start, end, thmi - ma, thma - mi);
1426 }
1427
1428 float SUB_NoImpactCheck()
1429 {
1430         // zero hitcontents = this is not the real impact, but either the
1431         // mirror-impact of something hitting the projectile instead of the
1432         // projectile hitting the something, or a touchareagrid one. Neither of
1433         // these stop the projectile from moving, so...
1434         if(trace_dphitcontents == 0)
1435         {
1436                 //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1437                 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));
1438                 checkclient();
1439         }
1440     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1441         return 1;
1442     if (other == world && self.size != '0 0 0')
1443     {
1444         vector tic;
1445         tic = self.velocity * sys_frametime;
1446         tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1447         traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1448         if (trace_fraction >= 1)
1449         {
1450             dprint("Odd... did not hit...?\n");
1451         }
1452         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1453         {
1454             dprint("Detected and prevented the sky-grapple bug.\n");
1455             return 1;
1456         }
1457     }
1458
1459     return 0;
1460 }
1461
1462 #define SUB_OwnerCheck() (other && (other == self.owner))
1463
1464 void RemoveGrapplingHook(entity pl);
1465 void W_Crylink_Dequeue(entity e);
1466 float WarpZone_Projectile_Touch_ImpactFilter_Callback()
1467 {
1468         if(SUB_OwnerCheck())
1469                 return true;
1470         if(SUB_NoImpactCheck())
1471         {
1472                 if(self.classname == "nade")
1473                         return false; // no checks here
1474                 else if(self.classname == "grapplinghook")
1475                         RemoveGrapplingHook(self.realowner);
1476                 else if(self.classname == "spike")
1477                 {
1478                         W_Crylink_Dequeue(self);
1479                         remove(self);
1480                 }
1481                 else
1482                         remove(self);
1483                 return true;
1484         }
1485         if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1486                 UpdateCSQCProjectile(self);
1487         return false;
1488 }
1489
1490
1491 void URI_Get_Callback(float id, float status, string data)
1492 {
1493         if(url_URI_Get_Callback(id, status, data))
1494         {
1495                 // handled
1496         }
1497         else if (id == URI_GET_DISCARD)
1498         {
1499                 // discard
1500         }
1501         else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1502         {
1503                 // sv_cmd curl
1504                 Curl_URI_Get_Callback(id, status, data);
1505         }
1506         else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1507         {
1508                 // online ban list
1509                 OnlineBanList_URI_Get_Callback(id, status, data);
1510         }
1511         else
1512         {
1513                 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1514         }
1515 }
1516
1517 string uid2name(string myuid) {
1518         string s;
1519         s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1520
1521         // FIXME remove this later after 0.6 release
1522         // convert old style broken records to correct style
1523         if(s == "")
1524         {
1525                 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1526                 if(s != "")
1527                 {
1528                         db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1529                         db_put(ServerProgsDB, strcat("uid2name", myuid), "");
1530                 }
1531         }
1532
1533         if(s == "")
1534                 s = "^1Unregistered Player";
1535         return s;
1536 }
1537
1538 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1539 {
1540     float m, i;
1541     vector start, org, delta, end, enddown, mstart;
1542     entity sp;
1543
1544     m = e.dphitcontentsmask;
1545     e.dphitcontentsmask = goodcontents | badcontents;
1546
1547     org = world.mins;
1548     delta = world.maxs - world.mins;
1549
1550     start = end = org;
1551
1552     for (i = 0; i < attempts; ++i)
1553     {
1554         start.x = org.x + random() * delta.x;
1555         start.y = org.y + random() * delta.y;
1556         start.z = org.z + random() * delta.z;
1557
1558         // rule 1: start inside world bounds, and outside
1559         // solid, and don't start from somewhere where you can
1560         // fall down to evil
1561         tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1562         if (trace_fraction >= 1)
1563             continue;
1564         if (trace_startsolid)
1565             continue;
1566         if (trace_dphitcontents & badcontents)
1567             continue;
1568         if (trace_dphitq3surfaceflags & badsurfaceflags)
1569             continue;
1570
1571         // rule 2: if we are too high, lower the point
1572         if (trace_fraction * delta.z > maxaboveground)
1573             start = trace_endpos + '0 0 1' * maxaboveground;
1574         enddown = trace_endpos;
1575
1576         // rule 3: make sure we aren't outside the map. This only works
1577         // for somewhat well formed maps. A good rule of thumb is that
1578         // the map should have a convex outside hull.
1579         // these can be traceLINES as we already verified the starting box
1580         mstart = start + 0.5 * (e.mins + e.maxs);
1581         traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1582         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1583             continue;
1584         traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1585         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1586             continue;
1587         traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1588         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1589             continue;
1590         traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1591         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1592             continue;
1593         traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1594         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1595             continue;
1596
1597         // rule 4: we must "see" some spawnpoint or item
1598         for(sp = world; (sp = find(sp, classname, "info_player_deathmatch")); )
1599                 if(checkpvs(mstart, sp))
1600                         if((traceline(mstart, sp.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1601                                 break;
1602         if(!sp)
1603         {
1604                 for(sp = world; (sp = findflags(sp, flags, FL_ITEM)); )
1605                         if(checkpvs(mstart, sp))
1606                                 if((traceline(mstart, sp.origin + (sp.mins + sp.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1607                                         break;
1608                 if(!sp)
1609                         continue;
1610         }
1611
1612         // find a random vector to "look at"
1613         end.x = org.x + random() * delta.x;
1614         end.y = org.y + random() * delta.y;
1615         end.z = org.z + random() * delta.z;
1616         end = start + normalize(end - start) * vlen(delta);
1617
1618         // rule 4: start TO end must not be too short
1619         tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1620         if (trace_startsolid)
1621             continue;
1622         if (trace_fraction < minviewdistance / vlen(delta))
1623             continue;
1624
1625         // rule 5: don't want to look at sky
1626         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1627             continue;
1628
1629         // rule 6: we must not end up in trigger_hurt
1630         if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1631             continue;
1632
1633         break;
1634     }
1635
1636     e.dphitcontentsmask = m;
1637
1638     if (i < attempts)
1639     {
1640         setorigin(e, start);
1641         e.angles = vectoangles(end - start);
1642         dprint("Needed ", ftos(i + 1), " attempts\n");
1643         return true;
1644     }
1645     else
1646         return false;
1647 }
1648
1649 void write_recordmarker(entity pl, float tstart, float dt)
1650 {
1651     GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1652
1653     // also write a marker into demo files for demotc-race-record-extractor to find
1654     stuffcmd(pl,
1655              strcat(
1656                  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1657                  " ", ftos(tstart), " ", ftos(dt), "\n"));
1658 }
1659
1660 vector shotorg_adjustfromclient(vector vecs, float y_is_right, float allowcenter, float algn)
1661 {
1662         switch(algn)
1663         {
1664                 default:
1665                 case 3: // right
1666                         break;
1667
1668                 case 4: // left
1669                         vecs.y = -vecs.y;
1670                         break;
1671
1672                 case 1:
1673                         if(allowcenter) // 2: allow center handedness
1674                         {
1675                                 // center
1676                                 vecs.y = 0;
1677                                 vecs.z -= 2;
1678                         }
1679                         else
1680                         {
1681                                 // right
1682                         }
1683                         break;
1684
1685                 case 2:
1686                         if(allowcenter) // 2: allow center handedness
1687                         {
1688                                 // center
1689                                 vecs.y = 0;
1690                                 vecs.z -= 2;
1691                         }
1692                         else
1693                         {
1694                                 // left
1695                                 vecs.y = -vecs.y;
1696                         }
1697                         break;
1698         }
1699         return vecs;
1700 }
1701
1702 vector shotorg_adjust_values(vector vecs, float y_is_right, float visual, float algn)
1703 {
1704         string s;
1705         vector v;
1706
1707         if (autocvar_g_shootfromeye)
1708         {
1709                 if (visual)
1710                 {
1711                         if (autocvar_g_shootfromclient) { vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn); }
1712                         else { vecs.y = 0; vecs.z -= 2; }
1713                 }
1714                 else
1715                 {
1716                         vecs.y = 0;
1717                         vecs.z = 0;
1718                 }
1719         }
1720         else if (autocvar_g_shootfromcenter)
1721         {
1722                 vecs.y = 0;
1723                 vecs.z -= 2;
1724         }
1725         else if ((s = autocvar_g_shootfromfixedorigin) != "")
1726         {
1727                 v = stov(s);
1728                 if (y_is_right)
1729                         v.y = -v.y;
1730                 if (v.x != 0)
1731                         vecs.x = v.x;
1732                 vecs.y = v.y;
1733                 vecs.z = v.z;
1734         }
1735         else if (autocvar_g_shootfromclient)
1736         {
1737                 vecs = shotorg_adjustfromclient(vecs, y_is_right, (autocvar_g_shootfromclient >= 2), algn);
1738         }
1739         return vecs;
1740 }
1741
1742 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
1743 {
1744         return shotorg_adjust_values(vecs, y_is_right, visual, self.owner.cvar_cl_gunalign);
1745 }
1746
1747
1748 void attach_sameorigin(entity e, entity to, string tag)
1749 {
1750     vector org, t_forward, t_left, t_up, e_forward, e_up;
1751     float tagscale;
1752
1753     org = e.origin - gettaginfo(to, gettagindex(to, tag));
1754     tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
1755     t_forward = v_forward * tagscale;
1756     t_left = v_right * -tagscale;
1757     t_up = v_up * tagscale;
1758
1759     e.origin_x = org * t_forward;
1760     e.origin_y = org * t_left;
1761     e.origin_z = org * t_up;
1762
1763     // current forward and up directions
1764     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1765                 e.angles = AnglesTransform_FromVAngles(e.angles);
1766         else
1767                 e.angles = AnglesTransform_FromAngles(e.angles);
1768     fixedmakevectors(e.angles);
1769
1770     // untransform forward, up!
1771     e_forward.x = v_forward * t_forward;
1772     e_forward.y = v_forward * t_left;
1773     e_forward.z = v_forward * t_up;
1774     e_up.x = v_up * t_forward;
1775     e_up.y = v_up * t_left;
1776     e_up.z = v_up * t_up;
1777
1778     e.angles = fixedvectoangles2(e_forward, e_up);
1779     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1780                 e.angles = AnglesTransform_ToVAngles(e.angles);
1781         else
1782                 e.angles = AnglesTransform_ToAngles(e.angles);
1783
1784     setattachment(e, to, tag);
1785     setorigin(e, e.origin);
1786 }
1787
1788 void detach_sameorigin(entity e)
1789 {
1790     vector org;
1791     org = gettaginfo(e, 0);
1792     e.angles = fixedvectoangles2(v_forward, v_up);
1793     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1794                 e.angles = AnglesTransform_ToVAngles(e.angles);
1795         else
1796                 e.angles = AnglesTransform_ToAngles(e.angles);
1797     setorigin(e, org);
1798     setattachment(e, world, "");
1799     setorigin(e, e.origin);
1800 }
1801
1802 void follow_sameorigin(entity e, entity to)
1803 {
1804     e.movetype = MOVETYPE_FOLLOW; // make the hole follow
1805     e.aiment = to; // make the hole follow bmodel
1806     e.punchangle = to.angles; // the original angles of bmodel
1807     e.view_ofs = e.origin - to.origin; // relative origin
1808     e.v_angle = e.angles - to.angles; // relative angles
1809 }
1810
1811 void unfollow_sameorigin(entity e)
1812 {
1813     e.movetype = MOVETYPE_NONE;
1814 }
1815
1816 entity gettaginfo_relative_ent;
1817 vector gettaginfo_relative(entity e, float tag)
1818 {
1819     if (!gettaginfo_relative_ent)
1820     {
1821         gettaginfo_relative_ent = spawn();
1822         gettaginfo_relative_ent.effects = EF_NODRAW;
1823     }
1824     gettaginfo_relative_ent.model = e.model;
1825     gettaginfo_relative_ent.modelindex = e.modelindex;
1826     gettaginfo_relative_ent.frame = e.frame;
1827     return gettaginfo(gettaginfo_relative_ent, tag);
1828 }
1829
1830 .float scale2;
1831
1832 float modeleffect_SendEntity(entity to, int sf)
1833 {
1834         float f;
1835         WriteByte(MSG_ENTITY, ENT_CLIENT_MODELEFFECT);
1836
1837         f = 0;
1838         if(self.velocity != '0 0 0')
1839                 f |= 1;
1840         if(self.angles != '0 0 0')
1841                 f |= 2;
1842         if(self.avelocity != '0 0 0')
1843                 f |= 4;
1844
1845         WriteByte(MSG_ENTITY, f);
1846         WriteShort(MSG_ENTITY, self.modelindex);
1847         WriteByte(MSG_ENTITY, self.skin);
1848         WriteByte(MSG_ENTITY, self.frame);
1849         WriteCoord(MSG_ENTITY, self.origin.x);
1850         WriteCoord(MSG_ENTITY, self.origin.y);
1851         WriteCoord(MSG_ENTITY, self.origin.z);
1852         if(f & 1)
1853         {
1854                 WriteCoord(MSG_ENTITY, self.velocity.x);
1855                 WriteCoord(MSG_ENTITY, self.velocity.y);
1856                 WriteCoord(MSG_ENTITY, self.velocity.z);
1857         }
1858         if(f & 2)
1859         {
1860                 WriteCoord(MSG_ENTITY, self.angles.x);
1861                 WriteCoord(MSG_ENTITY, self.angles.y);
1862                 WriteCoord(MSG_ENTITY, self.angles.z);
1863         }
1864         if(f & 4)
1865         {
1866                 WriteCoord(MSG_ENTITY, self.avelocity.x);
1867                 WriteCoord(MSG_ENTITY, self.avelocity.y);
1868                 WriteCoord(MSG_ENTITY, self.avelocity.z);
1869         }
1870         WriteShort(MSG_ENTITY, self.scale * 256.0);
1871         WriteShort(MSG_ENTITY, self.scale2 * 256.0);
1872         WriteByte(MSG_ENTITY, self.teleport_time * 100.0);
1873         WriteByte(MSG_ENTITY, self.fade_time * 100.0);
1874         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1875
1876         return true;
1877 }
1878
1879 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)
1880 {
1881         entity e;
1882         float sz;
1883         e = spawn();
1884         e.classname = "modeleffect";
1885         setmodel(e, m);
1886         e.frame = f;
1887         setorigin(e, o);
1888         e.velocity = v;
1889         e.angles = ang;
1890         e.avelocity = angv;
1891         e.alpha = a;
1892         e.teleport_time = t1;
1893         e.fade_time = t2;
1894         e.skin = s;
1895         if(s0 >= 0)
1896                 e.scale = s0 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1897         else
1898                 e.scale = -s0;
1899         if(s2 >= 0)
1900                 e.scale2 = s2 / max6(-e.mins.x, -e.mins.y, -e.mins.z, e.maxs.x, e.maxs.y, e.maxs.z);
1901         else
1902                 e.scale2 = -s2;
1903         sz = max(e.scale, e.scale2);
1904         setsize(e, e.mins * sz, e.maxs * sz);
1905         Net_LinkEntity(e, false, 0.1, modeleffect_SendEntity);
1906 }
1907
1908 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
1909 {
1910         return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
1911 }
1912
1913 float randombit(float bits)
1914 {
1915         if(!(bits & (bits-1))) // this ONLY holds for powers of two!
1916                 return bits;
1917
1918         float n, f, b, r;
1919
1920         r = random();
1921         b = 0;
1922         n = 0;
1923
1924         for(f = 1; f <= bits; f *= 2)
1925         {
1926                 if(bits & f)
1927                 {
1928                         ++n;
1929                         r *= n;
1930                         if(r <= 1)
1931                                 b = f;
1932                         else
1933                                 r = (r - 1) / (n - 1);
1934                 }
1935         }
1936
1937         return b;
1938 }
1939
1940 float randombits(float bits, float k, float error_return)
1941 {
1942         float r;
1943         r = 0;
1944         while(k > 0 && bits != r)
1945         {
1946                 r += randombit(bits - r);
1947                 --k;
1948         }
1949         if(error_return)
1950                 if(k > 0)
1951                         return -1; // all
1952         return r;
1953 }
1954
1955 void randombit_test(float bits, float iter)
1956 {
1957         while(iter > 0)
1958         {
1959                 print(ftos(randombit(bits)), "\n");
1960                 --iter;
1961         }
1962 }
1963
1964 float ExponentialFalloff(float mindist, float maxdist, float halflifedist, float d)
1965 {
1966         if(halflifedist > 0)
1967                 return pow(0.5, (bound(mindist, d, maxdist) - mindist) / halflifedist);
1968         else if(halflifedist < 0)
1969                 return pow(0.5, (bound(mindist, d, maxdist) - maxdist) / halflifedist);
1970         else
1971                 return 1;
1972 }
1973
1974
1975 void defer_think()
1976 {
1977     entity oself;
1978
1979     oself           = self;
1980     self            = self.owner;
1981     oself.think     = SUB_Remove;
1982     oself.nextthink = time;
1983
1984     oself.use();
1985 }
1986
1987 /*
1988     Execute func() after time + fdelay.
1989     self when func is executed = self when defer is called
1990 */
1991 void defer(float fdelay, void() func)
1992 {
1993     entity e;
1994
1995     e           = spawn();
1996     e.owner     = self;
1997     e.use       = func;
1998     e.think     = defer_think;
1999     e.nextthink = time + fdelay;
2000 }
2001
2002 .string aiment_classname;
2003 .float aiment_deadflag;
2004 void SetMovetypeFollow(entity ent, entity e)
2005 {
2006         // FIXME this may not be warpzone aware
2007         ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
2008         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.
2009         ent.aiment = e; // make the hole follow bmodel
2010         ent.punchangle = e.angles; // the original angles of bmodel
2011         ent.view_ofs = ent.origin - e.origin; // relative origin
2012         ent.v_angle = ent.angles - e.angles; // relative angles
2013         ent.aiment_classname = strzone(e.classname);
2014         ent.aiment_deadflag = e.deadflag;
2015 }
2016 void UnsetMovetypeFollow(entity ent)
2017 {
2018         ent.movetype = MOVETYPE_FLY;
2019         PROJECTILE_MAKETRIGGER(ent);
2020         ent.aiment = world;
2021 }
2022 float LostMovetypeFollow(entity ent)
2023 {
2024 /*
2025         if(ent.movetype != MOVETYPE_FOLLOW)
2026                 if(ent.aiment)
2027                         error("???");
2028 */
2029         if(ent.aiment)
2030         {
2031                 if(ent.aiment.classname != ent.aiment_classname)
2032                         return 1;
2033                 if(ent.aiment.deadflag != ent.aiment_deadflag)
2034                         return 1;
2035         }
2036         return 0;
2037 }
2038
2039 float isPushable(entity e)
2040 {
2041         if(e.iscreature)
2042                 return true;
2043         if(e.pushable)
2044                 return true;
2045         switch(e.classname)
2046         {
2047                 case "body":
2048                 case "droppedweapon":
2049                 case "keepawayball":
2050                 case "nexball_basketball":
2051                 case "nexball_football":
2052                         return true;
2053                 case "bullet": // antilagged bullets can't hit this either
2054                         return false;
2055         }
2056         if (e.projectiledeathtype)
2057                 return true;
2058         return false;
2059 }