]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/miscfunctions.qc
also make projectile speed variable :P
[divverent/nexuiz.git] / data / qcsrc / server / miscfunctions.qc
1 var void remove(entity e);
2 void objerror(string s);
3 void droptofloor();
4 .vector dropped_origin;
5
6 void() spawnfunc_info_player_deathmatch; // needed for the other spawnpoints
7 void() spawnpoint_use;
8 string ColoredTeamName(float t);
9
10 float DistributeEvenly_amount;
11 float DistributeEvenly_totalweight;
12 void DistributeEvenly_Init(float amount, float totalweight)
13 {
14     if (DistributeEvenly_amount)
15     {
16         dprint("DistributeEvenly_Init: UNFINISHED DISTRIBUTION (", ftos(DistributeEvenly_amount), " for ");
17         dprint(ftos(DistributeEvenly_totalweight), " left!)\n");
18     }
19     if (totalweight == 0)
20         DistributeEvenly_amount = 0;
21     else
22         DistributeEvenly_amount = amount;
23     DistributeEvenly_totalweight = totalweight;
24 }
25 float DistributeEvenly_Get(float weight)
26 {
27     float f;
28     if (weight <= 0)
29         return 0;
30     f = floor(0.5 + DistributeEvenly_amount * weight / DistributeEvenly_totalweight);
31     DistributeEvenly_totalweight -= weight;
32     DistributeEvenly_amount -= f;
33     return f;
34 }
35
36 void move_out_of_solid_expand(entity e, vector by)
37 {
38     float eps = 0.0625;
39     tracebox(e.origin, e.mins - '1 1 1' * eps, e.maxs + '1 1 1' * eps, e.origin + by, MOVE_WORLDONLY, e);
40     if (trace_startsolid)
41         return;
42     if (trace_fraction < 1)
43     {
44         // hit something
45         // adjust origin in the other direction...
46         e.origin = e.origin - by * (1 - trace_fraction);
47     }
48 }
49
50 float move_out_of_solid(entity e)
51 {
52     vector o, m0, m1;
53
54     o = e.origin;
55     traceline(o, o, MOVE_WORLDONLY, e);
56     if (trace_startsolid)
57         return 0;
58
59     tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
60     if (!trace_startsolid)
61         return 1;
62
63     m0 = e.mins;
64     m1 = e.maxs;
65     e.mins = '0 0 0';
66     e.maxs = '0 0 0';
67     move_out_of_solid_expand(e, '1 0 0' * m0_x);
68     e.mins_x = m0_x;
69     move_out_of_solid_expand(e, '1 0 0' * m1_x);
70     e.maxs_x = m1_x;
71     move_out_of_solid_expand(e, '0 1 0' * m0_y);
72     e.mins_y = m0_y;
73     move_out_of_solid_expand(e, '0 1 0' * m1_y);
74     e.maxs_y = m1_y;
75     move_out_of_solid_expand(e, '0 0 1' * m0_z);
76     e.mins_z = m0_z;
77     move_out_of_solid_expand(e, '0 0 1' * m1_z);
78     e.maxs_z = m1_z;
79     setorigin(e, e.origin);
80
81     tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
82     if (trace_startsolid)
83     {
84         setorigin(e, o);
85         return 0;
86     }
87
88     return 1;
89 }
90
91 string STR_PLAYER = "player";
92 string STR_SPECTATOR = "spectator";
93 string STR_OBSERVER = "observer";
94
95 #if 0
96 #define FOR_EACH_CLIENT(v) for(v = world; (v = findflags(v, flags, FL_CLIENT)) != world; )
97 #define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(clienttype(v) == CLIENTTYPE_REAL)
98 #define FOR_EACH_PLAYER(v) for(v = world; (v = find(v, classname, STR_PLAYER)) != world; )
99 #define FOR_EACH_REALPLAYER(v) FOR_EACH_PLAYER(v) if(clienttype(v) == CLIENTTYPE_REAL)
100 #else
101 #define FOR_EACH_CLIENTSLOT(v) for(v = world; (v = nextent(v)) && (num_for_edict(v) <= maxclients); )
102 #define FOR_EACH_CLIENT(v) FOR_EACH_CLIENTSLOT(v) if(v.flags & FL_CLIENT)
103 #define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(clienttype(v) == CLIENTTYPE_REAL)
104 #define FOR_EACH_PLAYER(v) FOR_EACH_CLIENT(v) if(v.classname == STR_PLAYER)
105 #define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(v.classname == STR_PLAYER)
106 #endif
107
108 // copies a string to a tempstring (so one can strunzone it)
109 string strcat1(string s) = #115; // FRIK_FILE
110
111 float logfile_open;
112 float logfile;
113
114 void bcenterprint(string s)
115 {
116     // TODO replace by MSG_ALL (would show it to spectators too, though)?
117     entity head;
118     FOR_EACH_PLAYER(head)
119     if (clienttype(head) == CLIENTTYPE_REAL)
120         centerprint(head, s);
121 }
122
123 void GameLogEcho(string s)
124 {
125     string fn;
126     float matches;
127
128     if (cvar("sv_eventlog_files"))
129     {
130         if (!logfile_open)
131         {
132             logfile_open = TRUE;
133             matches = cvar("sv_eventlog_files_counter") + 1;
134             cvar_set("sv_eventlog_files_counter", ftos(matches));
135             fn = ftos(matches);
136             if (strlen(fn) < 8)
137                 fn = strcat(substring("00000000", 0, 8 - strlen(fn)), fn);
138             fn = strcat(cvar_string("sv_eventlog_files_nameprefix"), fn, cvar_string("sv_eventlog_files_namesuffix"));
139             logfile = fopen(fn, FILE_APPEND);
140             fputs(logfile, ":logversion:3\n");
141         }
142         if (logfile >= 0)
143         {
144             if (cvar("sv_eventlog_files_timestamps"))
145                 fputs(logfile, strcat(":time:", strftime(TRUE, "%Y-%m-%d %H:%M:%S", "\n", s, "\n")));
146             else
147                 fputs(logfile, strcat(s, "\n"));
148         }
149     }
150     if (cvar("sv_eventlog_console"))
151     {
152         print(s, "\n");
153     }
154 }
155
156 void GameLogInit()
157 {
158     logfile_open = 0;
159     // will be opened later
160 }
161
162 void GameLogClose()
163 {
164     if (logfile_open && logfile >= 0)
165     {
166         fclose(logfile);
167         logfile = -1;
168     }
169 }
170
171 float spawnpoint_nag;
172 void relocate_spawnpoint()
173 {
174     // nudge off the floor
175     setorigin(self, self.origin + '0 0 1');
176
177     tracebox(self.origin, PL_MIN, PL_MAX, self.origin, TRUE, self);
178     if (trace_startsolid)
179     {
180         vector o;
181         o = self.origin;
182         self.mins = PL_MIN;
183         self.maxs = PL_MAX;
184         if (!move_out_of_solid(self))
185             objerror("could not get out of solid at all!");
186         print("^1NOTE: this map needs FIXING. Spawnpoint at ", vtos(o - '0 0 1'));
187         print(" needs to be moved out of solid, e.g. by '", ftos(self.origin_x - o_x));
188         print(" ", ftos(self.origin_y - o_y));
189         print(" ", ftos(self.origin_z - o_z), "'\n");
190         if (cvar("g_spawnpoints_auto_move_out_of_solid"))
191         {
192             if (!spawnpoint_nag)
193                 print("\{1}^1NOTE: this map needs FIXING (it contains spawnpoints in solid, see server log)\n");
194             spawnpoint_nag = 1;
195         }
196         else
197         {
198             self.origin = o;
199             self.mins = self.maxs = '0 0 0';
200             objerror("player spawn point in solid, mapper sucks!\n");
201             return;
202         }
203     }
204
205     if (cvar("g_spawnpoints_autodrop"))
206     {
207         setsize(self, PL_MIN, PL_MAX);
208         droptofloor();
209     }
210
211     self.use = spawnpoint_use;
212     self.team_saved = self.team;
213     if (!self.cnt)
214         self.cnt = 1;
215
216     if (g_ctf || g_assault || g_onslaught || g_domination || g_nexball)
217         if (self.team)
218             have_team_spawns = 1;
219
220     if (cvar("r_showbboxes"))
221     {
222         // show where spawnpoints point at too
223         makevectors(self.angles);
224         entity e;
225         e = spawn();
226         e.classname = "info_player_foo";
227         setorigin(e, self.origin + v_forward * 24);
228         setsize(e, '-8 -8 -8', '8 8 8');
229         e.solid = SOLID_TRIGGER;
230     }
231 }
232
233 #define strstr strstrofs
234 /*
235 // NOTE: DO NOT USE THIS FUNCTION TOO OFTEN.
236 // IT WILL MOST PROBABLY DESTROY _ALL_ OTHER TEMP
237 // STRINGS AND TAKE QUITE LONG. haystack and needle MUST
238 // BE CONSTANT OR strzoneD!
239 float strstr(string haystack, string needle, float offset)
240 {
241         float len, endpos;
242         string found;
243         len = strlen(needle);
244         endpos = strlen(haystack) - len;
245         while(offset <= endpos)
246         {
247                 found = substring(haystack, offset, len);
248                 if(found == needle)
249                         return offset;
250                 offset = offset + 1;
251         }
252         return -1;
253 }
254 */
255
256 float NUM_NEAREST_ENTITIES = 4;
257 entity nearest_entity[NUM_NEAREST_ENTITIES];
258 float nearest_length[NUM_NEAREST_ENTITIES];
259 entity findnearest(vector point, .string field, string value, vector axismod)
260 {
261     entity localhead;
262     float i;
263     float j;
264     float len;
265     vector dist;
266
267     float num_nearest;
268     num_nearest = 0;
269
270     localhead = find(world, field, value);
271     while (localhead)
272     {
273         if ((localhead.items == IT_KEY1 || localhead.items == IT_KEY2) && localhead.target == "###item###")
274             dist = localhead.oldorigin;
275         else
276             dist = localhead.origin;
277         dist = dist - point;
278         dist = dist_x * axismod_x * '1 0 0' + dist_y * axismod_y * '0 1 0' + dist_z * axismod_z * '0 0 1';
279         len = vlen(dist);
280
281         for (i = 0; i < num_nearest; ++i)
282         {
283             if (len < nearest_length[i])
284                 break;
285         }
286
287         // now i tells us where to insert at
288         //   INSERTION SORT! YOU'VE SEEN IT! RUN!
289         if (i < NUM_NEAREST_ENTITIES)
290         {
291             for (j = NUM_NEAREST_ENTITIES - 1; j >= i; --j)
292             {
293                 nearest_length[j + 1] = nearest_length[j];
294                 nearest_entity[j + 1] = nearest_entity[j];
295             }
296             nearest_length[i] = len;
297             nearest_entity[i] = localhead;
298             if (num_nearest < NUM_NEAREST_ENTITIES)
299                 num_nearest = num_nearest + 1;
300         }
301
302         localhead = find(localhead, field, value);
303     }
304
305     // now use the first one from our list that we can see
306     for (i = 0; i < num_nearest; ++i)
307     {
308         traceline(point, nearest_entity[i].origin, TRUE, world);
309         if (trace_fraction == 1)
310         {
311             if (i != 0)
312             {
313                 dprint("Nearest point (");
314                 dprint(nearest_entity[0].netname);
315                 dprint(") is not visible, using a visible one.\n");
316             }
317             return nearest_entity[i];
318         }
319     }
320
321     if (num_nearest == 0)
322         return world;
323
324     dprint("Not seeing any location point, using nearest as fallback.\n");
325     /* DEBUGGING CODE:
326     dprint("Candidates were: ");
327     for(j = 0; j < num_nearest; ++j)
328     {
329         if(j != 0)
330                 dprint(", ");
331         dprint(nearest_entity[j].netname);
332     }
333     dprint("\n");
334     */
335
336     return nearest_entity[0];
337 }
338
339 void spawnfunc_target_location()
340 {
341     self.classname = "target_location";
342     // location name in netname
343     // eventually support: count, teamgame selectors, line of sight?
344 };
345
346 void spawnfunc_info_location()
347 {
348     self.classname = "target_location";
349     self.message = self.netname;
350 };
351
352 string NearestLocation(vector p)
353 {
354     entity loc;
355     string ret;
356     ret = "somewhere";
357     loc = findnearest(p, classname, "target_location", '1 1 1');
358     if (loc)
359     {
360         ret = loc.message;
361     }
362     else
363     {
364         loc = findnearest(p, target, "###item###", '1 1 4');
365         if (loc)
366             ret = loc.netname;
367     }
368     return ret;
369 }
370
371 string formatmessage(string msg)
372 {
373     float p, p1, p2;
374     float n;
375     string escape;
376     string replacement;
377     p = 0;
378     n = 7;
379     while (1)
380     {
381         if (n < 1)
382             break; // too many replacements
383         n = n - 1;
384         p1 = strstr(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
385         p2 = strstr(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
386
387         if (p1 < 0)
388             p1 = p2;
389         if (p2 < 0)
390             p2 = p1;
391         p = min(p1, p2);
392
393         if (p < 0)
394             break;
395         replacement = substring(msg, p, 2);
396         escape = substring(msg, p + 1, 1);
397         if (escape == "%")
398             replacement = "%";
399         else if (escape == "\\")
400             replacement = "\\";
401         else if (escape == "n")
402             replacement = "\n";
403         else if (escape == "a")
404             replacement = ftos(floor(self.armorvalue));
405         else if (escape == "h")
406             replacement = ftos(floor(self.health));
407         else if (escape == "l")
408             replacement = NearestLocation(self.origin);
409         else if (escape == "y")
410             replacement = NearestLocation(self.cursor_trace_endpos);
411         else if (escape == "d")
412             replacement = NearestLocation(self.death_origin);
413         else if (escape == "w")
414         {
415             float wep;
416             wep = self.weapon;
417             if (!wep)
418                 wep = self.switchweapon;
419             if (!wep)
420                 wep = self.cnt;
421             replacement = W_Name(wep);
422         }
423         else if (escape == "W")
424         {
425             if (self.items & IT_SHELLS) replacement = "shells";
426             else if (self.items & IT_NAILS) replacement = "bullets";
427             else if (self.items & IT_ROCKETS) replacement = "rockets";
428             else if (self.items & IT_CELLS) replacement = "cells";
429             else replacement = "batteries"; // ;)
430         }
431         else if (escape == "x")
432         {
433             replacement = self.cursor_trace_ent.netname;
434             if (!replacement || !self.cursor_trace_ent)
435                 replacement = "nothing";
436         }
437         else if (escape == "p")
438         {
439             if (self.last_selected_player)
440                 replacement = self.last_selected_player.netname;
441             else
442                 replacement = "(nobody)";
443         }
444         msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
445         p = p + strlen(replacement);
446     }
447     return msg;
448 }
449
450 /*
451 =============
452 GetCvars
453 =============
454 Called with:
455   0:  sends the request
456   >0: receives a cvar from name=argv(f) value=argv(f+1)
457 */
458 void GetCvars_handleString(string thisname, float f, .string field, string name)
459 {
460     if (f < 0)
461     {
462         if (self.field)
463             strunzone(self.field);
464         self.field = string_null;
465     }
466     else if (f > 0)
467     {
468         if (thisname == name)
469         {
470             if (self.field)
471                 strunzone(self.field);
472             self.field = strzone(argv(f + 1));
473         }
474     }
475     else
476         stuffcmd(self, strcat("sendcvar ", name, "\n"));
477 }
478 void GetCvars_handleString_Fixup(string thisname, float f, .string field, string name, string(string) func)
479 {
480     GetCvars_handleString(thisname, f, field, name);
481     if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
482         if (thisname == name)
483         {
484             string s;
485             s = func(strcat1(self.field));
486             if (s != self.field)
487             {
488                 strunzone(self.field);
489                 self.field = strzone(s);
490             }
491         }
492 }
493 void GetCvars_handleFloat(string thisname, float f, .float field, string name)
494 {
495     if (f < 0)
496     {
497     }
498     else if (f > 0)
499     {
500         if (thisname == name)
501             self.field = stof(argv(f + 1));
502     }
503     else
504         stuffcmd(self, strcat("sendcvar ", name, "\n"));
505 }
506 string W_FixWeaponOrder_ForceComplete(string s);
507 string W_FixWeaponOrder_AllowIncomplete(string s);
508 float w_getbestweapon(entity e);
509 void GetCvars(float f)
510 {
511     string s;
512     if (f > 0)
513         s = strcat1(argv(f));
514     GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
515     GetCvars_handleFloat(s, f, cvar_cl_playerdetailreduction, "cl_playerdetailreduction");
516     GetCvars_handleFloat(s, f, cvar_scr_centertime, "scr_centertime");
517     GetCvars_handleFloat(s, f, cvar_cl_shownames, "cl_shownames");
518     GetCvars_handleString(s, f, cvar_g_nexuizversion, "g_nexuizversion");
519     GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
520     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete);
521     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
522     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
523     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
524     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
525     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
526     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
527     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
528     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
529     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
530     GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
531     GetCvars_handleFloat(s, f, cvar_cl_autotaunt, "cl_autotaunt");
532     GetCvars_handleFloat(s, f, cvar_cl_voice_directional, "cl_voice_directional");
533     GetCvars_handleFloat(s, f, cvar_cl_voice_directional_taunt_attenuation, "cl_voice_directional_taunt_attenuation");
534     GetCvars_handleFloat(s, f, cvar_cl_hitsound, "cl_hitsound");
535 #ifdef ALLOW_FORCEMODELS
536     GetCvars_handleFloat(s, f, cvar_cl_forceplayermodels, "cl_forceplayermodels");
537     GetCvars_handleFloat(s, f, cvar_cl_forceplayermodelsfromnexuiz, "cl_forceplayermodelsfromnexuiz");
538 #endif
539     GetCvars_handleFloat(s, f, cvar_cl_gunalign, "cl_gunalign");
540
541
542     // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
543     if (f > 0)
544     {
545         if (s == "cl_weaponpriority")
546             self.switchweapon = w_getbestweapon(self);
547     }
548 }
549
550 float fexists(string f)
551 {
552     float fh;
553     fh = fopen(f, FILE_READ);
554     if (fh < 0)
555         return FALSE;
556     fclose(fh);
557     return TRUE;
558 }
559
560 void backtrace(string msg)
561 {
562     float dev;
563     dev = cvar("developer");
564     cvar_set("developer", "1");
565     dprint("\n");
566     dprint("--- CUT HERE ---\nWARNING: ");
567     dprint(msg);
568     dprint("\n");
569     remove(world); // isn't there any better way to cause a backtrace?
570     dprint("\n--- CUT UNTIL HERE ---\n");
571     cvar_set("developer", ftos(dev));
572 }
573
574 string Team_ColorCode(float teamid)
575 {
576     if (teamid == COLOR_TEAM1)
577         return "^1";
578     else if (teamid == COLOR_TEAM2)
579         return "^4";
580     else if (teamid == COLOR_TEAM3)
581         return "^3";
582     else if (teamid == COLOR_TEAM4)
583         return "^6";
584     else
585         return "^7";
586 }
587 string Team_ColorName(float t)
588 {
589     // fixme: Search for team entities and get their .netname's!
590     if (t == COLOR_TEAM1)
591         return "Red";
592     if (t == COLOR_TEAM2)
593         return "Blue";
594     if (t == COLOR_TEAM3)
595         return "Yellow";
596     if (t == COLOR_TEAM4)
597         return "Pink";
598     return "Neutral";
599 }
600 string Team_ColorNameLowerCase(float t)
601 {
602     // fixme: Search for team entities and get their .netname's!
603     if (t == COLOR_TEAM1)
604         return "red";
605     if (t == COLOR_TEAM2)
606         return "blue";
607     if (t == COLOR_TEAM3)
608         return "yellow";
609     if (t == COLOR_TEAM4)
610         return "pink";
611     return "neutral";
612 }
613
614 #define CENTERPRIO_POINT 1
615 #define CENTERPRIO_SPAM 2
616 #define CENTERPRIO_VOTE 4
617 #define CENTERPRIO_NORMAL 5
618 #define CENTERPRIO_SHIELDING 7
619 #define CENTERPRIO_MAPVOTE 9
620 #define CENTERPRIO_IDLEKICK 50
621 #define CENTERPRIO_ADMIN 99
622 .float centerprint_priority;
623 .float centerprint_expires;
624 void centerprint_atprio(entity e, float prio, string s)
625 {
626     if (intermission_running)
627         if (prio < CENTERPRIO_MAPVOTE)
628             return;
629     if (time > e.centerprint_expires)
630         e.centerprint_priority = 0;
631     if (prio >= e.centerprint_priority)
632     {
633         e.centerprint_priority = prio;
634         if (timeoutStatus == 2)
635             e.centerprint_expires = time + (e.cvar_scr_centertime * TIMEOUT_SLOWMO_VALUE);
636         else
637             e.centerprint_expires = time + e.cvar_scr_centertime;
638         centerprint_builtin(e, s);
639     }
640 }
641 void centerprint_expire(entity e, float prio)
642 {
643     if (prio == e.centerprint_priority)
644     {
645         e.centerprint_priority = 0;
646         centerprint_builtin(e, "");
647     }
648 }
649 void centerprint(entity e, string s)
650 {
651     centerprint_atprio(e, CENTERPRIO_NORMAL, s);
652 }
653
654 // decolorizes and team colors the player name when needed
655 string playername(entity p)
656 {
657     string t;
658     if (teams_matter && !intermission_running && p.classname == "player")
659     {
660         t = Team_ColorCode(p.team);
661         return strcat(t, strdecolorize(p.netname));
662     }
663     else
664         return p.netname;
665 }
666
667 vector randompos(vector m1, vector m2)
668 {
669     local vector v;
670     m2 = m2 - m1;
671     v_x = m2_x * random() + m1_x;
672     v_y = m2_y * random() + m1_y;
673     v_z = m2_z * random() + m1_z;
674     return  v;
675 };
676
677 float g_pickup_shells;
678 float g_pickup_shells_max;
679 float g_pickup_nails;
680 float g_pickup_nails_max;
681 float g_pickup_rockets;
682 float g_pickup_rockets_max;
683 float g_pickup_cells;
684 float g_pickup_cells_max;
685 float g_pickup_fuel;
686 float g_pickup_fuel_jetpack;
687 float g_pickup_fuel_max;
688 float g_pickup_armorsmall;
689 float g_pickup_armorsmall_max;
690 float g_pickup_armormedium;
691 float g_pickup_armormedium_max;
692 float g_pickup_armorbig;
693 float g_pickup_armorbig_max;
694 float g_pickup_armorlarge;
695 float g_pickup_armorlarge_max;
696 float g_pickup_healthsmall;
697 float g_pickup_healthsmall_max;
698 float g_pickup_healthmedium;
699 float g_pickup_healthmedium_max;
700 float g_pickup_healthlarge;
701 float g_pickup_healthlarge_max;
702 float g_pickup_healthmega;
703 float g_pickup_healthmega_max;
704 float g_weaponarena;
705 string g_weaponarena_list;
706 float g_weaponspeedfactor;
707 float g_weaponratefactor;
708 float g_weapondamagefactor;
709 float g_weaponforcefactor;
710
711 float start_weapons;
712 float start_items;
713 float start_ammo_shells;
714 float start_ammo_nails;
715 float start_ammo_rockets;
716 float start_ammo_cells;
717 float start_ammo_fuel;
718 float start_health;
719 float start_armorvalue;
720 float warmup_start_weapons;
721 float warmup_start_ammo_shells;
722 float warmup_start_ammo_nails;
723 float warmup_start_ammo_rockets;
724 float warmup_start_ammo_cells;
725 float warmup_start_ammo_fuel;
726 float warmup_start_health;
727 float warmup_start_armorvalue;
728 float g_weapon_stay;
729
730 entity get_weaponinfo(float w);
731
732 float NixNex_CanChooseWeapon(float wpn);
733 void readplayerstartcvars()
734 {
735     entity e;
736     float i, j, t;
737     string s;
738
739     // initialize starting values for players
740     start_weapons = 0;
741     start_items = 0;
742     start_ammo_shells = 0;
743     start_ammo_nails = 0;
744     start_ammo_rockets = 0;
745     start_ammo_cells = 0;
746     start_health = cvar("g_balance_health_start");
747     start_armorvalue = cvar("g_balance_armor_start");
748
749     g_weaponarena = 0;
750     s = cvar_string("g_weaponarena");
751     if (s == "0")
752     {
753     }
754     else if (s == "all")
755     {
756         g_weaponarena_list = "All Weapons";
757         for (j = WEP_FIRST; j <= WEP_LAST; ++j)
758         {
759             e = get_weaponinfo(j);
760             g_weaponarena |= e.weapons;
761             weapon_action(e.weapon, WR_PRECACHE);
762         }
763     }
764     else if (s == "most")
765     {
766         g_weaponarena_list = "Most Weapons";
767         for (j = WEP_FIRST; j <= WEP_LAST; ++j)
768         {
769             e = get_weaponinfo(j);
770             if (e.spawnflags & WEPSPAWNFLAG_NORMAL)
771             {
772                 g_weaponarena |= e.weapons;
773                 weapon_action(e.weapon, WR_PRECACHE);
774             }
775         }
776     }
777     else if (s == "none")
778     {
779         g_weaponarena_list = "No Weapons";
780         g_weaponarena = WEPBIT_ALL + 1; // this supports no single weapon bit!
781     }
782     else
783     {
784         t = tokenize_console(s);
785         g_weaponarena_list = "";
786         for (i = 0; i < t; ++i)
787         {
788             s = argv(i);
789             for (j = WEP_FIRST; j <= WEP_LAST; ++j)
790             {
791                 e = get_weaponinfo(j);
792                 if (e.netname == s)
793                 {
794                     g_weaponarena |= e.weapons;
795                     weapon_action(e.weapon, WR_PRECACHE);
796                     g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
797                     break;
798                 }
799             }
800             if (j > WEP_LAST)
801             {
802                 print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
803             }
804         }
805         g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
806     }
807
808     if (g_nixnex)
809     {
810         start_weapons = 0;
811         // will be done later
812         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
813             if (NixNex_CanChooseWeapon(i))
814                 weapon_action(i, WR_PRECACHE);
815         if(!cvar("g_use_ammunition"))
816                 start_items |= IT_UNLIMITED_AMMO;
817     }
818     else if (g_weaponarena)
819     {
820         start_weapons = g_weaponarena;
821         start_ammo_rockets = 999;
822         start_ammo_shells = 999;
823         start_ammo_cells = 999;
824         start_ammo_nails = 999;
825         start_ammo_fuel = 999;
826         start_items |= IT_UNLIMITED_AMMO;
827     }
828     else if (g_minstagib)
829     {
830         start_health = 100;
831         start_armorvalue = 0;
832         start_weapons = WEPBIT_MINSTANEX;
833         weapon_action(WEP_MINSTANEX, WR_PRECACHE);
834         start_ammo_cells = cvar("g_minstagib_ammo_start");
835         g_minstagib_invis_alpha = cvar("g_minstagib_invis_alpha");
836         start_ammo_fuel = cvar("g_start_ammo_fuel");
837
838         if (g_minstagib_invis_alpha <= 0)
839             g_minstagib_invis_alpha = -1;
840     }
841     else
842     {
843         if (g_lms)
844         {
845             start_ammo_shells = cvar("g_lms_start_ammo_shells");
846             start_ammo_nails = cvar("g_lms_start_ammo_nails");
847             start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
848             start_ammo_cells = cvar("g_lms_start_ammo_cells");
849             start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
850             start_health = cvar("g_lms_start_health");
851             start_armorvalue = cvar("g_lms_start_armor");
852         }
853         else if (cvar("g_use_ammunition"))
854         {
855             start_ammo_shells = cvar("g_start_ammo_shells");
856             start_ammo_nails = cvar("g_start_ammo_nails");
857             start_ammo_rockets = cvar("g_start_ammo_rockets");
858             start_ammo_cells = cvar("g_start_ammo_cells");
859             start_ammo_fuel = cvar("g_start_ammo_fuel");
860         }
861         else
862         {
863             start_ammo_shells = cvar("g_pickup_shells_max");
864             start_ammo_nails = cvar("g_pickup_nails_max");
865             start_ammo_rockets = cvar("g_pickup_rockets_max");
866             start_ammo_cells = cvar("g_pickup_cells_max");
867             start_ammo_fuel = cvar("g_pickup_fuel_max");
868             start_items |= IT_UNLIMITED_AMMO;
869         }
870
871         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
872         {
873             e = get_weaponinfo(i);
874             if (!(e.weapon))
875                 continue;
876
877             t = cvar(strcat("g_start_weapon_", e.netname));
878
879             if (t < 0) // "default" weapon selection
880             {
881                 if (g_lms)
882                     t = (e.spawnflags & WEPSPAWNFLAG_NORMAL);
883                 else if (g_race || g_cts)
884                     t = (i == WEP_LASER);
885                 else if (g_nexball)
886                     t = 0; // weapon is set a few lines later
887                 else
888                     t = (i == WEP_LASER || i == WEP_SHOTGUN);
889                 if (g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
890                     t += (i == WEP_HOOK);
891             }
892
893             if (g_nexball && i == WEP_PORTO)
894                 t=1;
895
896             if (t)
897             {
898                 start_weapons |= e.weapons;
899                 weapon_action(e.weapon, WR_PRECACHE);
900             }
901         }
902     }
903
904     if (inWarmupStage)
905     {
906         warmup_start_ammo_shells = start_ammo_shells;
907         warmup_start_ammo_nails = start_ammo_nails;
908         warmup_start_ammo_rockets = start_ammo_rockets;
909         warmup_start_ammo_cells = start_ammo_cells;
910         warmup_start_health = start_health;
911         warmup_start_armorvalue = start_armorvalue;
912         warmup_start_weapons = start_weapons;
913
914         if (!g_weaponarena && !g_nixnex && !g_minstagib)
915         {
916             if (cvar("g_use_ammunition"))
917             {
918                 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
919                 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
920                 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
921                 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
922             }
923             warmup_start_health = cvar("g_warmup_start_health");
924             warmup_start_armorvalue = cvar("g_warmup_start_armor");
925             if (cvar("g_warmup_allguns"))
926             {
927                 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
928                 {
929                     e = get_weaponinfo(i);
930                     if (!(e.weapon))
931                         continue;
932                     if (e.spawnflags & WEPSPAWNFLAG_NORMAL)
933                     {
934                         warmup_start_weapons |= e.weapons;
935                         weapon_action(e.weapon, WR_PRECACHE);
936                     }
937                 }
938             }
939         }
940     }
941
942     if (g_jetpack || (g_grappling_hook && (start_weapons & WEPBIT_HOOK)))
943     {
944         g_grappling_hook = 0; // these two can't coexist, as they use the same button
945         start_items |= IT_FUEL_REGEN;
946         start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
947     }
948
949     if (g_jetpack)
950         start_items |= IT_JETPACK;
951
952     if (g_weapon_stay == 2)
953     {
954         if (!start_ammo_shells) start_ammo_shells = g_pickup_shells;
955         if (!start_ammo_nails) start_ammo_nails = g_pickup_nails;
956         if (!start_ammo_cells) start_ammo_cells = g_pickup_cells;
957         if (!start_ammo_rockets) start_ammo_rockets = g_pickup_rockets;
958         if (!start_ammo_fuel) start_ammo_fuel = g_pickup_fuel;
959         if (!warmup_start_ammo_shells) warmup_start_ammo_shells = g_pickup_shells;
960         if (!warmup_start_ammo_nails) warmup_start_ammo_nails = g_pickup_nails;
961         if (!warmup_start_ammo_cells) warmup_start_ammo_cells = g_pickup_cells;
962         if (!warmup_start_ammo_rockets) warmup_start_ammo_rockets = g_pickup_rockets;
963         if (!warmup_start_ammo_fuel) warmup_start_ammo_fuel = g_pickup_fuel;
964     }
965
966     start_ammo_shells = max(0, start_ammo_shells);
967     start_ammo_nails = max(0, start_ammo_nails);
968     start_ammo_cells = max(0, start_ammo_cells);
969     start_ammo_rockets = max(0, start_ammo_rockets);
970     start_ammo_fuel = max(0, start_ammo_fuel);
971
972     warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
973     warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
974     warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
975     warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
976     warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
977 }
978
979 float g_bugrigs;
980 float g_bugrigs_planar_movement;
981 float g_bugrigs_planar_movement_car_jumping;
982 float g_bugrigs_reverse_spinning;
983 float g_bugrigs_reverse_speeding;
984 float g_bugrigs_reverse_stopping;
985 float g_bugrigs_air_steering;
986 float g_bugrigs_angle_smoothing;
987 float g_bugrigs_friction_floor;
988 float g_bugrigs_friction_brake;
989 float g_bugrigs_friction_air;
990 float g_bugrigs_accel;
991 float g_bugrigs_speed_ref;
992 float g_bugrigs_speed_pow;
993 float g_bugrigs_steer;
994
995 float g_touchexplode;
996 float g_touchexplode_radius;
997 float g_touchexplode_damage;
998 float g_touchexplode_edgedamage;
999 float g_touchexplode_force;
1000
1001 void readlevelcvars(void)
1002 {
1003     g_bugrigs = cvar("g_bugrigs");
1004     g_bugrigs_planar_movement = cvar("g_bugrigs_planar_movement");
1005     g_bugrigs_planar_movement_car_jumping = cvar("g_bugrigs_planar_movement_car_jumping");
1006     g_bugrigs_reverse_spinning = cvar("g_bugrigs_reverse_spinning");
1007     g_bugrigs_reverse_speeding = cvar("g_bugrigs_reverse_speeding");
1008     g_bugrigs_reverse_stopping = cvar("g_bugrigs_reverse_stopping");
1009     g_bugrigs_air_steering = cvar("g_bugrigs_air_steering");
1010     g_bugrigs_angle_smoothing = cvar("g_bugrigs_angle_smoothing");
1011     g_bugrigs_friction_floor = cvar("g_bugrigs_friction_floor");
1012     g_bugrigs_friction_brake = cvar("g_bugrigs_friction_brake");
1013     g_bugrigs_friction_air = cvar("g_bugrigs_friction_air");
1014     g_bugrigs_accel = cvar("g_bugrigs_accel");
1015     g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref");
1016     g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
1017     g_bugrigs_steer = cvar("g_bugrigs_steer");
1018
1019     g_touchexplode = cvar("g_touchexplode");
1020     g_touchexplode_radius = cvar("g_touchexplode_radius");
1021     g_touchexplode_damage = cvar("g_touchexplode_damage");
1022     g_touchexplode_edgedamage = cvar("g_touchexplode_edgedamage");
1023     g_touchexplode_force = cvar("g_touchexplode_force");
1024
1025 #ifdef ALLOW_FORCEMODELS
1026     sv_clforceplayermodels = cvar("sv_clforceplayermodels");
1027 #endif
1028     sv_loddistance1 = cvar("sv_loddistance1");
1029     sv_loddistance2 = cvar("sv_loddistance2");
1030         if(sv_loddistance2 <= sv_loddistance1)
1031                 sv_loddistance2 = 1073741824; // enough to turn off LOD 2 reliably
1032     sv_clones = cvar("sv_clones");
1033     sv_cheats = cvar("sv_cheats");
1034     sv_gentle = cvar("sv_gentle");
1035     sv_foginterval = cvar("sv_foginterval");
1036     g_cloaked = cvar("g_cloaked");
1037     g_jump_grunt = cvar("g_jump_grunt");
1038     g_footsteps = cvar("g_footsteps");
1039     g_grappling_hook = cvar("g_grappling_hook");
1040     g_jetpack = cvar("g_jetpack");
1041     g_laserguided_missile = cvar("g_laserguided_missile");
1042     g_midair = cvar("g_midair");
1043     g_minstagib = cvar("g_minstagib");
1044     g_nixnex = cvar("g_nixnex");
1045     g_nixnex_with_laser = cvar("g_nixnex_with_laser");
1046     g_norecoil = cvar("g_norecoil");
1047     g_vampire = cvar("g_vampire");
1048     g_bloodloss = cvar("g_bloodloss");
1049     sv_maxidle = cvar("sv_maxidle");
1050     sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
1051     sv_pogostick = cvar("sv_pogostick");
1052     sv_doublejump = cvar("sv_doublejump");
1053     g_ctf_reverse = cvar("g_ctf_reverse");
1054
1055     inWarmupStage = cvar("g_warmup");
1056     g_warmup_limit = cvar("g_warmup_limit");
1057     g_warmup_allguns = cvar("g_warmup_allguns");
1058     g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
1059
1060     if ((g_race && g_race_qualifying == 2) || g_arena || g_assault || cvar("g_campaign"))
1061         inWarmupStage = 0; // these modes cannot work together, sorry
1062
1063     g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
1064     g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
1065     g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
1066     g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
1067     g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
1068     g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
1069     g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
1070     g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
1071     g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
1072     g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
1073     g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
1074     g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
1075
1076     if (g_minstagib) g_nixnex = g_weaponarena = 0;
1077     if (g_nixnex) g_weaponarena = 0;
1078     g_weaponarena = 0;
1079
1080     g_weaponspeedfactor = cvar("g_weaponspeedfactor");
1081     g_weaponratefactor = cvar("g_weaponratefactor");
1082     g_weapondamagefactor = cvar("g_weapondamagefactor");
1083     g_weaponforcefactor = cvar("g_weaponforcefactor");
1084
1085     g_pickup_shells                    = cvar("g_pickup_shells");
1086     g_pickup_shells_max                = cvar("g_pickup_shells_max");
1087     g_pickup_nails                     = cvar("g_pickup_nails");
1088     g_pickup_nails_max                 = cvar("g_pickup_nails_max");
1089     g_pickup_rockets                   = cvar("g_pickup_rockets");
1090     g_pickup_rockets_max               = cvar("g_pickup_rockets_max");
1091     g_pickup_cells                     = cvar("g_pickup_cells");
1092     g_pickup_cells_max                 = cvar("g_pickup_cells_max");
1093     g_pickup_fuel                     = cvar("g_pickup_fuel");
1094     g_pickup_fuel_jetpack             = cvar("g_pickup_fuel_jetpack");
1095     g_pickup_fuel_max                 = cvar("g_pickup_fuel_max");
1096     g_pickup_armorsmall                = cvar("g_pickup_armorsmall");
1097     g_pickup_armorsmall_max            = cvar("g_pickup_armorsmall_max");
1098     g_pickup_armormedium               = cvar("g_pickup_armormedium");
1099     g_pickup_armormedium_max           = cvar("g_pickup_armormedium_max");
1100     g_pickup_armorbig                  = cvar("g_pickup_armorbig");
1101     g_pickup_armorbig_max              = cvar("g_pickup_armorbig_max");
1102     g_pickup_armorlarge                = cvar("g_pickup_armorlarge");
1103     g_pickup_armorlarge_max            = cvar("g_pickup_armorlarge_max");
1104     g_pickup_healthsmall               = cvar("g_pickup_healthsmall");
1105     g_pickup_healthsmall_max           = cvar("g_pickup_healthsmall_max");
1106     g_pickup_healthmedium              = cvar("g_pickup_healthmedium");
1107     g_pickup_healthmedium_max          = cvar("g_pickup_healthmedium_max");
1108     g_pickup_healthlarge               = cvar("g_pickup_healthlarge");
1109     g_pickup_healthlarge_max           = cvar("g_pickup_healthlarge_max");
1110     g_pickup_healthmega                = cvar("g_pickup_healthmega");
1111     g_pickup_healthmega_max            = cvar("g_pickup_healthmega_max");
1112
1113     g_pinata = cvar("g_pinata");
1114
1115     g_weapon_stay = cvar("g_weapon_stay");
1116     if (!g_weapon_stay && (cvar("deathmatch") == 2))
1117         g_weapon_stay = 1;
1118
1119     if not(inWarmupStage)
1120         game_starttime                 = cvar("g_start_delay");
1121
1122     readplayerstartcvars();
1123 }
1124
1125 /*
1126 // TODO sound pack system
1127 string soundpack;
1128
1129 string precache_sound_builtin (string s) = #19;
1130 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1131 string precache_sound(string s)
1132 {
1133         return precache_sound_builtin(strcat(soundpack, s));
1134 }
1135 void play2(entity e, string filename)
1136 {
1137         stuffcmd(e, strcat("play2 ", soundpack, filename, "\n"));
1138 }
1139 void sound(entity e, float chan, string samp, float vol, float atten)
1140 {
1141         sound_builtin(e, chan, strcat(soundpack, samp), vol, atten);
1142 }
1143 */
1144
1145 // Sound functions
1146 string precache_sound (string s) = #19;
1147 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1148 float precache_sound_index (string s) = #19;
1149
1150 #define SND_VOLUME      1
1151 #define SND_ATTENUATION 2
1152 #define SND_LARGEENTITY 8
1153 #define SND_LARGESOUND  16
1154
1155 float sound_allowed(float dest, entity e)
1156 {
1157     // sounds from world may always pass
1158     for (;;)
1159     {
1160         if (e.classname == "body")
1161             e = e.enemy;
1162         if (e.owner && e.owner != e)
1163             e = e.owner;
1164         else
1165             break;
1166     }
1167     // sounds to self may always pass
1168     if (dest == MSG_ONE)
1169         if (e == msg_entity)
1170             return TRUE;
1171     // sounds by players can be removed
1172     if (cvar("bot_sound_monopoly"))
1173         if (clienttype(e) == CLIENTTYPE_REAL)
1174             return FALSE;
1175     // anything else may pass
1176     return TRUE;
1177 }
1178
1179 void sound(entity e, float chan, string samp, float vol, float atten)
1180 {
1181     if (!sound_allowed(MSG_BROADCAST, e))
1182         return;
1183     sound_builtin(e, chan, samp, vol, atten);
1184 }
1185 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
1186 {
1187     float entno, idx;
1188
1189     if (!sound_allowed(dest, e))
1190         return;
1191
1192     entno = num_for_edict(e);
1193     idx = precache_sound_index(samp);
1194
1195     float sflags;
1196     sflags = 0;
1197
1198     atten = floor(atten * 64);
1199     vol = floor(vol * 255);
1200
1201     if (vol != 255)
1202         sflags |= SND_VOLUME;
1203     if (atten != 64)
1204         sflags |= SND_ATTENUATION;
1205     if (entno >= 8192)
1206         sflags |= SND_LARGEENTITY;
1207     if (idx >= 256)
1208         sflags |= SND_LARGESOUND;
1209
1210     WriteByte(dest, SVC_SOUND);
1211     WriteByte(dest, sflags);
1212     if (sflags & SND_VOLUME)
1213         WriteByte(dest, vol);
1214     if (sflags & SND_ATTENUATION)
1215         WriteByte(dest, atten);
1216     if (sflags & SND_LARGEENTITY)
1217     {
1218         WriteShort(dest, entno);
1219         WriteByte(dest, chan);
1220     }
1221     else
1222     {
1223         WriteShort(dest, entno * 8 + chan);
1224     }
1225     if (sflags & SND_LARGESOUND)
1226         WriteShort(dest, idx);
1227     else
1228         WriteByte(dest, idx);
1229
1230     WriteCoord(dest, o_x);
1231     WriteCoord(dest, o_y);
1232     WriteCoord(dest, o_z);
1233 }
1234 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
1235 {
1236     vector o;
1237
1238     if (!sound_allowed(dest, e))
1239         return;
1240
1241     o = e.origin + 0.5 * (e.mins + e.maxs);
1242     soundtoat(dest, e, o, chan, samp, vol, atten);
1243 }
1244 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
1245 {
1246     soundtoat(MSG_BROADCAST, e, o, chan, samp, vol, atten);
1247 }
1248 void stopsoundto(float dest, entity e, float chan)
1249 {
1250     float entno;
1251
1252     if (!sound_allowed(dest, e))
1253         return;
1254
1255     entno = num_for_edict(e);
1256
1257     if (entno >= 8192)
1258     {
1259         float idx, sflags;
1260         idx = precache_sound_index("misc/null.wav");
1261         sflags = SND_LARGEENTITY;
1262         if (idx >= 256)
1263             sflags |= SND_LARGESOUND;
1264         WriteByte(dest, SVC_SOUND);
1265         WriteByte(dest, sflags);
1266         WriteShort(dest, entno);
1267         WriteByte(dest, chan);
1268         if (sflags & SND_LARGESOUND)
1269             WriteShort(dest, idx);
1270         else
1271             WriteByte(dest, idx);
1272         WriteCoord(dest, e.origin_x);
1273         WriteCoord(dest, e.origin_y);
1274         WriteCoord(dest, e.origin_z);
1275     }
1276     else
1277     {
1278         WriteByte(dest, SVC_STOPSOUND);
1279         WriteShort(dest, entno * 8 + chan);
1280     }
1281 }
1282 void stopsound(entity e, float chan)
1283 {
1284     if (!sound_allowed(MSG_BROADCAST, e))
1285         return;
1286
1287     stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
1288     stopsoundto(MSG_ALL, e, chan); // in case of packet loss
1289 }
1290
1291 void play2(entity e, string filename)
1292 {
1293     //stuffcmd(e, strcat("play2 ", filename, "\n"));
1294     msg_entity = e;
1295     soundtoat(MSG_ONE, world, '0 0 0', CHAN_AUTO, filename, VOL_BASE, ATTN_NONE);
1296 }
1297
1298 .float announcetime;
1299 float announce(entity player, string msg)
1300 {
1301     if (time > player.announcetime)
1302         if (clienttype(player) == CLIENTTYPE_REAL)
1303         {
1304             player.announcetime = time + 0.8;
1305             play2(player, msg);
1306             return TRUE;
1307         }
1308     return FALSE;
1309 }
1310 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
1311 float spamsound(entity e, float chan, string samp, float vol, float atten)
1312 {
1313     if (!sound_allowed(MSG_BROADCAST, e))
1314         return FALSE;
1315
1316     if (time > e.announcetime)
1317     {
1318         e.announcetime = time;
1319         sound(e, chan, samp, vol, atten);
1320         return TRUE;
1321     }
1322     return FALSE;
1323 }
1324
1325 void play2team(float t, string filename)
1326 {
1327     local entity head;
1328
1329     if (cvar("bot_sound_monopoly"))
1330         return;
1331
1332     FOR_EACH_REALPLAYER(head)
1333     {
1334         if (head.team == t)
1335             play2(head, filename);
1336     }
1337 }
1338
1339 void play2all(string samp)
1340 {
1341     if (cvar("bot_sound_monopoly"))
1342         return;
1343
1344     sound(world, CHAN_AUTO, samp, VOL_BASE, ATTN_NONE);
1345 }
1346
1347 void PrecachePlayerSounds(string f);
1348 void precache_all_models(string pattern)
1349 {
1350     float globhandle, i, n;
1351     string f;
1352
1353     globhandle = search_begin(pattern, TRUE, FALSE);
1354     if (globhandle < 0)
1355         return;
1356     n = search_getsize(globhandle);
1357     for (i = 0; i < n; ++i)
1358     {
1359                 //print(search_getfilename(globhandle, i), "\n");
1360                 f = search_getfilename(globhandle, i);
1361                 if(sv_loddistance1)
1362                         precache_model(f);
1363                 if(substring(f, -9,5) == "_lod1")
1364                         continue;
1365                 if(substring(f, -9,5) == "_lod2")
1366                         continue;
1367                 if(!sv_loddistance1)
1368                         precache_model(f);
1369                 PrecachePlayerSounds(strcat(f, ".sounds"));
1370     }
1371     search_end(globhandle);
1372 }
1373
1374 void precache()
1375 {
1376     // gamemode related things
1377     precache_model ("models/misc/chatbubble.spr");
1378     precache_model ("models/misc/teambubble.spr");
1379     if (g_runematch)
1380     {
1381         precache_model ("models/runematch/curse.mdl");
1382         precache_model ("models/runematch/rune.mdl");
1383     }
1384
1385 #ifdef TTURRETS_ENABLED
1386     if (cvar("g_turrets"))
1387         turrets_precash();
1388 #endif
1389
1390     // Precache all player models if desired
1391     if (cvar("sv_precacheplayermodels"))
1392     {
1393         PrecachePlayerSounds("sound/player/default.sounds");
1394         precache_all_models("models/player/*.zym");
1395         precache_all_models("models/player/*.dpm");
1396         precache_all_models("models/player/*.md3");
1397         precache_all_models("models/player/*.psk");
1398         //precache_model("models/player/carni.zym");
1399         //precache_model("models/player/crash.zym");
1400         //precache_model("models/player/grunt.zym");
1401         //precache_model("models/player/headhunter.zym");
1402         //precache_model("models/player/insurrectionist.zym");
1403         //precache_model("models/player/jeandarc.zym");
1404         //precache_model("models/player/lurk.zym");
1405         //precache_model("models/player/lycanthrope.zym");
1406         //precache_model("models/player/marine.zym");
1407         //precache_model("models/player/nexus.zym");
1408         //precache_model("models/player/pyria.zym");
1409         //precache_model("models/player/shock.zym");
1410         //precache_model("models/player/skadi.zym");
1411         //precache_model("models/player/specop.zym");
1412         //precache_model("models/player/visitant.zym");
1413     }
1414
1415     if (cvar("sv_defaultcharacter"))
1416     {
1417         string s;
1418         s = cvar_string("sv_defaultplayermodel_red");
1419         if (s != "")
1420         {
1421             precache_model(s);
1422             PrecachePlayerSounds(strcat(s, ".sounds"));
1423         }
1424         s = cvar_string("sv_defaultplayermodel_blue");
1425         if (s != "")
1426         {
1427             precache_model(s);
1428             PrecachePlayerSounds(strcat(s, ".sounds"));
1429         }
1430         s = cvar_string("sv_defaultplayermodel_yellow");
1431         if (s != "")
1432         {
1433             precache_model(s);
1434             PrecachePlayerSounds(strcat(s, ".sounds"));
1435         }
1436         s = cvar_string("sv_defaultplayermodel_pink");
1437         if (s != "")
1438         {
1439             precache_model(s);
1440             PrecachePlayerSounds(strcat(s, ".sounds"));
1441         }
1442         s = cvar_string("sv_defaultplayermodel");
1443         if (s != "")
1444         {
1445             precache_model(s);
1446             PrecachePlayerSounds(strcat(s, ".sounds"));
1447         }
1448     }
1449
1450     if (g_footsteps)
1451     {
1452         PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1453         PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1454     }
1455
1456     // gore and miscellaneous sounds
1457     //precache_sound ("misc/h2ohit.wav");
1458     precache_model ("models/hook.md3");
1459     precache_sound ("misc/armorimpact.wav");
1460     precache_sound ("misc/bodyimpact1.wav");
1461     precache_sound ("misc/bodyimpact2.wav");
1462     precache_sound ("misc/gib.wav");
1463     precache_sound ("misc/gib_splat01.wav");
1464     precache_sound ("misc/gib_splat02.wav");
1465     precache_sound ("misc/gib_splat03.wav");
1466     precache_sound ("misc/gib_splat04.wav");
1467     precache_sound ("misc/hit.wav");
1468     PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1469     PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1470     precache_sound ("misc/null.wav");
1471     precache_sound ("misc/spawn.wav");
1472     precache_sound ("misc/talk.wav");
1473     precache_sound ("misc/teleport.wav");
1474     precache_sound ("misc/poweroff.wav");
1475     precache_sound ("player/lava.wav");
1476     precache_sound ("player/slime.wav");
1477
1478     if (g_jetpack)
1479         precache_sound ("misc/jetpack_fly.wav");
1480
1481     // announcer sounds - male
1482     precache_sound ("announcer/male/electrobitch.wav");
1483     precache_sound ("announcer/male/airshot.wav");
1484     precache_sound ("announcer/male/03kills.wav");
1485     precache_sound ("announcer/male/05kills.wav");
1486     precache_sound ("announcer/male/10kills.wav");
1487     precache_sound ("announcer/male/15kills.wav");
1488     precache_sound ("announcer/male/20kills.wav");
1489     precache_sound ("announcer/male/25kills.wav");
1490     precache_sound ("announcer/male/30kills.wav");
1491     precache_sound ("announcer/male/botlike.wav");
1492     precache_sound ("announcer/male/yoda.wav");
1493     precache_sound ("announcer/male/amazing.wav");
1494     precache_sound ("announcer/male/awesome.wav");
1495     precache_sound ("announcer/male/headshot.wav");
1496     precache_sound ("announcer/male/impressive.wav");
1497
1498     // announcer sounds - robotic
1499     precache_sound ("announcer/robotic/prepareforbattle.wav");
1500     precache_sound ("announcer/robotic/begin.wav");
1501     precache_sound ("announcer/robotic/timeoutcalled.wav");
1502     precache_sound ("announcer/robotic/1fragleft.wav");
1503     precache_sound ("announcer/robotic/2fragsleft.wav");
1504     precache_sound ("announcer/robotic/3fragsleft.wav");
1505     if (g_minstagib)
1506     {
1507         precache_sound ("announcer/robotic/lastsecond.wav");
1508         precache_sound ("announcer/robotic/narrowly.wav");
1509     }
1510
1511     precache_model ("models/sprites/0.spr32");
1512     precache_model ("models/sprites/1.spr32");
1513     precache_model ("models/sprites/2.spr32");
1514     precache_model ("models/sprites/3.spr32");
1515     precache_model ("models/sprites/4.spr32");
1516     precache_model ("models/sprites/5.spr32");
1517     precache_model ("models/sprites/6.spr32");
1518     precache_model ("models/sprites/7.spr32");
1519     precache_model ("models/sprites/8.spr32");
1520     precache_model ("models/sprites/9.spr32");
1521     precache_model ("models/sprites/10.spr32");
1522     precache_sound ("announcer/robotic/1.wav");
1523     precache_sound ("announcer/robotic/2.wav");
1524     precache_sound ("announcer/robotic/3.wav");
1525     precache_sound ("announcer/robotic/4.wav");
1526     precache_sound ("announcer/robotic/5.wav");
1527     precache_sound ("announcer/robotic/6.wav");
1528     precache_sound ("announcer/robotic/7.wav");
1529     precache_sound ("announcer/robotic/8.wav");
1530     precache_sound ("announcer/robotic/9.wav");
1531     precache_sound ("announcer/robotic/10.wav");
1532
1533     // common weapon precaches
1534     precache_sound ("weapons/weapon_switch.wav");
1535     precache_sound ("weapons/weaponpickup.wav");
1536     precache_sound ("weapons/unavailable.wav");
1537     if (g_grappling_hook)
1538     {
1539         precache_sound ("weapons/hook_fire.wav"); // hook
1540         precache_sound ("weapons/hook_impact.wav"); // hook
1541     }
1542
1543     if (cvar("sv_precacheweapons") || g_nixnex)
1544     {
1545         //precache weapon models/sounds
1546         local float wep;
1547         wep = WEP_FIRST;
1548         while (wep <= WEP_LAST)
1549         {
1550             weapon_action(wep, WR_PRECACHE);
1551             wep = wep + 1;
1552         }
1553     }
1554
1555     precache_model("models/elaser.mdl");
1556     precache_model("models/laser.mdl");
1557     precache_model("models/ebomb.mdl");
1558
1559 #if 0
1560     // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1561
1562     if (!self.noise && self.music) // quake 3 uses the music field
1563         self.noise = self.music;
1564
1565     // plays music for the level if there is any
1566     if (self.noise)
1567     {
1568         precache_sound (self.noise);
1569         ambientsound ('0 0 0', self.noise, VOL_BASE, ATTN_NONE);
1570     }
1571 #endif
1572 }
1573
1574 // sorry, but using \ in macros breaks line numbers
1575 #define WRITESPECTATABLE_MSG_ONE_VARNAME(varname,statement) entity varname; varname = msg_entity; FOR_EACH_REALCLIENT(msg_entity) if(msg_entity == varname || (msg_entity.classname == STR_SPECTATOR && msg_entity.enemy == varname)) statement msg_entity = varname
1576 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
1577 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
1578
1579 vector ExactTriggerHit_mins;
1580 vector ExactTriggerHit_maxs;
1581 float ExactTriggerHit_Recurse()
1582 {
1583     float s;
1584     entity se;
1585     float f;
1586
1587     tracebox('0 0 0', ExactTriggerHit_mins, ExactTriggerHit_maxs, '0 0 0', MOVE_NORMAL, other);
1588     if not(trace_ent)
1589         return 0;
1590     if (trace_ent == self)
1591         return 1;
1592
1593     se = trace_ent;
1594     s = se.solid;
1595     se.solid = SOLID_NOT;
1596     f = ExactTriggerHit_Recurse();
1597     se.solid = s;
1598
1599     return f;
1600 }
1601
1602 float ExactTriggerHit()
1603 {
1604     float f, s;
1605
1606     if not(self.modelindex)
1607         return 1;
1608
1609     s = self.solid;
1610     self.solid = SOLID_BSP;
1611     ExactTriggerHit_mins = other.absmin;
1612     ExactTriggerHit_maxs = other.absmax;
1613     f = ExactTriggerHit_Recurse();
1614     self.solid = s;
1615
1616     return f;
1617 }
1618
1619 // WARNING: this kills the trace globals
1620 #define EXACTTRIGGER_TOUCH if not(ExactTriggerHit()) return
1621 #define EXACTTRIGGER_INIT  InitSolidBSPTrigger(); self.solid = SOLID_TRIGGER
1622
1623 #define INITPRIO_FIRST              0
1624 #define INITPRIO_GAMETYPE           0
1625 #define INITPRIO_GAMETYPE_FALLBACK  1
1626 #define INITPRIO_CVARS              5
1627 #define INITPRIO_FINDTARGET        10
1628 #define INITPRIO_DROPTOFLOOR       20
1629 #define INITPRIO_SETLOCATION       90
1630 #define INITPRIO_LINKDOORS         91
1631 #define INITPRIO_LAST              99
1632
1633 .void(void) initialize_entity;
1634 .float initialize_entity_order;
1635 .entity initialize_entity_next;
1636 entity initialize_entity_first;
1637
1638 void make_safe_for_remove(entity e)
1639 {
1640     if (e.initialize_entity)
1641     {
1642         entity ent, prev;
1643         for (ent = initialize_entity_first; ent; )
1644         {
1645             if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1646             {
1647                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1648                 // skip it in linked list
1649                 if (prev)
1650                 {
1651                     prev.initialize_entity_next = ent.initialize_entity_next;
1652                     ent = prev.initialize_entity_next;
1653                 }
1654                 else
1655                 {
1656                     initialize_entity_first = ent.initialize_entity_next;
1657                     ent = initialize_entity_first;
1658                 }
1659             }
1660             else
1661             {
1662                 prev = ent;
1663                 ent = ent.initialize_entity_next;
1664             }
1665         }
1666     }
1667 }
1668
1669 void objerror(string s)
1670 {
1671     make_safe_for_remove(self);
1672     objerror_builtin(s);
1673 }
1674
1675 void remove_unsafely(entity e)
1676 {
1677     remove_builtin(e);
1678 }
1679
1680 void remove_safely(entity e)
1681 {
1682     make_safe_for_remove(e);
1683     remove_builtin(e);
1684 }
1685
1686 void InitializeEntity(entity e, void(void) func, float order)
1687 {
1688     entity prev, cur;
1689
1690     if (!e || e.initialize_entity)
1691     {
1692         // make a proxy initializer entity
1693         entity e_old;
1694         e_old = e;
1695         e = spawn();
1696         e.classname = "initialize_entity";
1697         e.enemy = e_old;
1698     }
1699
1700     e.initialize_entity = func;
1701     e.initialize_entity_order = order;
1702
1703     cur = initialize_entity_first;
1704     for (;;)
1705     {
1706         if (!cur || cur.initialize_entity_order > order)
1707         {
1708             // insert between prev and cur
1709             if (prev)
1710                 prev.initialize_entity_next = e;
1711             else
1712                 initialize_entity_first = e;
1713             e.initialize_entity_next = cur;
1714             return;
1715         }
1716         prev = cur;
1717         cur = cur.initialize_entity_next;
1718     }
1719 }
1720 void InitializeEntitiesRun()
1721 {
1722     entity startoflist;
1723     startoflist = initialize_entity_first;
1724     initialize_entity_first = world;
1725     for (self = startoflist; self; )
1726     {
1727         entity e;
1728         var void(void) func;
1729         e = self.initialize_entity_next;
1730         func = self.initialize_entity;
1731         self.initialize_entity_order = 0;
1732         self.initialize_entity = func_null;
1733         self.initialize_entity_next = world;
1734         if (self.classname == "initialize_entity")
1735         {
1736             entity e_old;
1737             e_old = self.enemy;
1738             remove_builtin(self);
1739             self = e_old;
1740         }
1741         //dprint("Delayed initialization: ", self.classname, "\n");
1742         func();
1743         self = e;
1744     }
1745 }
1746
1747 .float uncustomizeentityforclient_set;
1748 .void(void) uncustomizeentityforclient;
1749 void(void) SUB_Nullpointer = #0;
1750 void UncustomizeEntitiesRun()
1751 {
1752     entity oldself;
1753     oldself = self;
1754     for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1755         self.uncustomizeentityforclient();
1756     self = oldself;
1757 }
1758 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1759 {
1760     e.customizeentityforclient = customizer;
1761     e.uncustomizeentityforclient = uncustomizer;
1762     e.uncustomizeentityforclient_set = (uncustomizer != SUB_Nullpointer);
1763 }
1764
1765 .float nottargeted;
1766 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1767
1768 void() SUB_Remove;
1769 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1770 {
1771     vector mi, ma;
1772
1773     if (e.classname == "")
1774         e.classname = "net_linked";
1775
1776     if (e.model == "" || self.modelindex == 0)
1777     {
1778         mi = e.mins;
1779         ma = e.maxs;
1780         setmodel(e, "null");
1781         setsize(e, mi, ma);
1782     }
1783
1784     e.SendEntity = sendfunc;
1785     e.SendFlags = 0xFFFFFF;
1786
1787     if (!docull)
1788         e.effects |= EF_NODEPTHTEST;
1789
1790     if (dt)
1791     {
1792         e.nextthink = time + dt;
1793         e.think = SUB_Remove;
1794     }
1795 }
1796
1797 void adaptor_think2touch()
1798 {
1799     entity o;
1800     o = other;
1801     other = world;
1802     self.touch();
1803     other = o;
1804 }
1805
1806 void adaptor_think2use()
1807 {
1808     entity o, a;
1809     o = other;
1810     a = activator;
1811     activator = world;
1812     other = world;
1813     self.use();
1814     other = o;
1815     activator = a;
1816 }
1817
1818 // deferred dropping
1819 void DropToFloor_Handler()
1820 {
1821     droptofloor_builtin();
1822     self.dropped_origin = self.origin;
1823 }
1824
1825 void droptofloor()
1826 {
1827     InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1828 }
1829
1830
1831
1832 float trace_hits_box_a0, trace_hits_box_a1;
1833
1834 float trace_hits_box_1d(float end, float thmi, float thma)
1835 {
1836     if (end == 0)
1837     {
1838         // just check if x is in range
1839         if (0 < thmi)
1840             return FALSE;
1841         if (0 > thma)
1842             return FALSE;
1843     }
1844     else
1845     {
1846         // do the trace with respect to x
1847         // 0 -> end has to stay in thmi -> thma
1848         trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1849         trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1850         if (trace_hits_box_a0 > trace_hits_box_a1)
1851             return FALSE;
1852     }
1853     return TRUE;
1854 }
1855
1856 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1857 {
1858     end -= start;
1859     thmi -= start;
1860     thma -= start;
1861     // now it is a trace from 0 to end
1862
1863     trace_hits_box_a0 = 0;
1864     trace_hits_box_a1 = 1;
1865
1866     if (!trace_hits_box_1d(end_x, thmi_x, thma_x))
1867         return FALSE;
1868     if (!trace_hits_box_1d(end_y, thmi_y, thma_y))
1869         return FALSE;
1870     if (!trace_hits_box_1d(end_z, thmi_z, thma_z))
1871         return FALSE;
1872
1873     return TRUE;
1874 }
1875
1876 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1877 {
1878     return trace_hits_box(start, end, thmi - ma, thma - mi);
1879 }
1880
1881 float SUB_NoImpactCheck()
1882 {
1883         // zero hitcontents = this is not the real impact, but either the
1884         // mirror-impact of something hitting the projectile instead of the
1885         // projectile hitting the something, or a touchareagrid one. Neither of
1886         // these stop the projectile from moving, so...
1887         if(trace_dphitcontents == 0)
1888         {
1889                 dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
1890                 checkclient();
1891         }
1892     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1893         return 1;
1894     if (other == world && self.size != '0 0 0')
1895     {
1896         vector tic;
1897         tic = self.velocity * sys_ticrate;
1898         tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1899         traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1900         if (trace_fraction >= 1)
1901         {
1902             dprint("Odd... did not hit...?\n");
1903         }
1904         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1905         {
1906             dprint("Detected and prevented the sky-grapple bug.\n");
1907             return 1;
1908         }
1909     }
1910
1911     return 0;
1912 }
1913
1914 #define SUB_OwnerCheck() (other && (other == self.owner))
1915
1916 #define PROJECTILE_TOUCH do { if(SUB_OwnerCheck()) return; if(SUB_NoImpactCheck()) { remove(self); return; } if(trace_ent && trace_ent.solid > SOLID_TRIGGER) UpdateCSQCProjectileNextFrame(self); } while(0)
1917
1918 float MAX_IPBAN_URIS = 16;
1919
1920 float URI_GET_DISCARD   = 0;
1921 float URI_GET_IPBAN     = 1;
1922 float URI_GET_IPBAN_END = 16;
1923
1924 void URI_Get_Callback(float id, float status, string data)
1925 {
1926     dprint("Received HTTP request data for id ", ftos(id), "; status is ", ftos(status), "\nData is:\n");
1927     dprint(data);
1928     dprint("\nEnd of data.\n");
1929
1930     if (id == URI_GET_DISCARD)
1931     {
1932         // discard
1933     }
1934     else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1935     {
1936         // online ban list
1937         OnlineBanList_URI_Get_Callback(id, status, data);
1938     }
1939     else
1940     {
1941         print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1942     }
1943 }
1944
1945 void print_to(entity e, string s)
1946 {
1947     if (e)
1948         sprint(e, strcat(s, "\n"));
1949     else
1950         print(s, "\n");
1951 }
1952
1953 string getrecords()
1954 {
1955     float rec;
1956     string h;
1957     float r;
1958     float i;
1959     string s;
1960
1961     rec = 0;
1962
1963     s = "";
1964
1965     if (g_ctf)
1966     {
1967         for (i = 0; i < MapInfo_count; ++i)
1968         {
1969             if (MapInfo_Get_ByID(i))
1970             {
1971                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
1972                 if (r == 0)
1973                     continue;
1974                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
1975                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
1976                 ++rec;
1977             }
1978         }
1979     }
1980
1981     if (g_race)
1982     {
1983         for (i = 0; i < MapInfo_count; ++i)
1984         {
1985             if (MapInfo_Get_ByID(i))
1986             {
1987                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/time")));
1988                 if (r == 0)
1989                     continue;
1990                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/netname"));
1991                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, mmsss(r)), " ", h, "\n");
1992                 ++rec;
1993             }
1994         }
1995     }
1996
1997     if (g_cts)
1998     {
1999         for (i = 0; i < MapInfo_count; ++i)
2000         {
2001             if (MapInfo_Get_ByID(i))
2002             {
2003                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/ctsrecord/time")));
2004                 if (r == 0)
2005                     continue;
2006                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/ctsrecord/netname"));
2007                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, mmsss(r)), " ", h, "\n");
2008                 ++rec;
2009             }
2010         }
2011     }
2012
2013     MapInfo_ClearTemps();
2014
2015     if (s == "")
2016         return "No records are available on this server.\n";
2017     else
2018         return strcat("Records on this server:\n", s);
2019 }
2020
2021 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
2022 {
2023     float m, i;
2024     vector start, org, delta, end, enddown, mstart;
2025
2026     m = e.dphitcontentsmask;
2027     e.dphitcontentsmask = goodcontents | badcontents;
2028
2029     org = world.mins;
2030     delta = world.maxs - world.mins;
2031
2032     for (i = 0; i < attempts; ++i)
2033     {
2034         start_x = org_x + random() * delta_x;
2035         start_y = org_y + random() * delta_y;
2036         start_z = org_z + random() * delta_z;
2037
2038         // rule 1: start inside world bounds, and outside
2039         // solid, and don't start from somewhere where you can
2040         // fall down to evil
2041         tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta_z, MOVE_NORMAL, e);
2042         if (trace_fraction >= 1)
2043             continue;
2044         if (trace_startsolid)
2045             continue;
2046         if (trace_dphitcontents & badcontents)
2047             continue;
2048         if (trace_dphitq3surfaceflags & badsurfaceflags)
2049             continue;
2050
2051         // rule 2: if we are too high, lower the point
2052         if (trace_fraction * delta_z > maxaboveground)
2053             start = trace_endpos + '0 0 1' * maxaboveground;
2054         enddown = trace_endpos;
2055
2056         // rule 3: make sure we aren't outside the map. This only works
2057         // for somewhat well formed maps. A good rule of thumb is that
2058         // the map should have a convex outside hull.
2059         // these can be traceLINES as we already verified the starting box
2060         mstart = start + 0.5 * (e.mins + e.maxs);
2061         traceline(mstart, mstart + '1 0 0' * delta_x, MOVE_NORMAL, e);
2062         if (trace_fraction >= 1)
2063             continue;
2064         traceline(mstart, mstart - '1 0 0' * delta_x, MOVE_NORMAL, e);
2065         if (trace_fraction >= 1)
2066             continue;
2067         traceline(mstart, mstart + '0 1 0' * delta_y, MOVE_NORMAL, e);
2068         if (trace_fraction >= 1)
2069             continue;
2070         traceline(mstart, mstart - '0 1 0' * delta_y, MOVE_NORMAL, e);
2071         if (trace_fraction >= 1)
2072             continue;
2073         traceline(mstart, mstart + '0 0 1' * delta_z, MOVE_NORMAL, e);
2074         if (trace_fraction >= 1)
2075             continue;
2076
2077         // find a random vector to "look at"
2078         end_x = org_x + random() * delta_x;
2079         end_y = org_y + random() * delta_y;
2080         end_z = org_z + random() * delta_z;
2081         end = start + normalize(end - start) * vlen(delta);
2082
2083         // rule 4: start TO end must not be too short
2084         tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
2085         if (trace_startsolid)
2086             continue;
2087         if (trace_fraction < minviewdistance / vlen(delta))
2088             continue;
2089
2090         // rule 5: don't want to look at sky
2091         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
2092             continue;
2093
2094         // rule 6: we must not end up in trigger_hurt
2095         if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
2096         {
2097             dprint("trigger_hurt! ouch! and nothing else could find it!\n");
2098             continue;
2099         }
2100
2101         break;
2102     }
2103
2104     e.dphitcontentsmask = m;
2105
2106     if (i < attempts)
2107     {
2108         setorigin(e, start);
2109         e.angles = vectoangles(end - start);
2110         dprint("Needed ", ftos(i + 1), " attempts\n");
2111         return TRUE;
2112     }
2113     else
2114         return FALSE;
2115 }
2116
2117 void zcurveparticles(float effectno, vector start, vector end, float end_dz, float spd)
2118 {
2119     WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
2120     WriteByte(MSG_BROADCAST, TE_CSQC_ZCURVEPARTICLES);
2121     WriteShort(MSG_BROADCAST, effectno);
2122     WriteCoord(MSG_BROADCAST, start_x);
2123     WriteCoord(MSG_BROADCAST, start_y);
2124     WriteCoord(MSG_BROADCAST, start_z);
2125     WriteCoord(MSG_BROADCAST, end_x);
2126     WriteCoord(MSG_BROADCAST, end_y);
2127     WriteCoord(MSG_BROADCAST, end_z);
2128     WriteCoord(MSG_BROADCAST, end_dz);
2129     WriteShort(MSG_BROADCAST, spd / 16);
2130 }
2131
2132 void zcurveparticles_from_tracetoss(float effectno, vector start, vector end, vector vel)
2133 {
2134     float end_dz;
2135     vector vecxy, velxy;
2136
2137     vecxy = end - start;
2138     vecxy_z = 0;
2139     velxy = vel;
2140     velxy_z = 0;
2141
2142     if (vlen(velxy) < 0.000001 * fabs(vel_z))
2143     {
2144         trailparticles(world, effectno, start, end);
2145         return;
2146     }
2147
2148     end_dz = vlen(vecxy) / vlen(velxy) * vel_z - (end_z - start_z);
2149     zcurveparticles(effectno, start, end, end_dz, vlen(vel));
2150 }
2151
2152 string GetGametype(); // g_world.qc
2153 void write_recordmarker(entity pl, float tstart, float dt)
2154 {
2155     GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
2156
2157     // also write a marker into demo files for demotc-race-record-extractor to find
2158     stuffcmd(pl,
2159              strcat(
2160                  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", mmsss(dt * 10)),
2161                  " ", ftos(tstart), " ", ftos(dt), "\n"));
2162 }
2163
2164 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
2165 {
2166         string s;
2167         vector v;
2168
2169         if (cvar("g_shootfromclient"))
2170         {
2171                 switch(self.owner.cvar_cl_gunalign)
2172                 {
2173                         case 1: // right
2174                                 break;
2175
2176                         case 2: // left
2177                                 vecs_y = -vecs_y;
2178                                 break;
2179
2180                         default:
2181                         case 3: // center
2182                                 vecs_y = 0;
2183                                 vecs_z -= 4;
2184                                 break;
2185                 }
2186         }
2187         else if (cvar("g_shootfromeye"))
2188         {
2189                 if (visual)
2190                 {
2191                         vecs_y = 0;
2192                         vecs_z -= 4;
2193                 }
2194                 else
2195                 {
2196                         vecs_y = 0;
2197                         vecs_z = 0;
2198                 }
2199         }
2200         else if (cvar("g_shootfromcenter"))
2201         {
2202                 vecs_y = 0;
2203                 vecs_z -= 4;
2204         }
2205         else if ((s = cvar_string("g_shootfromfixedorigin")) != "")
2206         {
2207                 v = stov(s);
2208                 if (y_is_right)
2209                         v_y = -v_y;
2210                 if (v_x != 0)
2211                         vecs_x = v_x;
2212                 vecs_y = v_y;
2213                 vecs_z = v_z;
2214         }
2215         return vecs;
2216 }
2217
2218
2219
2220 void attach_sameorigin(entity e, entity to, string tag)
2221 {
2222     vector org, t_forward, t_left, t_up, e_forward, e_up;
2223     vector org0, ang0;
2224     float tagscale;
2225
2226     ang0 = e.angles;
2227     org0 = e.origin;
2228
2229     org = e.origin - gettaginfo(to, gettagindex(to, tag));
2230     tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
2231     t_forward = v_forward * tagscale;
2232     t_left = v_right * -tagscale;
2233     t_up = v_up * tagscale;
2234
2235     e.origin_x = org * t_forward;
2236     e.origin_y = org * t_left;
2237     e.origin_z = org * t_up;
2238
2239     // current forward and up directions
2240     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2241         e.angles_x = -e.angles_x;
2242     fixedmakevectors(e.angles);
2243
2244     // untransform forward, up!
2245     e_forward_x = v_forward * t_forward;
2246     e_forward_y = v_forward * t_left;
2247     e_forward_z = v_forward * t_up;
2248     e_up_x = v_up * t_forward;
2249     e_up_y = v_up * t_left;
2250     e_up_z = v_up * t_up;
2251
2252     e.angles = fixedvectoangles2(e_forward, e_up);
2253     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2254         e.angles_x = -e.angles_x;
2255
2256     setattachment(e, to, tag);
2257     setorigin(e, e.origin);
2258 }
2259
2260 void detach_sameorigin(entity e)
2261 {
2262     vector org;
2263     org = gettaginfo(e, 0);
2264     e.angles = fixedvectoangles2(v_forward, v_up);
2265     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2266         e.angles_x = -e.angles_x;
2267     e.origin = org;
2268     setattachment(e, world, "");
2269     setorigin(e, e.origin);
2270 }
2271
2272 void follow_sameorigin(entity e, entity to)
2273 {
2274     e.movetype = MOVETYPE_FOLLOW; // make the hole follow
2275     e.aiment = to; // make the hole follow bmodel
2276     e.punchangle = to.angles; // the original angles of bmodel
2277     e.view_ofs = e.origin - to.origin; // relative origin
2278     e.v_angle = e.angles - to.angles; // relative angles
2279 }
2280
2281 void unfollow_sameorigin(entity e)
2282 {
2283     e.movetype = MOVETYPE_NONE;
2284 }
2285
2286 entity gettaginfo_relative_ent;
2287 vector gettaginfo_relative(entity e, float tag)
2288 {
2289     if (!gettaginfo_relative_ent)
2290     {
2291         gettaginfo_relative_ent = spawn();
2292         gettaginfo_relative_ent.effects = EF_NODRAW;
2293     }
2294     gettaginfo_relative_ent.model = e.model;
2295     gettaginfo_relative_ent.modelindex = e.modelindex;
2296     gettaginfo_relative_ent.frame = e.frame;
2297     return gettaginfo(gettaginfo_relative_ent, tag);
2298 }
2299
2300 void SoundEntity_StartSound(entity pl, float chan, string samp, float vol, float attn)
2301 {
2302     float p;
2303     p = pow(2, chan);
2304     if (pl.soundentity.cnt & p)
2305         return;
2306     soundtoat(MSG_ALL, pl.soundentity, gettaginfo(pl.soundentity, 0), chan, samp, vol, attn);
2307     pl.soundentity.cnt |= p;
2308 }
2309
2310 void SoundEntity_StopSound(entity pl, float chan)
2311 {
2312     float p;
2313     p = pow(2, chan);
2314     if (pl.soundentity.cnt & p)
2315     {
2316         stopsoundto(MSG_ALL, pl.soundentity, chan);
2317         pl.soundentity.cnt &~= p;
2318     }
2319 }
2320
2321 void SoundEntity_Attach(entity pl)
2322 {
2323     pl.soundentity = spawn();
2324     pl.soundentity.classname = "soundentity";
2325     pl.soundentity.owner = pl;
2326     setattachment(pl.soundentity, pl, "");
2327     setmodel(pl.soundentity, "null");
2328 }
2329
2330 void SoundEntity_Detach(entity pl)
2331 {
2332     float i;
2333     for (i = 0; i <= 7; ++i)
2334         SoundEntity_StopSound(pl, i);
2335 }
2336
2337
2338 float ParseCommandPlayerSlotTarget_firsttoken;
2339 entity GetCommandPlayerSlotTargetFromTokenizedCommand(float tokens, float idx) // idx = start index
2340 {
2341     string s;
2342     entity e, head;
2343     float n;
2344
2345     s = string_null;
2346
2347     ParseCommandPlayerSlotTarget_firsttoken = -1;
2348
2349     if (tokens > idx)
2350     {
2351         if (substring(argv(idx), 0, 1) == "#")
2352         {
2353             s = substring(argv(idx), 1, -1);
2354             ++idx;
2355             if (s == "")
2356                 if (tokens > idx)
2357                 {
2358                     s = argv(idx);
2359                     ++idx;
2360                 }
2361             if (s == ftos(stof(s)))
2362             {
2363                 e = edict_num(stof(s));
2364                 if (e.flags & FL_CLIENT)
2365                 {
2366                     ParseCommandPlayerSlotTarget_firsttoken = idx;
2367                     return e;
2368                 }
2369             }
2370         }
2371         else
2372         {
2373             // it must be a nick name
2374             s = argv(idx);
2375             ++idx;
2376
2377             n = 0;
2378             FOR_EACH_CLIENT(head)
2379             if (head.netname == s)
2380             {
2381                 e = head;
2382                 ++n;
2383             }
2384             if (n == 1)
2385             {
2386                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2387                 return e;
2388             }
2389
2390             s = strdecolorize(s);
2391             n = 0;
2392             FOR_EACH_CLIENT(head)
2393             if (strdecolorize(head.netname) == s)
2394             {
2395                 e = head;
2396                 ++n;
2397             }
2398             if (n == 1)
2399             {
2400                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2401                 return e;
2402             }
2403         }
2404     }
2405
2406     return world;
2407 }