]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/miscfunctions.qc
LOD (cl_playerdetailreduction) enabled now, master switch sv_loddistance1 (also turns...
[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     GetCvars_handleFloat(s, f, cvar_cl_forceplayermodels, "cl_forceplayermodels");
536     GetCvars_handleFloat(s, f, cvar_cl_forceplayermodelsfromnexuiz, "cl_forceplayermodelsfromnexuiz");
537     GetCvars_handleFloat(s, f, cvar_cl_gunalign, "cl_gunalign");
538
539
540     // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
541     if (f > 0)
542     {
543         if (s == "cl_weaponpriority")
544             self.switchweapon = w_getbestweapon(self);
545     }
546 }
547
548 float fexists(string f)
549 {
550     float fh;
551     fh = fopen(f, FILE_READ);
552     if (fh < 0)
553         return FALSE;
554     fclose(fh);
555     return TRUE;
556 }
557
558 void backtrace(string msg)
559 {
560     float dev;
561     dev = cvar("developer");
562     cvar_set("developer", "1");
563     dprint("\n");
564     dprint("--- CUT HERE ---\nWARNING: ");
565     dprint(msg);
566     dprint("\n");
567     remove(world); // isn't there any better way to cause a backtrace?
568     dprint("\n--- CUT UNTIL HERE ---\n");
569     cvar_set("developer", ftos(dev));
570 }
571
572 string Team_ColorCode(float teamid)
573 {
574     if (teamid == COLOR_TEAM1)
575         return "^1";
576     else if (teamid == COLOR_TEAM2)
577         return "^4";
578     else if (teamid == COLOR_TEAM3)
579         return "^3";
580     else if (teamid == COLOR_TEAM4)
581         return "^6";
582     else
583         return "^7";
584 }
585 string Team_ColorName(float t)
586 {
587     // fixme: Search for team entities and get their .netname's!
588     if (t == COLOR_TEAM1)
589         return "Red";
590     if (t == COLOR_TEAM2)
591         return "Blue";
592     if (t == COLOR_TEAM3)
593         return "Yellow";
594     if (t == COLOR_TEAM4)
595         return "Pink";
596     return "Neutral";
597 }
598 string Team_ColorNameLowerCase(float t)
599 {
600     // fixme: Search for team entities and get their .netname's!
601     if (t == COLOR_TEAM1)
602         return "red";
603     if (t == COLOR_TEAM2)
604         return "blue";
605     if (t == COLOR_TEAM3)
606         return "yellow";
607     if (t == COLOR_TEAM4)
608         return "pink";
609     return "neutral";
610 }
611
612 #define CENTERPRIO_POINT 1
613 #define CENTERPRIO_SPAM 2
614 #define CENTERPRIO_VOTE 4
615 #define CENTERPRIO_NORMAL 5
616 #define CENTERPRIO_SHIELDING 7
617 #define CENTERPRIO_MAPVOTE 9
618 #define CENTERPRIO_IDLEKICK 50
619 #define CENTERPRIO_ADMIN 99
620 .float centerprint_priority;
621 .float centerprint_expires;
622 void centerprint_atprio(entity e, float prio, string s)
623 {
624     if (intermission_running)
625         if (prio < CENTERPRIO_MAPVOTE)
626             return;
627     if (time > e.centerprint_expires)
628         e.centerprint_priority = 0;
629     if (prio >= e.centerprint_priority)
630     {
631         e.centerprint_priority = prio;
632         if (timeoutStatus == 2)
633             e.centerprint_expires = time + (e.cvar_scr_centertime * TIMEOUT_SLOWMO_VALUE);
634         else
635             e.centerprint_expires = time + e.cvar_scr_centertime;
636         centerprint_builtin(e, s);
637     }
638 }
639 void centerprint_expire(entity e, float prio)
640 {
641     if (prio == e.centerprint_priority)
642     {
643         e.centerprint_priority = 0;
644         centerprint_builtin(e, "");
645     }
646 }
647 void centerprint(entity e, string s)
648 {
649     centerprint_atprio(e, CENTERPRIO_NORMAL, s);
650 }
651
652 // decolorizes and team colors the player name when needed
653 string playername(entity p)
654 {
655     string t;
656     if (teams_matter && !intermission_running && p.classname == "player")
657     {
658         t = Team_ColorCode(p.team);
659         return strcat(t, strdecolorize(p.netname));
660     }
661     else
662         return p.netname;
663 }
664
665 vector randompos(vector m1, vector m2)
666 {
667     local vector v;
668     m2 = m2 - m1;
669     v_x = m2_x * random() + m1_x;
670     v_y = m2_y * random() + m1_y;
671     v_z = m2_z * random() + m1_z;
672     return  v;
673 };
674
675 float g_pickup_shells;
676 float g_pickup_shells_max;
677 float g_pickup_nails;
678 float g_pickup_nails_max;
679 float g_pickup_rockets;
680 float g_pickup_rockets_max;
681 float g_pickup_cells;
682 float g_pickup_cells_max;
683 float g_pickup_fuel;
684 float g_pickup_fuel_jetpack;
685 float g_pickup_fuel_max;
686 float g_pickup_armorsmall;
687 float g_pickup_armorsmall_max;
688 float g_pickup_armormedium;
689 float g_pickup_armormedium_max;
690 float g_pickup_armorbig;
691 float g_pickup_armorbig_max;
692 float g_pickup_armorlarge;
693 float g_pickup_armorlarge_max;
694 float g_pickup_healthsmall;
695 float g_pickup_healthsmall_max;
696 float g_pickup_healthmedium;
697 float g_pickup_healthmedium_max;
698 float g_pickup_healthlarge;
699 float g_pickup_healthlarge_max;
700 float g_pickup_healthmega;
701 float g_pickup_healthmega_max;
702 float g_weaponarena;
703 string g_weaponarena_list;
704 float g_weaponspeedfactor;
705 float g_weapondamagefactor;
706
707 float start_weapons;
708 float start_items;
709 float start_ammo_shells;
710 float start_ammo_nails;
711 float start_ammo_rockets;
712 float start_ammo_cells;
713 float start_ammo_fuel;
714 float start_health;
715 float start_armorvalue;
716 float warmup_start_weapons;
717 float warmup_start_ammo_shells;
718 float warmup_start_ammo_nails;
719 float warmup_start_ammo_rockets;
720 float warmup_start_ammo_cells;
721 float warmup_start_ammo_fuel;
722 float warmup_start_health;
723 float warmup_start_armorvalue;
724 float g_weapon_stay;
725
726 entity get_weaponinfo(float w);
727
728 float NixNex_CanChooseWeapon(float wpn);
729 void readplayerstartcvars()
730 {
731     entity e;
732     float i, j, t;
733     string s;
734
735     // initialize starting values for players
736     start_weapons = 0;
737     start_items = 0;
738     start_ammo_shells = 0;
739     start_ammo_nails = 0;
740     start_ammo_rockets = 0;
741     start_ammo_cells = 0;
742     start_health = cvar("g_balance_health_start");
743     start_armorvalue = cvar("g_balance_armor_start");
744
745     g_weaponarena = 0;
746     s = cvar_string("g_weaponarena");
747     if (s == "0")
748     {
749     }
750     else if (s == "all")
751     {
752         g_weaponarena_list = "All Weapons";
753         for (j = WEP_FIRST; j <= WEP_LAST; ++j)
754         {
755             e = get_weaponinfo(j);
756             g_weaponarena |= e.weapons;
757             weapon_action(e.weapon, WR_PRECACHE);
758         }
759     }
760     else if (s == "most")
761     {
762         g_weaponarena_list = "Most Weapons";
763         for (j = WEP_FIRST; j <= WEP_LAST; ++j)
764         {
765             e = get_weaponinfo(j);
766             if (e.spawnflags & WEPSPAWNFLAG_NORMAL)
767             {
768                 g_weaponarena |= e.weapons;
769                 weapon_action(e.weapon, WR_PRECACHE);
770             }
771         }
772     }
773     else if (s == "none")
774     {
775         g_weaponarena_list = "No Weapons";
776         g_weaponarena = WEPBIT_ALL + 1; // this supports no single weapon bit!
777     }
778     else
779     {
780         t = tokenize_console(s);
781         g_weaponarena_list = "";
782         for (i = 0; i < t; ++i)
783         {
784             s = argv(i);
785             for (j = WEP_FIRST; j <= WEP_LAST; ++j)
786             {
787                 e = get_weaponinfo(j);
788                 if (e.netname == s)
789                 {
790                     g_weaponarena |= e.weapons;
791                     weapon_action(e.weapon, WR_PRECACHE);
792                     g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
793                     break;
794                 }
795             }
796             if (j > WEP_LAST)
797             {
798                 print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
799             }
800         }
801         g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
802     }
803
804     if (g_nixnex)
805     {
806         start_weapons = 0;
807         // will be done later
808         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
809             if (NixNex_CanChooseWeapon(i))
810                 weapon_action(i, WR_PRECACHE);
811     }
812     else if (g_weaponarena)
813     {
814         start_weapons = g_weaponarena;
815         start_ammo_rockets = 999;
816         start_ammo_shells = 999;
817         start_ammo_cells = 999;
818         start_ammo_nails = 999;
819         start_ammo_fuel = 999;
820         start_items |= IT_UNLIMITED_AMMO;
821     }
822     else if (g_minstagib)
823     {
824         start_health = 100;
825         start_armorvalue = 0;
826         start_weapons = WEPBIT_MINSTANEX;
827         weapon_action(WEP_MINSTANEX, WR_PRECACHE);
828         start_ammo_cells = cvar("g_minstagib_ammo_start");
829         g_minstagib_invis_alpha = cvar("g_minstagib_invis_alpha");
830         start_ammo_fuel = cvar("g_start_ammo_fuel");
831
832         if (g_minstagib_invis_alpha <= 0)
833             g_minstagib_invis_alpha = -1;
834     }
835     else
836     {
837         if (g_lms)
838         {
839             start_ammo_shells = cvar("g_lms_start_ammo_shells");
840             start_ammo_nails = cvar("g_lms_start_ammo_nails");
841             start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
842             start_ammo_cells = cvar("g_lms_start_ammo_cells");
843             start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
844             start_health = cvar("g_lms_start_health");
845             start_armorvalue = cvar("g_lms_start_armor");
846         }
847         else if (cvar("g_use_ammunition"))
848         {
849             start_ammo_shells = cvar("g_start_ammo_shells");
850             start_ammo_nails = cvar("g_start_ammo_nails");
851             start_ammo_rockets = cvar("g_start_ammo_rockets");
852             start_ammo_cells = cvar("g_start_ammo_cells");
853             start_ammo_fuel = cvar("g_start_ammo_fuel");
854         }
855         else
856         {
857             start_ammo_shells = cvar("g_pickup_shells_max");
858             start_ammo_nails = cvar("g_pickup_nails_max");
859             start_ammo_rockets = cvar("g_pickup_rockets_max");
860             start_ammo_cells = cvar("g_pickup_cells_max");
861             start_ammo_fuel = cvar("g_pickup_fuel_max");
862             start_items |= IT_UNLIMITED_AMMO;
863         }
864
865         for (i = WEP_FIRST; i <= WEP_LAST; ++i)
866         {
867             e = get_weaponinfo(i);
868             if (!(e.weapon))
869                 continue;
870
871             t = cvar(strcat("g_start_weapon_", e.netname));
872
873             if (t < 0) // "default" weapon selection
874             {
875                 if (g_lms)
876                     t = (e.spawnflags & WEPSPAWNFLAG_NORMAL);
877                 else if (g_race)
878                     t = (i == WEP_LASER);
879                 else if (g_nexball)
880                     t = 0; // weapon is set a few lines later
881                 else
882                     t = (i == WEP_LASER || i == WEP_SHOTGUN);
883                 if (g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
884                     t += (i == WEP_HOOK);
885             }
886
887             if (g_nexball && i == WEP_PORTO)
888                 t=1;
889
890             if (t)
891             {
892                 start_weapons |= e.weapons;
893                 weapon_action(e.weapon, WR_PRECACHE);
894             }
895         }
896     }
897
898     if (inWarmupStage)
899     {
900         warmup_start_ammo_shells = start_ammo_shells;
901         warmup_start_ammo_nails = start_ammo_nails;
902         warmup_start_ammo_rockets = start_ammo_rockets;
903         warmup_start_ammo_cells = start_ammo_cells;
904         warmup_start_health = start_health;
905         warmup_start_armorvalue = start_armorvalue;
906         warmup_start_weapons = start_weapons;
907
908         if (!g_weaponarena && !g_nixnex && !g_minstagib)
909         {
910             if (cvar("g_use_ammunition"))
911             {
912                 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
913                 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
914                 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
915                 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
916             }
917             warmup_start_health = cvar("g_warmup_start_health");
918             warmup_start_armorvalue = cvar("g_warmup_start_armor");
919             if (cvar("g_warmup_allguns"))
920             {
921                 for (i = WEP_FIRST; i <= WEP_LAST; ++i)
922                 {
923                     e = get_weaponinfo(i);
924                     if (!(e.weapon))
925                         continue;
926                     if (e.spawnflags & WEPSPAWNFLAG_NORMAL)
927                     {
928                         warmup_start_weapons |= e.weapons;
929                         weapon_action(e.weapon, WR_PRECACHE);
930                     }
931                 }
932             }
933         }
934     }
935
936     if (g_jetpack || (g_grappling_hook && (start_weapons & WEPBIT_HOOK)))
937     {
938         g_grappling_hook = 0; // these two can't coexist, as they use the same button
939         start_items |= IT_FUEL_REGEN;
940         start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
941     }
942
943     if (g_jetpack)
944         start_items |= IT_JETPACK;
945
946     if (g_weapon_stay == 2)
947     {
948         if (!start_ammo_shells) start_ammo_shells = g_pickup_shells;
949         if (!start_ammo_nails) start_ammo_nails = g_pickup_nails;
950         if (!start_ammo_cells) start_ammo_cells = g_pickup_cells;
951         if (!start_ammo_rockets) start_ammo_rockets = g_pickup_rockets;
952         if (!start_ammo_fuel) start_ammo_fuel = g_pickup_fuel;
953         if (!warmup_start_ammo_shells) warmup_start_ammo_shells = g_pickup_shells;
954         if (!warmup_start_ammo_nails) warmup_start_ammo_nails = g_pickup_nails;
955         if (!warmup_start_ammo_cells) warmup_start_ammo_cells = g_pickup_cells;
956         if (!warmup_start_ammo_rockets) warmup_start_ammo_rockets = g_pickup_rockets;
957         if (!warmup_start_ammo_fuel) warmup_start_ammo_fuel = g_pickup_fuel;
958     }
959
960     start_ammo_shells = max(0, start_ammo_shells);
961     start_ammo_nails = max(0, start_ammo_nails);
962     start_ammo_cells = max(0, start_ammo_cells);
963     start_ammo_rockets = max(0, start_ammo_rockets);
964     start_ammo_fuel = max(0, start_ammo_fuel);
965
966     warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
967     warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
968     warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
969     warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
970     warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
971 }
972
973 float g_bugrigs;
974 float g_bugrigs_planar_movement;
975 float g_bugrigs_planar_movement_car_jumping;
976 float g_bugrigs_reverse_spinning;
977 float g_bugrigs_reverse_speeding;
978 float g_bugrigs_reverse_stopping;
979 float g_bugrigs_air_steering;
980 float g_bugrigs_angle_smoothing;
981 float g_bugrigs_friction_floor;
982 float g_bugrigs_friction_brake;
983 float g_bugrigs_friction_air;
984 float g_bugrigs_accel;
985 float g_bugrigs_speed_ref;
986 float g_bugrigs_speed_pow;
987 float g_bugrigs_steer;
988
989 float g_touchexplode;
990 float g_touchexplode_radius;
991 float g_touchexplode_damage;
992 float g_touchexplode_edgedamage;
993 float g_touchexplode_force;
994
995 void readlevelcvars(void)
996 {
997     g_bugrigs = cvar("g_bugrigs");
998     g_bugrigs_planar_movement = cvar("g_bugrigs_planar_movement");
999     g_bugrigs_planar_movement_car_jumping = cvar("g_bugrigs_planar_movement_car_jumping");
1000     g_bugrigs_reverse_spinning = cvar("g_bugrigs_reverse_spinning");
1001     g_bugrigs_reverse_speeding = cvar("g_bugrigs_reverse_speeding");
1002     g_bugrigs_reverse_stopping = cvar("g_bugrigs_reverse_stopping");
1003     g_bugrigs_air_steering = cvar("g_bugrigs_air_steering");
1004     g_bugrigs_angle_smoothing = cvar("g_bugrigs_angle_smoothing");
1005     g_bugrigs_friction_floor = cvar("g_bugrigs_friction_floor");
1006     g_bugrigs_friction_brake = cvar("g_bugrigs_friction_brake");
1007     g_bugrigs_friction_air = cvar("g_bugrigs_friction_air");
1008     g_bugrigs_accel = cvar("g_bugrigs_accel");
1009     g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref");
1010     g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
1011     g_bugrigs_steer = cvar("g_bugrigs_steer");
1012
1013     g_touchexplode = cvar("g_touchexplode");
1014     g_touchexplode_radius = cvar("g_touchexplode_radius");
1015     g_touchexplode_damage = cvar("g_touchexplode_damage");
1016     g_touchexplode_edgedamage = cvar("g_touchexplode_edgedamage");
1017     g_touchexplode_force = cvar("g_touchexplode_force");
1018
1019     sv_clforceplayermodels = cvar("sv_clforceplayermodels");
1020     sv_loddistance1 = cvar("sv_loddistance1");
1021     sv_loddistance2 = cvar("sv_loddistance2");
1022         if(sv_loddistance2 <= sv_loddistance1)
1023                 sv_loddistance2 = 1073741824; // enough to turn off LOD 2 reliably
1024     sv_clones = cvar("sv_clones");
1025     sv_cheats = cvar("sv_cheats");
1026     sv_gentle = cvar("sv_gentle");
1027     sv_foginterval = cvar("sv_foginterval");
1028     g_cloaked = cvar("g_cloaked");
1029     g_jump_grunt = cvar("g_jump_grunt");
1030     g_footsteps = cvar("g_footsteps");
1031     g_grappling_hook = cvar("g_grappling_hook");
1032     g_jetpack = cvar("g_jetpack");
1033     g_laserguided_missile = cvar("g_laserguided_missile");
1034     g_midair = cvar("g_midair");
1035     g_minstagib = cvar("g_minstagib");
1036     g_nixnex = cvar("g_nixnex");
1037     g_nixnex_with_laser = cvar("g_nixnex_with_laser");
1038     g_norecoil = cvar("g_norecoil");
1039     g_vampire = cvar("g_vampire");
1040     g_bloodloss = cvar("g_bloodloss");
1041     sv_maxidle = cvar("sv_maxidle");
1042     sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
1043     sv_pogostick = cvar("sv_pogostick");
1044     sv_doublejump = cvar("sv_doublejump");
1045     g_maplist_allow_hidden = cvar("g_maplist_allow_hidden");
1046     g_ctf_reverse = cvar("g_ctf_reverse");
1047
1048     inWarmupStage = cvar("g_warmup");
1049     g_warmup_limit = cvar("g_warmup_limit");
1050     g_warmup_allguns = cvar("g_warmup_allguns");
1051     g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
1052
1053     if (g_race && g_race_qualifying == 2 || g_arena || g_assault || cvar("g_campaign"))
1054         inWarmupStage = 0; // these modes cannot work together, sorry
1055
1056     g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
1057     g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
1058     g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
1059     g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
1060     g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
1061     g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
1062     g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
1063     g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
1064     g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
1065     g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
1066     g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
1067     g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
1068
1069     if (g_minstagib) g_nixnex = g_weaponarena = 0;
1070     if (g_nixnex) g_weaponarena = 0;
1071     g_weaponarena = 0;
1072
1073     g_weaponspeedfactor = cvar("g_weaponspeedfactor");
1074     g_weapondamagefactor = cvar("g_weapondamagefactor");
1075
1076     g_pickup_shells                    = cvar("g_pickup_shells");
1077     g_pickup_shells_max                = cvar("g_pickup_shells_max");
1078     g_pickup_nails                     = cvar("g_pickup_nails");
1079     g_pickup_nails_max                 = cvar("g_pickup_nails_max");
1080     g_pickup_rockets                   = cvar("g_pickup_rockets");
1081     g_pickup_rockets_max               = cvar("g_pickup_rockets_max");
1082     g_pickup_cells                     = cvar("g_pickup_cells");
1083     g_pickup_cells_max                 = cvar("g_pickup_cells_max");
1084     g_pickup_fuel                     = cvar("g_pickup_fuel");
1085     g_pickup_fuel_jetpack             = cvar("g_pickup_fuel_jetpack");
1086     g_pickup_fuel_max                 = cvar("g_pickup_fuel_max");
1087     g_pickup_armorsmall                = cvar("g_pickup_armorsmall");
1088     g_pickup_armorsmall_max            = cvar("g_pickup_armorsmall_max");
1089     g_pickup_armormedium               = cvar("g_pickup_armormedium");
1090     g_pickup_armormedium_max           = cvar("g_pickup_armormedium_max");
1091     g_pickup_armorbig                  = cvar("g_pickup_armorbig");
1092     g_pickup_armorbig_max              = cvar("g_pickup_armorbig_max");
1093     g_pickup_armorlarge                = cvar("g_pickup_armorlarge");
1094     g_pickup_armorlarge_max            = cvar("g_pickup_armorlarge_max");
1095     g_pickup_healthsmall               = cvar("g_pickup_healthsmall");
1096     g_pickup_healthsmall_max           = cvar("g_pickup_healthsmall_max");
1097     g_pickup_healthmedium              = cvar("g_pickup_healthmedium");
1098     g_pickup_healthmedium_max          = cvar("g_pickup_healthmedium_max");
1099     g_pickup_healthlarge               = cvar("g_pickup_healthlarge");
1100     g_pickup_healthlarge_max           = cvar("g_pickup_healthlarge_max");
1101     g_pickup_healthmega                = cvar("g_pickup_healthmega");
1102     g_pickup_healthmega_max            = cvar("g_pickup_healthmega_max");
1103
1104     g_pinata = cvar("g_pinata");
1105
1106     g_weapon_stay = cvar("g_weapon_stay");
1107     if (!g_weapon_stay && (cvar("deathmatch") == 2))
1108         g_weapon_stay = 1;
1109
1110     if not(inWarmupStage)
1111         game_starttime                 = cvar("g_start_delay");
1112
1113     readplayerstartcvars();
1114 }
1115
1116 /*
1117 // TODO sound pack system
1118 string soundpack;
1119
1120 string precache_sound_builtin (string s) = #19;
1121 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1122 string precache_sound(string s)
1123 {
1124         return precache_sound_builtin(strcat(soundpack, s));
1125 }
1126 void play2(entity e, string filename)
1127 {
1128         stuffcmd(e, strcat("play2 ", soundpack, filename, "\n"));
1129 }
1130 void sound(entity e, float chan, string samp, float vol, float atten)
1131 {
1132         sound_builtin(e, chan, strcat(soundpack, samp), vol, atten);
1133 }
1134 */
1135
1136 // Sound functions
1137 string precache_sound (string s) = #19;
1138 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1139 float precache_sound_index (string s) = #19;
1140
1141 #define SND_VOLUME      1
1142 #define SND_ATTENUATION 2
1143 #define SND_LARGEENTITY 8
1144 #define SND_LARGESOUND  16
1145
1146 float sound_allowed(float dest, entity e)
1147 {
1148     // sounds from world may always pass
1149     for (;;)
1150     {
1151         if (e.classname == "body")
1152             e = e.enemy;
1153         if (e.owner && e.owner != e)
1154             e = e.owner;
1155         else
1156             break;
1157     }
1158     // sounds to self may always pass
1159     if (dest == MSG_ONE)
1160         if (e == msg_entity)
1161             return TRUE;
1162     // sounds by players can be removed
1163     if (cvar("bot_sound_monopoly"))
1164         if (clienttype(e) == CLIENTTYPE_REAL)
1165             return FALSE;
1166     // anything else may pass
1167     return TRUE;
1168 }
1169
1170 void sound(entity e, float chan, string samp, float vol, float atten)
1171 {
1172     if (!sound_allowed(MSG_BROADCAST, e))
1173         return;
1174     sound_builtin(e, chan, samp, vol, atten);
1175 }
1176 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
1177 {
1178     float entno, idx;
1179
1180     if (!sound_allowed(dest, e))
1181         return;
1182
1183     entno = num_for_edict(e);
1184     idx = precache_sound_index(samp);
1185
1186     float sflags;
1187     sflags = 0;
1188
1189     atten = floor(atten * 64);
1190     vol = floor(vol * 255);
1191
1192     if (vol != 255)
1193         sflags |= SND_VOLUME;
1194     if (atten != 64)
1195         sflags |= SND_ATTENUATION;
1196     if (entno >= 8192)
1197         sflags |= SND_LARGEENTITY;
1198     if (idx >= 256)
1199         sflags |= SND_LARGESOUND;
1200
1201     WriteByte(dest, SVC_SOUND);
1202     WriteByte(dest, sflags);
1203     if (sflags & SND_VOLUME)
1204         WriteByte(dest, vol);
1205     if (sflags & SND_ATTENUATION)
1206         WriteByte(dest, atten);
1207     if (sflags & SND_LARGEENTITY)
1208     {
1209         WriteShort(dest, entno);
1210         WriteByte(dest, chan);
1211     }
1212     else
1213     {
1214         WriteShort(dest, entno * 8 + chan);
1215     }
1216     if (sflags & SND_LARGESOUND)
1217         WriteShort(dest, idx);
1218     else
1219         WriteByte(dest, idx);
1220
1221     WriteCoord(dest, o_x);
1222     WriteCoord(dest, o_y);
1223     WriteCoord(dest, o_z);
1224 }
1225 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
1226 {
1227     vector o;
1228
1229     if (!sound_allowed(dest, e))
1230         return;
1231
1232     o = e.origin + 0.5 * (e.mins + e.maxs);
1233     soundtoat(dest, e, o, chan, samp, vol, atten);
1234 }
1235 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
1236 {
1237     soundtoat(MSG_BROADCAST, e, o, chan, samp, vol, atten);
1238 }
1239 void stopsoundto(float dest, entity e, float chan)
1240 {
1241     float entno;
1242
1243     if (!sound_allowed(dest, e))
1244         return;
1245
1246     entno = num_for_edict(e);
1247
1248     if (entno >= 8192)
1249     {
1250         float idx, sflags;
1251         idx = precache_sound_index("misc/null.wav");
1252         sflags = SND_LARGEENTITY;
1253         if (idx >= 256)
1254             sflags |= SND_LARGESOUND;
1255         WriteByte(dest, SVC_SOUND);
1256         WriteByte(dest, sflags);
1257         WriteShort(dest, entno);
1258         WriteByte(dest, chan);
1259         if (sflags & SND_LARGESOUND)
1260             WriteShort(dest, idx);
1261         else
1262             WriteByte(dest, idx);
1263         WriteCoord(dest, e.origin_x);
1264         WriteCoord(dest, e.origin_y);
1265         WriteCoord(dest, e.origin_z);
1266     }
1267     else
1268     {
1269         WriteByte(dest, SVC_STOPSOUND);
1270         WriteShort(dest, entno * 8 + chan);
1271     }
1272 }
1273 void stopsound(entity e, float chan)
1274 {
1275     if (!sound_allowed(MSG_BROADCAST, e))
1276         return;
1277
1278     stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
1279     stopsoundto(MSG_ALL, e, chan); // in case of packet loss
1280 }
1281
1282 void play2(entity e, string filename)
1283 {
1284     //stuffcmd(e, strcat("play2 ", filename, "\n"));
1285     msg_entity = e;
1286     soundtoat(MSG_ONE, world, '0 0 0', CHAN_AUTO, filename, VOL_BASE, ATTN_NONE);
1287 }
1288
1289 .float announcetime;
1290 float announce(entity player, string msg)
1291 {
1292     if (time > player.announcetime)
1293         if (clienttype(player) == CLIENTTYPE_REAL)
1294         {
1295             player.announcetime = time + 0.8;
1296             play2(player, msg);
1297             return TRUE;
1298         }
1299     return FALSE;
1300 }
1301 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
1302 float spamsound(entity e, float chan, string samp, float vol, float atten)
1303 {
1304     if (!sound_allowed(MSG_BROADCAST, e))
1305         return FALSE;
1306
1307     if (time > e.announcetime)
1308     {
1309         e.announcetime = time;
1310         sound(e, chan, samp, vol, atten);
1311         return TRUE;
1312     }
1313     return FALSE;
1314 }
1315
1316 void play2team(float t, string filename)
1317 {
1318     local entity head;
1319
1320     if (cvar("bot_sound_monopoly"))
1321         return;
1322
1323     FOR_EACH_REALPLAYER(head)
1324     {
1325         if (head.team == t)
1326             play2(head, filename);
1327     }
1328 }
1329
1330 void play2all(string samp)
1331 {
1332     if (cvar("bot_sound_monopoly"))
1333         return;
1334
1335     sound(world, CHAN_AUTO, samp, VOL_BASE, ATTN_NONE);
1336 }
1337
1338 void PrecachePlayerSounds(string f);
1339 void precache_all_models(string pattern)
1340 {
1341     float globhandle, i, n;
1342     string f;
1343
1344     globhandle = search_begin(pattern, TRUE, FALSE);
1345     if (globhandle < 0)
1346         return;
1347     n = search_getsize(globhandle);
1348     for (i = 0; i < n; ++i)
1349     {
1350                 //print(search_getfilename(globhandle, i), "\n");
1351                 f = search_getfilename(globhandle, i);
1352                 if(sv_loddistance1)
1353                         precache_model(f);
1354                 if(substring(f, -9,5) == "_lod1")
1355                         continue;
1356                 if(substring(f, -9,5) == "_lod2")
1357                         continue;
1358                 if(!sv_loddistance1)
1359                         precache_model(f);
1360                 PrecachePlayerSounds(strcat(f, ".sounds"));
1361     }
1362     search_end(globhandle);
1363 }
1364
1365 void precache()
1366 {
1367     // gamemode related things
1368     precache_model ("models/misc/chatbubble.spr");
1369     precache_model ("models/misc/teambubble.spr");
1370     if (g_runematch)
1371     {
1372         precache_model ("models/runematch/curse.mdl");
1373         precache_model ("models/runematch/rune.mdl");
1374     }
1375
1376 #ifdef TTURRETS_ENABLED
1377     if (cvar("g_turrets"))
1378         turrets_precash();
1379 #endif
1380
1381     // Precache all player models if desired
1382     if (cvar("sv_precacheplayermodels"))
1383     {
1384         PrecachePlayerSounds("sound/player/default.sounds");
1385         precache_all_models("models/player/*.zym");
1386         precache_all_models("models/player/*.dpm");
1387         precache_all_models("models/player/*.md3");
1388         precache_all_models("models/player/*.psk");
1389         //precache_model("models/player/carni.zym");
1390         //precache_model("models/player/crash.zym");
1391         //precache_model("models/player/grunt.zym");
1392         //precache_model("models/player/headhunter.zym");
1393         //precache_model("models/player/insurrectionist.zym");
1394         //precache_model("models/player/jeandarc.zym");
1395         //precache_model("models/player/lurk.zym");
1396         //precache_model("models/player/lycanthrope.zym");
1397         //precache_model("models/player/marine.zym");
1398         //precache_model("models/player/nexus.zym");
1399         //precache_model("models/player/pyria.zym");
1400         //precache_model("models/player/shock.zym");
1401         //precache_model("models/player/skadi.zym");
1402         //precache_model("models/player/specop.zym");
1403         //precache_model("models/player/visitant.zym");
1404     }
1405
1406     if (cvar("sv_defaultcharacter"))
1407     {
1408         string s;
1409         s = cvar_string("sv_defaultplayermodel_red");
1410         if (s != "")
1411         {
1412             precache_model(s);
1413             PrecachePlayerSounds(strcat(s, ".sounds"));
1414         }
1415         s = cvar_string("sv_defaultplayermodel_blue");
1416         if (s != "")
1417         {
1418             precache_model(s);
1419             PrecachePlayerSounds(strcat(s, ".sounds"));
1420         }
1421         s = cvar_string("sv_defaultplayermodel_yellow");
1422         if (s != "")
1423         {
1424             precache_model(s);
1425             PrecachePlayerSounds(strcat(s, ".sounds"));
1426         }
1427         s = cvar_string("sv_defaultplayermodel_pink");
1428         if (s != "")
1429         {
1430             precache_model(s);
1431             PrecachePlayerSounds(strcat(s, ".sounds"));
1432         }
1433         s = cvar_string("sv_defaultplayermodel");
1434         if (s != "")
1435         {
1436             precache_model(s);
1437             PrecachePlayerSounds(strcat(s, ".sounds"));
1438         }
1439     }
1440
1441     if (g_footsteps)
1442     {
1443         PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1444         PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1445     }
1446
1447     // gore and miscellaneous sounds
1448     //precache_sound ("misc/h2ohit.wav");
1449     precache_model ("models/hook.md3");
1450     precache_sound ("misc/armorimpact.wav");
1451     precache_sound ("misc/bodyimpact1.wav");
1452     precache_sound ("misc/bodyimpact2.wav");
1453     precache_sound ("misc/gib.wav");
1454     precache_sound ("misc/gib_splat01.wav");
1455     precache_sound ("misc/gib_splat02.wav");
1456     precache_sound ("misc/gib_splat03.wav");
1457     precache_sound ("misc/gib_splat04.wav");
1458     precache_sound ("misc/hit.wav");
1459     PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1460     PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1461     precache_sound ("misc/null.wav");
1462     precache_sound ("misc/spawn.wav");
1463     precache_sound ("misc/talk.wav");
1464     precache_sound ("misc/teleport.wav");
1465     precache_sound ("misc/poweroff.wav");
1466     precache_sound ("player/lava.wav");
1467     precache_sound ("player/slime.wav");
1468
1469     if (g_jetpack)
1470         precache_sound ("misc/jetpack_fly.wav");
1471
1472     // announcer sounds - male
1473     precache_sound ("announcer/male/electrobitch.wav");
1474     precache_sound ("announcer/male/airshot.wav");
1475     precache_sound ("announcer/male/03kills.wav");
1476     precache_sound ("announcer/male/05kills.wav");
1477     precache_sound ("announcer/male/10kills.wav");
1478     precache_sound ("announcer/male/15kills.wav");
1479     precache_sound ("announcer/male/20kills.wav");
1480     precache_sound ("announcer/male/25kills.wav");
1481     precache_sound ("announcer/male/30kills.wav");
1482     precache_sound ("announcer/male/botlike.wav");
1483     precache_sound ("announcer/male/yoda.wav");
1484     precache_sound ("announcer/male/amazing.wav");
1485     precache_sound ("announcer/male/awesome.wav");
1486     precache_sound ("announcer/male/headshot.wav");
1487     precache_sound ("announcer/male/impressive.wav");
1488
1489     // announcer sounds - robotic
1490     precache_sound ("announcer/robotic/prepareforbattle.wav");
1491     precache_sound ("announcer/robotic/begin.wav");
1492     precache_sound ("announcer/robotic/timeoutcalled.wav");
1493     precache_sound ("announcer/robotic/1fragleft.wav");
1494     precache_sound ("announcer/robotic/2fragsleft.wav");
1495     precache_sound ("announcer/robotic/3fragsleft.wav");
1496     if (g_minstagib)
1497     {
1498         precache_sound ("announcer/robotic/lastsecond.wav");
1499         precache_sound ("announcer/robotic/narrowly.wav");
1500     }
1501
1502     precache_model ("models/sprites/1.spr32");
1503     precache_model ("models/sprites/2.spr32");
1504     precache_model ("models/sprites/3.spr32");
1505     precache_model ("models/sprites/4.spr32");
1506     precache_model ("models/sprites/5.spr32");
1507     precache_model ("models/sprites/6.spr32");
1508     precache_model ("models/sprites/7.spr32");
1509     precache_model ("models/sprites/8.spr32");
1510     precache_model ("models/sprites/9.spr32");
1511     precache_model ("models/sprites/10.spr32");
1512     precache_sound ("announcer/robotic/1.wav");
1513     precache_sound ("announcer/robotic/2.wav");
1514     precache_sound ("announcer/robotic/3.wav");
1515     precache_sound ("announcer/robotic/4.wav");
1516     precache_sound ("announcer/robotic/5.wav");
1517     precache_sound ("announcer/robotic/6.wav");
1518     precache_sound ("announcer/robotic/7.wav");
1519     precache_sound ("announcer/robotic/8.wav");
1520     precache_sound ("announcer/robotic/9.wav");
1521     precache_sound ("announcer/robotic/10.wav");
1522
1523     // common weapon precaches
1524     precache_sound ("weapons/weapon_switch.wav");
1525     precache_sound ("weapons/weaponpickup.wav");
1526     precache_sound ("weapons/unavailable.wav");
1527     if (g_grappling_hook)
1528     {
1529         precache_sound ("weapons/hook_fire.wav"); // hook
1530         precache_sound ("weapons/hook_impact.wav"); // hook
1531     }
1532
1533     if (cvar("sv_precacheweapons") || g_nixnex)
1534     {
1535         //precache weapon models/sounds
1536         local float wep;
1537         wep = WEP_FIRST;
1538         while (wep <= WEP_LAST)
1539         {
1540             weapon_action(wep, WR_PRECACHE);
1541             wep = wep + 1;
1542         }
1543     }
1544
1545     precache_model("models/elaser.mdl");
1546     precache_model("models/laser.mdl");
1547     precache_model("models/ebomb.mdl");
1548
1549 #if 0
1550     // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1551
1552     if (!self.noise && self.music) // quake 3 uses the music field
1553         self.noise = self.music;
1554
1555     // plays music for the level if there is any
1556     if (self.noise)
1557     {
1558         precache_sound (self.noise);
1559         ambientsound ('0 0 0', self.noise, VOL_BASE, ATTN_NONE);
1560     }
1561 #endif
1562 }
1563
1564 // sorry, but using \ in macros breaks line numbers
1565 #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
1566 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
1567 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
1568
1569 vector ExactTriggerHit_mins;
1570 vector ExactTriggerHit_maxs;
1571 float ExactTriggerHit_Recurse()
1572 {
1573     float s;
1574     entity se;
1575     float f;
1576
1577     tracebox('0 0 0', ExactTriggerHit_mins, ExactTriggerHit_maxs, '0 0 0', MOVE_NORMAL, other);
1578     if not(trace_ent)
1579         return 0;
1580     if (trace_ent == self)
1581         return 1;
1582
1583     se = trace_ent;
1584     s = se.solid;
1585     se.solid = SOLID_NOT;
1586     f = ExactTriggerHit_Recurse();
1587     se.solid = s;
1588
1589     return f;
1590 }
1591
1592 float ExactTriggerHit()
1593 {
1594     float f, s;
1595
1596     if not(self.modelindex)
1597         return 1;
1598
1599     s = self.solid;
1600     self.solid = SOLID_BSP;
1601     ExactTriggerHit_mins = other.absmin;
1602     ExactTriggerHit_maxs = other.absmax;
1603     f = ExactTriggerHit_Recurse();
1604     self.solid = s;
1605
1606     return f;
1607 }
1608
1609 // WARNING: this kills the trace globals
1610 #define EXACTTRIGGER_TOUCH if not(ExactTriggerHit()) return
1611 #define EXACTTRIGGER_INIT  InitSolidBSPTrigger(); self.solid = SOLID_TRIGGER
1612
1613 #define INITPRIO_FIRST              0
1614 #define INITPRIO_GAMETYPE           0
1615 #define INITPRIO_GAMETYPE_FALLBACK  1
1616 #define INITPRIO_CVARS              5
1617 #define INITPRIO_FINDTARGET        10
1618 #define INITPRIO_DROPTOFLOOR       20
1619 #define INITPRIO_SETLOCATION       90
1620 #define INITPRIO_LINKDOORS         91
1621 #define INITPRIO_LAST              99
1622
1623 .void(void) initialize_entity;
1624 .float initialize_entity_order;
1625 .entity initialize_entity_next;
1626 entity initialize_entity_first;
1627
1628 void make_safe_for_remove(entity e)
1629 {
1630     if (e.initialize_entity)
1631     {
1632         entity ent, prev;
1633         for (ent = initialize_entity_first; ent; )
1634         {
1635             if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1636             {
1637                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1638                 // skip it in linked list
1639                 if (prev)
1640                 {
1641                     prev.initialize_entity_next = ent.initialize_entity_next;
1642                     ent = prev.initialize_entity_next;
1643                 }
1644                 else
1645                 {
1646                     initialize_entity_first = ent.initialize_entity_next;
1647                     ent = initialize_entity_first;
1648                 }
1649             }
1650             else
1651             {
1652                 prev = ent;
1653                 ent = ent.initialize_entity_next;
1654             }
1655         }
1656     }
1657 }
1658
1659 void objerror(string s)
1660 {
1661     make_safe_for_remove(self);
1662     objerror_builtin(s);
1663 }
1664
1665 void remove_unsafely(entity e)
1666 {
1667     remove_builtin(e);
1668 }
1669
1670 void remove_safely(entity e)
1671 {
1672     make_safe_for_remove(e);
1673     remove_builtin(e);
1674 }
1675
1676 void InitializeEntity(entity e, void(void) func, float order)
1677 {
1678     entity prev, cur;
1679
1680     if (!e || e.initialize_entity)
1681     {
1682         // make a proxy initializer entity
1683         entity e_old;
1684         e_old = e;
1685         e = spawn();
1686         e.classname = "initialize_entity";
1687         e.enemy = e_old;
1688     }
1689
1690     e.initialize_entity = func;
1691     e.initialize_entity_order = order;
1692
1693     cur = initialize_entity_first;
1694     for (;;)
1695     {
1696         if (!cur || cur.initialize_entity_order > order)
1697         {
1698             // insert between prev and cur
1699             if (prev)
1700                 prev.initialize_entity_next = e;
1701             else
1702                 initialize_entity_first = e;
1703             e.initialize_entity_next = cur;
1704             return;
1705         }
1706         prev = cur;
1707         cur = cur.initialize_entity_next;
1708     }
1709 }
1710 void InitializeEntitiesRun()
1711 {
1712     entity startoflist;
1713     startoflist = initialize_entity_first;
1714     initialize_entity_first = world;
1715     for (self = startoflist; self; )
1716     {
1717         entity e;
1718         var void(void) func;
1719         e = self.initialize_entity_next;
1720         func = self.initialize_entity;
1721         self.initialize_entity_order = 0;
1722         self.initialize_entity = func_null;
1723         self.initialize_entity_next = world;
1724         if (self.classname == "initialize_entity")
1725         {
1726             entity e_old;
1727             e_old = self.enemy;
1728             remove_builtin(self);
1729             self = e_old;
1730         }
1731         //dprint("Delayed initialization: ", self.classname, "\n");
1732         func();
1733         self = e;
1734     }
1735 }
1736
1737 .float uncustomizeentityforclient_set;
1738 .void(void) uncustomizeentityforclient;
1739 void(void) SUB_Nullpointer = #0;
1740 void UncustomizeEntitiesRun()
1741 {
1742     entity oldself;
1743     oldself = self;
1744     for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1745         self.uncustomizeentityforclient();
1746     self = oldself;
1747 }
1748 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1749 {
1750     e.customizeentityforclient = customizer;
1751     e.uncustomizeentityforclient = uncustomizer;
1752     e.uncustomizeentityforclient_set = (uncustomizer != SUB_Nullpointer);
1753 }
1754
1755 .float nottargeted;
1756 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1757
1758 void() SUB_Remove;
1759 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1760 {
1761     vector mi, ma;
1762
1763     if (e.classname == "")
1764         e.classname = "net_linked";
1765
1766     if (e.model == "" || self.modelindex == 0)
1767     {
1768         mi = e.mins;
1769         ma = e.maxs;
1770         setmodel(e, "null");
1771         setsize(e, mi, ma);
1772     }
1773
1774     e.SendEntity = sendfunc;
1775     e.SendFlags = 0xFFFFFF;
1776
1777     if (!docull)
1778         e.effects |= EF_NODEPTHTEST;
1779
1780     if (dt)
1781     {
1782         e.nextthink = time + dt;
1783         e.think = SUB_Remove;
1784     }
1785 }
1786
1787 void adaptor_think2touch()
1788 {
1789     entity o;
1790     o = other;
1791     other = world;
1792     self.touch();
1793     other = o;
1794 }
1795
1796 void adaptor_think2use()
1797 {
1798     entity o, a;
1799     o = other;
1800     a = activator;
1801     activator = world;
1802     other = world;
1803     self.use();
1804     other = o;
1805     activator = a;
1806 }
1807
1808 // deferred dropping
1809 void DropToFloor_Handler()
1810 {
1811     droptofloor_builtin();
1812     self.dropped_origin = self.origin;
1813 }
1814
1815 void droptofloor()
1816 {
1817     InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1818 }
1819
1820
1821
1822 float trace_hits_box_a0, trace_hits_box_a1;
1823
1824 float trace_hits_box_1d(float end, float thmi, float thma)
1825 {
1826     if (end == 0)
1827     {
1828         // just check if x is in range
1829         if (0 < thmi)
1830             return FALSE;
1831         if (0 > thma)
1832             return FALSE;
1833     }
1834     else
1835     {
1836         // do the trace with respect to x
1837         // 0 -> end has to stay in thmi -> thma
1838         trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1839         trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1840         if (trace_hits_box_a0 > trace_hits_box_a1)
1841             return FALSE;
1842     }
1843     return TRUE;
1844 }
1845
1846 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1847 {
1848     end -= start;
1849     thmi -= start;
1850     thma -= start;
1851     // now it is a trace from 0 to end
1852
1853     trace_hits_box_a0 = 0;
1854     trace_hits_box_a1 = 1;
1855
1856     if (!trace_hits_box_1d(end_x, thmi_x, thma_x))
1857         return FALSE;
1858     if (!trace_hits_box_1d(end_y, thmi_y, thma_y))
1859         return FALSE;
1860     if (!trace_hits_box_1d(end_z, thmi_z, thma_z))
1861         return FALSE;
1862
1863     return TRUE;
1864 }
1865
1866 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1867 {
1868     return trace_hits_box(start, end, thmi - ma, thma - mi);
1869 }
1870
1871 float SUB_NoImpactCheck()
1872 {
1873     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1874         return 1;
1875     if (other == world && self.size != '0 0 0')
1876     {
1877         vector tic;
1878         tic = self.velocity * sys_ticrate;
1879         tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1880         traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1881         if (trace_fraction >= 1)
1882         {
1883             dprint("Odd... did not hit...?\n");
1884         }
1885         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1886         {
1887             dprint("Detected and prevented the sky-grapple bug.\n");
1888             return 1;
1889         }
1890     }
1891
1892     return 0;
1893 }
1894
1895 #define SUB_OwnerCheck() (other && (other == self.owner))
1896
1897 #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)
1898
1899 float MAX_IPBAN_URIS = 16;
1900
1901 float URI_GET_DISCARD   = 0;
1902 float URI_GET_IPBAN     = 1;
1903 float URI_GET_IPBAN_END = 16;
1904
1905 void URI_Get_Callback(float id, float status, string data)
1906 {
1907     dprint("Received HTTP request data for id ", ftos(id), "; status is ", ftos(status), "\nData is:\n");
1908     dprint(data);
1909     dprint("\nEnd of data.\n");
1910
1911     if (id == URI_GET_DISCARD)
1912     {
1913         // discard
1914     }
1915     else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1916     {
1917         // online ban list
1918         OnlineBanList_URI_Get_Callback(id, status, data);
1919     }
1920     else
1921     {
1922         print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1923     }
1924 }
1925
1926 void print_to(entity e, string s)
1927 {
1928     if (e)
1929         sprint(e, strcat(s, "\n"));
1930     else
1931         print(s, "\n");
1932 }
1933
1934 string getrecords()
1935 {
1936     float rec;
1937     string h;
1938     float r;
1939     float i;
1940     string s;
1941
1942     rec = 0;
1943
1944     s = "";
1945
1946     if (g_ctf)
1947     {
1948         for (i = 0; i < MapInfo_count; ++i)
1949         {
1950             if (MapInfo_Get_ByID(i))
1951             {
1952                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
1953                 if (r == 0)
1954                     continue;
1955                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
1956                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
1957                 ++rec;
1958             }
1959         }
1960     }
1961
1962     if (g_race)
1963     {
1964         for (i = 0; i < MapInfo_count; ++i)
1965         {
1966             if (MapInfo_Get_ByID(i))
1967             {
1968                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/time")));
1969                 if (r == 0)
1970                     continue;
1971                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/netname"));
1972                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, mmsss(r)), " ", h, "\n");
1973                 ++rec;
1974             }
1975         }
1976     }
1977     MapInfo_ClearTemps();
1978
1979     if (s == "")
1980         return "No records are available on this server.\n";
1981     else
1982         return strcat("Records on this server:\n", s);
1983 }
1984
1985 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1986 {
1987     float m, i;
1988     vector start, org, delta, end, enddown, mstart;
1989
1990     m = e.dphitcontentsmask;
1991     e.dphitcontentsmask = goodcontents | badcontents;
1992
1993     org = world.mins;
1994     delta = world.maxs - world.mins;
1995
1996     for (i = 0; i < attempts; ++i)
1997     {
1998         start_x = org_x + random() * delta_x;
1999         start_y = org_y + random() * delta_y;
2000         start_z = org_z + random() * delta_z;
2001
2002         // rule 1: start inside world bounds, and outside
2003         // solid, and don't start from somewhere where you can
2004         // fall down to evil
2005         tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta_z, MOVE_NORMAL, e);
2006         if (trace_fraction >= 1)
2007             continue;
2008         if (trace_startsolid)
2009             continue;
2010         if (trace_dphitcontents & badcontents)
2011             continue;
2012         if (trace_dphitq3surfaceflags & badsurfaceflags)
2013             continue;
2014
2015         // rule 2: if we are too high, lower the point
2016         if (trace_fraction * delta_z > maxaboveground)
2017             start = trace_endpos + '0 0 1' * maxaboveground;
2018         enddown = trace_endpos;
2019
2020         // rule 3: make sure we aren't outside the map. This only works
2021         // for somewhat well formed maps. A good rule of thumb is that
2022         // the map should have a convex outside hull.
2023         // these can be traceLINES as we already verified the starting box
2024         mstart = start + 0.5 * (e.mins + e.maxs);
2025         traceline(mstart, mstart + '1 0 0' * delta_x, MOVE_NORMAL, e);
2026         if (trace_fraction >= 1)
2027             continue;
2028         traceline(mstart, mstart - '1 0 0' * delta_x, MOVE_NORMAL, e);
2029         if (trace_fraction >= 1)
2030             continue;
2031         traceline(mstart, mstart + '0 1 0' * delta_y, MOVE_NORMAL, e);
2032         if (trace_fraction >= 1)
2033             continue;
2034         traceline(mstart, mstart - '0 1 0' * delta_y, MOVE_NORMAL, e);
2035         if (trace_fraction >= 1)
2036             continue;
2037         traceline(mstart, mstart + '0 0 1' * delta_z, MOVE_NORMAL, e);
2038         if (trace_fraction >= 1)
2039             continue;
2040
2041         // find a random vector to "look at"
2042         end_x = org_x + random() * delta_x;
2043         end_y = org_y + random() * delta_y;
2044         end_z = org_z + random() * delta_z;
2045         end = start + normalize(end - start) * vlen(delta);
2046
2047         // rule 4: start TO end must not be too short
2048         tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
2049         if (trace_startsolid)
2050             continue;
2051         if (trace_fraction < minviewdistance / vlen(delta))
2052             continue;
2053
2054         // rule 5: don't want to look at sky
2055         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
2056             continue;
2057
2058         // rule 6: we must not end up in trigger_hurt
2059         if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
2060         {
2061             dprint("trigger_hurt! ouch! and nothing else could find it!\n");
2062             continue;
2063         }
2064
2065         break;
2066     }
2067
2068     e.dphitcontentsmask = m;
2069
2070     if (i < attempts)
2071     {
2072         setorigin(e, start);
2073         e.angles = vectoangles(end - start);
2074         dprint("Needed ", ftos(i + 1), " attempts\n");
2075         return TRUE;
2076     }
2077     else
2078         return FALSE;
2079 }
2080
2081 void zcurveparticles(float effectno, vector start, vector end, float end_dz, float spd)
2082 {
2083     WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
2084     WriteByte(MSG_BROADCAST, TE_CSQC_ZCURVEPARTICLES);
2085     WriteShort(MSG_BROADCAST, effectno);
2086     WriteCoord(MSG_BROADCAST, start_x);
2087     WriteCoord(MSG_BROADCAST, start_y);
2088     WriteCoord(MSG_BROADCAST, start_z);
2089     WriteCoord(MSG_BROADCAST, end_x);
2090     WriteCoord(MSG_BROADCAST, end_y);
2091     WriteCoord(MSG_BROADCAST, end_z);
2092     WriteCoord(MSG_BROADCAST, end_dz);
2093     WriteShort(MSG_BROADCAST, spd / 16);
2094 }
2095
2096 void zcurveparticles_from_tracetoss(float effectno, vector start, vector end, vector vel)
2097 {
2098     float end_dz;
2099     vector vecxy, velxy;
2100
2101     vecxy = end - start;
2102     vecxy_z = 0;
2103     velxy = vel;
2104     velxy_z = 0;
2105
2106     if (vlen(velxy) < 0.000001 * fabs(vel_z))
2107     {
2108         trailparticles(world, effectno, start, end);
2109         return;
2110     }
2111
2112     end_dz = vlen(vecxy) / vlen(velxy) * vel_z - (end_z - start_z);
2113     zcurveparticles(effectno, start, end, end_dz, vlen(vel));
2114 }
2115
2116 string GetGametype(); // g_world.qc
2117 void write_recordmarker(entity pl, float tstart, float dt)
2118 {
2119     GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt / 10)));
2120
2121     // also write a marker into demo files for demotc-race-record-extractor to find
2122     stuffcmd(pl,
2123              strcat(
2124                  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", mmsss(dt * 10)),
2125                  " ", ftos(tstart), " ", ftos(dt), "\n"));
2126 }
2127
2128 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
2129 {
2130         string s;
2131         vector v;
2132
2133         if (cvar("g_shootfromclient"))
2134         {
2135                 switch(self.owner.cvar_cl_gunalign)
2136                 {
2137                         case 1: // right
2138                                 break;
2139
2140                         case 2: // left
2141                                 vecs_y = -vecs_y;
2142                                 break;
2143
2144                         default:
2145                         case 3: // center
2146                                 vecs_y = 0;
2147                                 vecs_z -= 4;
2148                                 break;
2149                 }
2150         }
2151         else if (cvar("g_shootfromeye"))
2152         {
2153                 if (visual)
2154                 {
2155                         vecs_y = 0;
2156                         vecs_z -= 4;
2157                 }
2158                 else
2159                 {
2160                         vecs_y = 0;
2161                         vecs_z = 0;
2162                 }
2163         }
2164         else if (cvar("g_shootfromcenter"))
2165         {
2166                 vecs_y = 0;
2167                 vecs_z -= 4;
2168         }
2169         else if ((s = cvar_string("g_shootfromfixedorigin")) != "")
2170         {
2171                 v = stov(s);
2172                 if (y_is_right)
2173                         v_y = -v_y;
2174                 if (v_x != 0)
2175                         vecs_x = v_x;
2176                 vecs_y = v_y;
2177                 vecs_z = v_z;
2178         }
2179         return vecs;
2180 }
2181
2182
2183
2184 void attach_sameorigin(entity e, entity to, string tag)
2185 {
2186     vector org, t_forward, t_left, t_up, e_forward, e_up;
2187     vector org0, ang0;
2188     float tagscale;
2189
2190     ang0 = e.angles;
2191     org0 = e.origin;
2192
2193     org = e.origin - gettaginfo(to, gettagindex(to, tag));
2194     tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
2195     t_forward = v_forward * tagscale;
2196     t_left = v_right * -tagscale;
2197     t_up = v_up * tagscale;
2198
2199     e.origin_x = org * t_forward;
2200     e.origin_y = org * t_left;
2201     e.origin_z = org * t_up;
2202
2203     // current forward and up directions
2204     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2205         e.angles_x = -e.angles_x;
2206     fixedmakevectors(e.angles);
2207
2208     // untransform forward, up!
2209     e_forward_x = v_forward * t_forward;
2210     e_forward_y = v_forward * t_left;
2211     e_forward_z = v_forward * t_up;
2212     e_up_x = v_up * t_forward;
2213     e_up_y = v_up * t_left;
2214     e_up_z = v_up * t_up;
2215
2216     e.angles = fixedvectoangles2(e_forward, e_up);
2217     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2218         e.angles_x = -e.angles_x;
2219
2220     setattachment(e, to, tag);
2221     setorigin(e, e.origin);
2222 }
2223
2224 void detach_sameorigin(entity e)
2225 {
2226     vector org;
2227     org = gettaginfo(e, 0);
2228     e.angles = fixedvectoangles2(v_forward, v_up);
2229     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2230         e.angles_x = -e.angles_x;
2231     e.origin = org;
2232     setattachment(e, world, "");
2233     setorigin(e, e.origin);
2234 }
2235
2236 void follow_sameorigin(entity e, entity to)
2237 {
2238     e.movetype = MOVETYPE_FOLLOW; // make the hole follow
2239     e.aiment = to; // make the hole follow bmodel
2240     e.punchangle = to.angles; // the original angles of bmodel
2241     e.view_ofs = e.origin - to.origin; // relative origin
2242     e.v_angle = e.angles - to.angles; // relative angles
2243 }
2244
2245 void unfollow_sameorigin(entity e)
2246 {
2247     e.movetype = MOVETYPE_NONE;
2248 }
2249
2250 entity gettaginfo_relative_ent;
2251 vector gettaginfo_relative(entity e, float tag)
2252 {
2253     if (!gettaginfo_relative_ent)
2254     {
2255         gettaginfo_relative_ent = spawn();
2256         gettaginfo_relative_ent.effects = EF_NODRAW;
2257     }
2258     gettaginfo_relative_ent.model = e.model;
2259     gettaginfo_relative_ent.modelindex = e.modelindex;
2260     gettaginfo_relative_ent.frame = e.frame;
2261     return gettaginfo(gettaginfo_relative_ent, tag);
2262 }
2263
2264 void SoundEntity_StartSound(entity pl, float chan, string samp, float vol, float attn)
2265 {
2266     float p;
2267     p = pow(2, chan);
2268     if (pl.soundentity.cnt & p)
2269         return;
2270     soundtoat(MSG_ALL, pl.soundentity, gettaginfo(pl.soundentity, 0), chan, samp, vol, attn);
2271     pl.soundentity.cnt |= p;
2272 }
2273
2274 void SoundEntity_StopSound(entity pl, float chan)
2275 {
2276     float p;
2277     p = pow(2, chan);
2278     if (pl.soundentity.cnt & p)
2279     {
2280         stopsoundto(MSG_ALL, pl.soundentity, chan);
2281         pl.soundentity.cnt &~= p;
2282     }
2283 }
2284
2285 void SoundEntity_Attach(entity pl)
2286 {
2287     pl.soundentity = spawn();
2288     pl.soundentity.classname = "soundentity";
2289     pl.soundentity.owner = pl;
2290     setattachment(pl.soundentity, pl, "");
2291     setmodel(pl.soundentity, "null");
2292 }
2293
2294 void SoundEntity_Detach(entity pl)
2295 {
2296     float i;
2297     for (i = 0; i <= 7; ++i)
2298         SoundEntity_StopSound(pl, i);
2299 }
2300
2301
2302 float ParseCommandPlayerSlotTarget_firsttoken;
2303 entity GetCommandPlayerSlotTargetFromTokenizedCommand(float tokens, float idx) // idx = start index
2304 {
2305     string s;
2306     entity e, head;
2307     float n;
2308
2309     s = string_null;
2310
2311     ParseCommandPlayerSlotTarget_firsttoken = -1;
2312
2313     if (tokens > idx)
2314     {
2315         if (substring(argv(idx), 0, 1) == "#")
2316         {
2317             s = substring(argv(idx), 1, -1);
2318             ++idx;
2319             if (s == "")
2320                 if (tokens > idx)
2321                 {
2322                     s = argv(idx);
2323                     ++idx;
2324                 }
2325             if (s == ftos(stof(s)))
2326             {
2327                 e = edict_num(stof(s));
2328                 if (e.flags & FL_CLIENT)
2329                 {
2330                     ParseCommandPlayerSlotTarget_firsttoken = idx;
2331                     return e;
2332                 }
2333             }
2334         }
2335         else
2336         {
2337             // it must be a nick name
2338             s = argv(idx);
2339             ++idx;
2340
2341             n = 0;
2342             FOR_EACH_CLIENT(head)
2343             if (head.netname == s)
2344             {
2345                 e = head;
2346                 ++n;
2347             }
2348             if (n == 1)
2349             {
2350                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2351                 return e;
2352             }
2353
2354             s = strdecolorize(s);
2355             n = 0;
2356             FOR_EACH_CLIENT(head)
2357             if (strdecolorize(head.netname) == s)
2358             {
2359                 e = head;
2360                 ++n;
2361             }
2362             if (n == 1)
2363             {
2364                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2365                 return e;
2366             }
2367         }
2368     }
2369
2370     return world;
2371 }