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