]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/miscfunctions.qc
change LOD code to support the models the way morphed named them. Next time, morphed...
[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_clones = cvar("sv_clones");
1021     sv_cheats = cvar("sv_cheats");
1022     sv_gentle = cvar("sv_gentle");
1023     sv_foginterval = cvar("sv_foginterval");
1024     g_cloaked = cvar("g_cloaked");
1025     g_jump_grunt = cvar("g_jump_grunt");
1026     g_footsteps = cvar("g_footsteps");
1027     g_grappling_hook = cvar("g_grappling_hook");
1028     g_jetpack = cvar("g_jetpack");
1029     g_laserguided_missile = cvar("g_laserguided_missile");
1030     g_midair = cvar("g_midair");
1031     g_minstagib = cvar("g_minstagib");
1032     g_nixnex = cvar("g_nixnex");
1033     g_nixnex_with_laser = cvar("g_nixnex_with_laser");
1034     g_norecoil = cvar("g_norecoil");
1035     g_vampire = cvar("g_vampire");
1036     g_bloodloss = cvar("g_bloodloss");
1037     sv_maxidle = cvar("sv_maxidle");
1038     sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
1039     sv_pogostick = cvar("sv_pogostick");
1040     sv_doublejump = cvar("sv_doublejump");
1041     g_maplist_allow_hidden = cvar("g_maplist_allow_hidden");
1042     g_ctf_reverse = cvar("g_ctf_reverse");
1043
1044     inWarmupStage = cvar("g_warmup");
1045     g_warmup_limit = cvar("g_warmup_limit");
1046     g_warmup_allguns = cvar("g_warmup_allguns");
1047     g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
1048
1049     if (g_race && g_race_qualifying == 2 || g_arena || g_assault || cvar("g_campaign"))
1050         inWarmupStage = 0; // these modes cannot work together, sorry
1051
1052     g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
1053     g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
1054     g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
1055     g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
1056     g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
1057     g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
1058     g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
1059     g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
1060     g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
1061     g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
1062     g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
1063     g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
1064
1065     if (g_minstagib) g_nixnex = g_weaponarena = 0;
1066     if (g_nixnex) g_weaponarena = 0;
1067     g_weaponarena = 0;
1068
1069     g_weaponspeedfactor = cvar("g_weaponspeedfactor");
1070     g_weapondamagefactor = cvar("g_weapondamagefactor");
1071
1072     g_pickup_shells                    = cvar("g_pickup_shells");
1073     g_pickup_shells_max                = cvar("g_pickup_shells_max");
1074     g_pickup_nails                     = cvar("g_pickup_nails");
1075     g_pickup_nails_max                 = cvar("g_pickup_nails_max");
1076     g_pickup_rockets                   = cvar("g_pickup_rockets");
1077     g_pickup_rockets_max               = cvar("g_pickup_rockets_max");
1078     g_pickup_cells                     = cvar("g_pickup_cells");
1079     g_pickup_cells_max                 = cvar("g_pickup_cells_max");
1080     g_pickup_fuel                     = cvar("g_pickup_fuel");
1081     g_pickup_fuel_jetpack             = cvar("g_pickup_fuel_jetpack");
1082     g_pickup_fuel_max                 = cvar("g_pickup_fuel_max");
1083     g_pickup_armorsmall                = cvar("g_pickup_armorsmall");
1084     g_pickup_armorsmall_max            = cvar("g_pickup_armorsmall_max");
1085     g_pickup_armormedium               = cvar("g_pickup_armormedium");
1086     g_pickup_armormedium_max           = cvar("g_pickup_armormedium_max");
1087     g_pickup_armorbig                  = cvar("g_pickup_armorbig");
1088     g_pickup_armorbig_max              = cvar("g_pickup_armorbig_max");
1089     g_pickup_armorlarge                = cvar("g_pickup_armorlarge");
1090     g_pickup_armorlarge_max            = cvar("g_pickup_armorlarge_max");
1091     g_pickup_healthsmall               = cvar("g_pickup_healthsmall");
1092     g_pickup_healthsmall_max           = cvar("g_pickup_healthsmall_max");
1093     g_pickup_healthmedium              = cvar("g_pickup_healthmedium");
1094     g_pickup_healthmedium_max          = cvar("g_pickup_healthmedium_max");
1095     g_pickup_healthlarge               = cvar("g_pickup_healthlarge");
1096     g_pickup_healthlarge_max           = cvar("g_pickup_healthlarge_max");
1097     g_pickup_healthmega                = cvar("g_pickup_healthmega");
1098     g_pickup_healthmega_max            = cvar("g_pickup_healthmega_max");
1099
1100     g_pinata = cvar("g_pinata");
1101
1102     g_weapon_stay = cvar("g_weapon_stay");
1103     if (!g_weapon_stay && (cvar("deathmatch") == 2))
1104         g_weapon_stay = 1;
1105
1106     if not(inWarmupStage)
1107         game_starttime                 = cvar("g_start_delay");
1108
1109     readplayerstartcvars();
1110 }
1111
1112 /*
1113 // TODO sound pack system
1114 string soundpack;
1115
1116 string precache_sound_builtin (string s) = #19;
1117 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1118 string precache_sound(string s)
1119 {
1120         return precache_sound_builtin(strcat(soundpack, s));
1121 }
1122 void play2(entity e, string filename)
1123 {
1124         stuffcmd(e, strcat("play2 ", soundpack, filename, "\n"));
1125 }
1126 void sound(entity e, float chan, string samp, float vol, float atten)
1127 {
1128         sound_builtin(e, chan, strcat(soundpack, samp), vol, atten);
1129 }
1130 */
1131
1132 // Sound functions
1133 string precache_sound (string s) = #19;
1134 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1135 float precache_sound_index (string s) = #19;
1136
1137 #define SND_VOLUME      1
1138 #define SND_ATTENUATION 2
1139 #define SND_LARGEENTITY 8
1140 #define SND_LARGESOUND  16
1141
1142 float sound_allowed(float dest, entity e)
1143 {
1144     // sounds from world may always pass
1145     for (;;)
1146     {
1147         if (e.classname == "body")
1148             e = e.enemy;
1149         if (e.owner && e.owner != e)
1150             e = e.owner;
1151         else
1152             break;
1153     }
1154     // sounds to self may always pass
1155     if (dest == MSG_ONE)
1156         if (e == msg_entity)
1157             return TRUE;
1158     // sounds by players can be removed
1159     if (cvar("bot_sound_monopoly"))
1160         if (clienttype(e) == CLIENTTYPE_REAL)
1161             return FALSE;
1162     // anything else may pass
1163     return TRUE;
1164 }
1165
1166 void sound(entity e, float chan, string samp, float vol, float atten)
1167 {
1168     if (!sound_allowed(MSG_BROADCAST, e))
1169         return;
1170     sound_builtin(e, chan, samp, vol, atten);
1171 }
1172 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
1173 {
1174     float entno, idx;
1175
1176     if (!sound_allowed(dest, e))
1177         return;
1178
1179     entno = num_for_edict(e);
1180     idx = precache_sound_index(samp);
1181
1182     float sflags;
1183     sflags = 0;
1184
1185     atten = floor(atten * 64);
1186     vol = floor(vol * 255);
1187
1188     if (vol != 255)
1189         sflags |= SND_VOLUME;
1190     if (atten != 64)
1191         sflags |= SND_ATTENUATION;
1192     if (entno >= 8192)
1193         sflags |= SND_LARGEENTITY;
1194     if (idx >= 256)
1195         sflags |= SND_LARGESOUND;
1196
1197     WriteByte(dest, SVC_SOUND);
1198     WriteByte(dest, sflags);
1199     if (sflags & SND_VOLUME)
1200         WriteByte(dest, vol);
1201     if (sflags & SND_ATTENUATION)
1202         WriteByte(dest, atten);
1203     if (sflags & SND_LARGEENTITY)
1204     {
1205         WriteShort(dest, entno);
1206         WriteByte(dest, chan);
1207     }
1208     else
1209     {
1210         WriteShort(dest, entno * 8 + chan);
1211     }
1212     if (sflags & SND_LARGESOUND)
1213         WriteShort(dest, idx);
1214     else
1215         WriteByte(dest, idx);
1216
1217     WriteCoord(dest, o_x);
1218     WriteCoord(dest, o_y);
1219     WriteCoord(dest, o_z);
1220 }
1221 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
1222 {
1223     vector o;
1224
1225     if (!sound_allowed(dest, e))
1226         return;
1227
1228     o = e.origin + 0.5 * (e.mins + e.maxs);
1229     soundtoat(dest, e, o, chan, samp, vol, atten);
1230 }
1231 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
1232 {
1233     soundtoat(MSG_BROADCAST, e, o, chan, samp, vol, atten);
1234 }
1235 void stopsoundto(float dest, entity e, float chan)
1236 {
1237     float entno;
1238
1239     if (!sound_allowed(dest, e))
1240         return;
1241
1242     entno = num_for_edict(e);
1243
1244     if (entno >= 8192)
1245     {
1246         float idx, sflags;
1247         idx = precache_sound_index("misc/null.wav");
1248         sflags = SND_LARGEENTITY;
1249         if (idx >= 256)
1250             sflags |= SND_LARGESOUND;
1251         WriteByte(dest, SVC_SOUND);
1252         WriteByte(dest, sflags);
1253         WriteShort(dest, entno);
1254         WriteByte(dest, chan);
1255         if (sflags & SND_LARGESOUND)
1256             WriteShort(dest, idx);
1257         else
1258             WriteByte(dest, idx);
1259         WriteCoord(dest, e.origin_x);
1260         WriteCoord(dest, e.origin_y);
1261         WriteCoord(dest, e.origin_z);
1262     }
1263     else
1264     {
1265         WriteByte(dest, SVC_STOPSOUND);
1266         WriteShort(dest, entno * 8 + chan);
1267     }
1268 }
1269 void stopsound(entity e, float chan)
1270 {
1271     if (!sound_allowed(MSG_BROADCAST, e))
1272         return;
1273
1274     stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
1275     stopsoundto(MSG_ALL, e, chan); // in case of packet loss
1276 }
1277
1278 void play2(entity e, string filename)
1279 {
1280     //stuffcmd(e, strcat("play2 ", filename, "\n"));
1281     msg_entity = e;
1282     soundtoat(MSG_ONE, world, '0 0 0', CHAN_AUTO, filename, VOL_BASE, ATTN_NONE);
1283 }
1284
1285 .float announcetime;
1286 float announce(entity player, string msg)
1287 {
1288     if (time > player.announcetime)
1289         if (clienttype(player) == CLIENTTYPE_REAL)
1290         {
1291             player.announcetime = time + 0.8;
1292             play2(player, msg);
1293             return TRUE;
1294         }
1295     return FALSE;
1296 }
1297 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
1298 float spamsound(entity e, float chan, string samp, float vol, float atten)
1299 {
1300     if (!sound_allowed(MSG_BROADCAST, e))
1301         return FALSE;
1302
1303     if (time > e.announcetime)
1304     {
1305         e.announcetime = time;
1306         sound(e, chan, samp, vol, atten);
1307         return TRUE;
1308     }
1309     return FALSE;
1310 }
1311
1312 void play2team(float t, string filename)
1313 {
1314     local entity head;
1315
1316     if (cvar("bot_sound_monopoly"))
1317         return;
1318
1319     FOR_EACH_REALPLAYER(head)
1320     {
1321         if (head.team == t)
1322             play2(head, filename);
1323     }
1324 }
1325
1326 void play2all(string samp)
1327 {
1328     if (cvar("bot_sound_monopoly"))
1329         return;
1330
1331     sound(world, CHAN_AUTO, samp, VOL_BASE, ATTN_NONE);
1332 }
1333
1334 void PrecachePlayerSounds(string f);
1335 void precache_all_models(string pattern)
1336 {
1337     float globhandle, i, n;
1338     string f;
1339
1340     globhandle = search_begin(pattern, TRUE, FALSE);
1341     if (globhandle < 0)
1342         return;
1343     n = search_getsize(globhandle);
1344     for (i = 0; i < n; ++i)
1345     {
1346         //print(search_getfilename(globhandle, i), "\n");
1347         f = search_getfilename(globhandle, i);
1348 #ifdef ALLOW_VARIABLE_LOD
1349         precache_model(f);
1350 #endif
1351         if(substring(f, strlen(f)-6,2) == "_lod1")
1352                 continue;
1353         if(substring(f, strlen(f)-6,2) == "_lod2")
1354                 continue;
1355 #ifndef ALLOW_VARIABLE_LOD
1356         precache_model(f);
1357 #endif
1358         PrecachePlayerSounds(strcat(f, ".sounds"));
1359     }
1360     search_end(globhandle);
1361 }
1362
1363 void precache()
1364 {
1365     // gamemode related things
1366     precache_model ("models/misc/chatbubble.spr");
1367     precache_model ("models/misc/teambubble.spr");
1368     if (g_runematch)
1369     {
1370         precache_model ("models/runematch/curse.mdl");
1371         precache_model ("models/runematch/rune.mdl");
1372     }
1373
1374 #ifdef TTURRETS_ENABLED
1375     if (cvar("g_turrets"))
1376         turrets_precash();
1377 #endif
1378
1379     // Precache all player models if desired
1380     if (cvar("sv_precacheplayermodels"))
1381     {
1382         PrecachePlayerSounds("sound/player/default.sounds");
1383         precache_all_models("models/player/*.zym");
1384         precache_all_models("models/player/*.dpm");
1385         precache_all_models("models/player/*.md3");
1386         precache_all_models("models/player/*.psk");
1387         //precache_model("models/player/carni.zym");
1388         //precache_model("models/player/crash.zym");
1389         //precache_model("models/player/grunt.zym");
1390         //precache_model("models/player/headhunter.zym");
1391         //precache_model("models/player/insurrectionist.zym");
1392         //precache_model("models/player/jeandarc.zym");
1393         //precache_model("models/player/lurk.zym");
1394         //precache_model("models/player/lycanthrope.zym");
1395         //precache_model("models/player/marine.zym");
1396         //precache_model("models/player/nexus.zym");
1397         //precache_model("models/player/pyria.zym");
1398         //precache_model("models/player/shock.zym");
1399         //precache_model("models/player/skadi.zym");
1400         //precache_model("models/player/specop.zym");
1401         //precache_model("models/player/visitant.zym");
1402     }
1403
1404     if (cvar("sv_defaultcharacter"))
1405     {
1406         string s;
1407         s = cvar_string("sv_defaultplayermodel_red");
1408         if (s != "")
1409         {
1410             precache_model(s);
1411             PrecachePlayerSounds(strcat(s, ".sounds"));
1412         }
1413         s = cvar_string("sv_defaultplayermodel_blue");
1414         if (s != "")
1415         {
1416             precache_model(s);
1417             PrecachePlayerSounds(strcat(s, ".sounds"));
1418         }
1419         s = cvar_string("sv_defaultplayermodel_yellow");
1420         if (s != "")
1421         {
1422             precache_model(s);
1423             PrecachePlayerSounds(strcat(s, ".sounds"));
1424         }
1425         s = cvar_string("sv_defaultplayermodel_pink");
1426         if (s != "")
1427         {
1428             precache_model(s);
1429             PrecachePlayerSounds(strcat(s, ".sounds"));
1430         }
1431         s = cvar_string("sv_defaultplayermodel");
1432         if (s != "")
1433         {
1434             precache_model(s);
1435             PrecachePlayerSounds(strcat(s, ".sounds"));
1436         }
1437     }
1438
1439     if (g_footsteps)
1440     {
1441         PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1442         PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1443     }
1444
1445     // gore and miscellaneous sounds
1446     //precache_sound ("misc/h2ohit.wav");
1447     precache_model ("models/hook.md3");
1448     precache_sound ("misc/armorimpact.wav");
1449     precache_sound ("misc/bodyimpact1.wav");
1450     precache_sound ("misc/bodyimpact2.wav");
1451     precache_sound ("misc/gib.wav");
1452     precache_sound ("misc/gib_splat01.wav");
1453     precache_sound ("misc/gib_splat02.wav");
1454     precache_sound ("misc/gib_splat03.wav");
1455     precache_sound ("misc/gib_splat04.wav");
1456     precache_sound ("misc/hit.wav");
1457     PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1458     PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1459     precache_sound ("misc/null.wav");
1460     precache_sound ("misc/spawn.wav");
1461     precache_sound ("misc/talk.wav");
1462     precache_sound ("misc/teleport.wav");
1463     precache_sound ("misc/poweroff.wav");
1464     precache_sound ("player/lava.wav");
1465     precache_sound ("player/slime.wav");
1466
1467     if (g_jetpack)
1468         precache_sound ("misc/jetpack_fly.wav");
1469
1470     // announcer sounds - male
1471     precache_sound ("announcer/male/electrobitch.wav");
1472     precache_sound ("announcer/male/airshot.wav");
1473     precache_sound ("announcer/male/03kills.wav");
1474     precache_sound ("announcer/male/05kills.wav");
1475     precache_sound ("announcer/male/10kills.wav");
1476     precache_sound ("announcer/male/15kills.wav");
1477     precache_sound ("announcer/male/20kills.wav");
1478     precache_sound ("announcer/male/25kills.wav");
1479     precache_sound ("announcer/male/30kills.wav");
1480     precache_sound ("announcer/male/botlike.wav");
1481     precache_sound ("announcer/male/yoda.wav");
1482     precache_sound ("announcer/male/amazing.wav");
1483     precache_sound ("announcer/male/awesome.wav");
1484     precache_sound ("announcer/male/headshot.wav");
1485     precache_sound ("announcer/male/impressive.wav");
1486
1487     // announcer sounds - robotic
1488     precache_sound ("announcer/robotic/prepareforbattle.wav");
1489     precache_sound ("announcer/robotic/begin.wav");
1490     precache_sound ("announcer/robotic/timeoutcalled.wav");
1491     precache_sound ("announcer/robotic/1fragleft.wav");
1492     precache_sound ("announcer/robotic/2fragsleft.wav");
1493     precache_sound ("announcer/robotic/3fragsleft.wav");
1494     if (g_minstagib)
1495     {
1496         precache_sound ("announcer/robotic/lastsecond.wav");
1497         precache_sound ("announcer/robotic/narrowly.wav");
1498     }
1499
1500     precache_model ("models/sprites/1.spr32");
1501     precache_model ("models/sprites/2.spr32");
1502     precache_model ("models/sprites/3.spr32");
1503     precache_model ("models/sprites/4.spr32");
1504     precache_model ("models/sprites/5.spr32");
1505     precache_model ("models/sprites/6.spr32");
1506     precache_model ("models/sprites/7.spr32");
1507     precache_model ("models/sprites/8.spr32");
1508     precache_model ("models/sprites/9.spr32");
1509     precache_model ("models/sprites/10.spr32");
1510     precache_sound ("announcer/robotic/1.wav");
1511     precache_sound ("announcer/robotic/2.wav");
1512     precache_sound ("announcer/robotic/3.wav");
1513     precache_sound ("announcer/robotic/4.wav");
1514     precache_sound ("announcer/robotic/5.wav");
1515     precache_sound ("announcer/robotic/6.wav");
1516     precache_sound ("announcer/robotic/7.wav");
1517     precache_sound ("announcer/robotic/8.wav");
1518     precache_sound ("announcer/robotic/9.wav");
1519     precache_sound ("announcer/robotic/10.wav");
1520
1521     // common weapon precaches
1522     precache_sound ("weapons/weapon_switch.wav");
1523     precache_sound ("weapons/weaponpickup.wav");
1524     precache_sound ("weapons/unavailable.wav");
1525     if (g_grappling_hook)
1526     {
1527         precache_sound ("weapons/hook_fire.wav"); // hook
1528         precache_sound ("weapons/hook_impact.wav"); // hook
1529     }
1530
1531     if (cvar("sv_precacheweapons") || g_nixnex)
1532     {
1533         //precache weapon models/sounds
1534         local float wep;
1535         wep = WEP_FIRST;
1536         while (wep <= WEP_LAST)
1537         {
1538             weapon_action(wep, WR_PRECACHE);
1539             wep = wep + 1;
1540         }
1541     }
1542
1543     precache_model("models/elaser.mdl");
1544     precache_model("models/laser.mdl");
1545     precache_model("models/ebomb.mdl");
1546
1547 #if 0
1548     // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1549
1550     if (!self.noise && self.music) // quake 3 uses the music field
1551         self.noise = self.music;
1552
1553     // plays music for the level if there is any
1554     if (self.noise)
1555     {
1556         precache_sound (self.noise);
1557         ambientsound ('0 0 0', self.noise, VOL_BASE, ATTN_NONE);
1558     }
1559 #endif
1560 }
1561
1562 // sorry, but using \ in macros breaks line numbers
1563 #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
1564 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
1565 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
1566
1567 vector ExactTriggerHit_mins;
1568 vector ExactTriggerHit_maxs;
1569 float ExactTriggerHit_Recurse()
1570 {
1571     float s;
1572     entity se;
1573     float f;
1574
1575     tracebox('0 0 0', ExactTriggerHit_mins, ExactTriggerHit_maxs, '0 0 0', MOVE_NORMAL, other);
1576     if not(trace_ent)
1577         return 0;
1578     if (trace_ent == self)
1579         return 1;
1580
1581     se = trace_ent;
1582     s = se.solid;
1583     se.solid = SOLID_NOT;
1584     f = ExactTriggerHit_Recurse();
1585     se.solid = s;
1586
1587     return f;
1588 }
1589
1590 float ExactTriggerHit()
1591 {
1592     float f, s;
1593
1594     if not(self.modelindex)
1595         return 1;
1596
1597     s = self.solid;
1598     self.solid = SOLID_BSP;
1599     ExactTriggerHit_mins = other.absmin;
1600     ExactTriggerHit_maxs = other.absmax;
1601     f = ExactTriggerHit_Recurse();
1602     self.solid = s;
1603
1604     return f;
1605 }
1606
1607 // WARNING: this kills the trace globals
1608 #define EXACTTRIGGER_TOUCH if not(ExactTriggerHit()) return
1609 #define EXACTTRIGGER_INIT  InitSolidBSPTrigger(); self.solid = SOLID_TRIGGER
1610
1611 #define INITPRIO_FIRST              0
1612 #define INITPRIO_GAMETYPE           0
1613 #define INITPRIO_GAMETYPE_FALLBACK  1
1614 #define INITPRIO_CVARS              5
1615 #define INITPRIO_FINDTARGET        10
1616 #define INITPRIO_DROPTOFLOOR       20
1617 #define INITPRIO_SETLOCATION       90
1618 #define INITPRIO_LINKDOORS         91
1619 #define INITPRIO_LAST              99
1620
1621 .void(void) initialize_entity;
1622 .float initialize_entity_order;
1623 .entity initialize_entity_next;
1624 entity initialize_entity_first;
1625
1626 void make_safe_for_remove(entity e)
1627 {
1628     if (e.initialize_entity)
1629     {
1630         entity ent, prev;
1631         for (ent = initialize_entity_first; ent; )
1632         {
1633             if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1634             {
1635                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1636                 // skip it in linked list
1637                 if (prev)
1638                 {
1639                     prev.initialize_entity_next = ent.initialize_entity_next;
1640                     ent = prev.initialize_entity_next;
1641                 }
1642                 else
1643                 {
1644                     initialize_entity_first = ent.initialize_entity_next;
1645                     ent = initialize_entity_first;
1646                 }
1647             }
1648             else
1649             {
1650                 prev = ent;
1651                 ent = ent.initialize_entity_next;
1652             }
1653         }
1654     }
1655 }
1656
1657 void objerror(string s)
1658 {
1659     make_safe_for_remove(self);
1660     objerror_builtin(s);
1661 }
1662
1663 void remove_unsafely(entity e)
1664 {
1665     remove_builtin(e);
1666 }
1667
1668 void remove_safely(entity e)
1669 {
1670     make_safe_for_remove(e);
1671     remove_builtin(e);
1672 }
1673
1674 void InitializeEntity(entity e, void(void) func, float order)
1675 {
1676     entity prev, cur;
1677
1678     if (!e || e.initialize_entity)
1679     {
1680         // make a proxy initializer entity
1681         entity e_old;
1682         e_old = e;
1683         e = spawn();
1684         e.classname = "initialize_entity";
1685         e.enemy = e_old;
1686     }
1687
1688     e.initialize_entity = func;
1689     e.initialize_entity_order = order;
1690
1691     cur = initialize_entity_first;
1692     for (;;)
1693     {
1694         if (!cur || cur.initialize_entity_order > order)
1695         {
1696             // insert between prev and cur
1697             if (prev)
1698                 prev.initialize_entity_next = e;
1699             else
1700                 initialize_entity_first = e;
1701             e.initialize_entity_next = cur;
1702             return;
1703         }
1704         prev = cur;
1705         cur = cur.initialize_entity_next;
1706     }
1707 }
1708 void InitializeEntitiesRun()
1709 {
1710     entity startoflist;
1711     startoflist = initialize_entity_first;
1712     initialize_entity_first = world;
1713     for (self = startoflist; self; )
1714     {
1715         entity e;
1716         var void(void) func;
1717         e = self.initialize_entity_next;
1718         func = self.initialize_entity;
1719         self.initialize_entity_order = 0;
1720         self.initialize_entity = func_null;
1721         self.initialize_entity_next = world;
1722         if (self.classname == "initialize_entity")
1723         {
1724             entity e_old;
1725             e_old = self.enemy;
1726             remove_builtin(self);
1727             self = e_old;
1728         }
1729         //dprint("Delayed initialization: ", self.classname, "\n");
1730         func();
1731         self = e;
1732     }
1733 }
1734
1735 .float uncustomizeentityforclient_set;
1736 .void(void) uncustomizeentityforclient;
1737 void(void) SUB_Nullpointer = #0;
1738 void UncustomizeEntitiesRun()
1739 {
1740     entity oldself;
1741     oldself = self;
1742     for (self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1743         self.uncustomizeentityforclient();
1744     self = oldself;
1745 }
1746 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1747 {
1748     e.customizeentityforclient = customizer;
1749     e.uncustomizeentityforclient = uncustomizer;
1750     e.uncustomizeentityforclient_set = (uncustomizer != SUB_Nullpointer);
1751 }
1752
1753 .float nottargeted;
1754 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1755
1756 void() SUB_Remove;
1757 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1758 {
1759     vector mi, ma;
1760
1761     if (e.classname == "")
1762         e.classname = "net_linked";
1763
1764     if (e.model == "" || self.modelindex == 0)
1765     {
1766         mi = e.mins;
1767         ma = e.maxs;
1768         setmodel(e, "null");
1769         setsize(e, mi, ma);
1770     }
1771
1772     e.SendEntity = sendfunc;
1773     e.SendFlags = 0xFFFFFF;
1774
1775     if (!docull)
1776         e.effects |= EF_NODEPTHTEST;
1777
1778     if (dt)
1779     {
1780         e.nextthink = time + dt;
1781         e.think = SUB_Remove;
1782     }
1783 }
1784
1785 void adaptor_think2touch()
1786 {
1787     entity o;
1788     o = other;
1789     other = world;
1790     self.touch();
1791     other = o;
1792 }
1793
1794 void adaptor_think2use()
1795 {
1796     entity o, a;
1797     o = other;
1798     a = activator;
1799     activator = world;
1800     other = world;
1801     self.use();
1802     other = o;
1803     activator = a;
1804 }
1805
1806 // deferred dropping
1807 void DropToFloor_Handler()
1808 {
1809     droptofloor_builtin();
1810     self.dropped_origin = self.origin;
1811 }
1812
1813 void droptofloor()
1814 {
1815     InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1816 }
1817
1818
1819
1820 float trace_hits_box_a0, trace_hits_box_a1;
1821
1822 float trace_hits_box_1d(float end, float thmi, float thma)
1823 {
1824     if (end == 0)
1825     {
1826         // just check if x is in range
1827         if (0 < thmi)
1828             return FALSE;
1829         if (0 > thma)
1830             return FALSE;
1831     }
1832     else
1833     {
1834         // do the trace with respect to x
1835         // 0 -> end has to stay in thmi -> thma
1836         trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1837         trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1838         if (trace_hits_box_a0 > trace_hits_box_a1)
1839             return FALSE;
1840     }
1841     return TRUE;
1842 }
1843
1844 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1845 {
1846     end -= start;
1847     thmi -= start;
1848     thma -= start;
1849     // now it is a trace from 0 to end
1850
1851     trace_hits_box_a0 = 0;
1852     trace_hits_box_a1 = 1;
1853
1854     if (!trace_hits_box_1d(end_x, thmi_x, thma_x))
1855         return FALSE;
1856     if (!trace_hits_box_1d(end_y, thmi_y, thma_y))
1857         return FALSE;
1858     if (!trace_hits_box_1d(end_z, thmi_z, thma_z))
1859         return FALSE;
1860
1861     return TRUE;
1862 }
1863
1864 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1865 {
1866     return trace_hits_box(start, end, thmi - ma, thma - mi);
1867 }
1868
1869 float SUB_NoImpactCheck()
1870 {
1871     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1872         return 1;
1873     if (other == world && self.size != '0 0 0')
1874     {
1875         vector tic;
1876         tic = self.velocity * sys_ticrate;
1877         tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1878         traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1879         if (trace_fraction >= 1)
1880         {
1881             dprint("Odd... did not hit...?\n");
1882         }
1883         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1884         {
1885             dprint("Detected and prevented the sky-grapple bug.\n");
1886             return 1;
1887         }
1888     }
1889
1890     return 0;
1891 }
1892
1893 #define SUB_OwnerCheck() (other && (other == self.owner))
1894
1895 #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)
1896
1897 float MAX_IPBAN_URIS = 16;
1898
1899 float URI_GET_DISCARD   = 0;
1900 float URI_GET_IPBAN     = 1;
1901 float URI_GET_IPBAN_END = 16;
1902
1903 void URI_Get_Callback(float id, float status, string data)
1904 {
1905     dprint("Received HTTP request data for id ", ftos(id), "; status is ", ftos(status), "\nData is:\n");
1906     dprint(data);
1907     dprint("\nEnd of data.\n");
1908
1909     if (id == URI_GET_DISCARD)
1910     {
1911         // discard
1912     }
1913     else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1914     {
1915         // online ban list
1916         OnlineBanList_URI_Get_Callback(id, status, data);
1917     }
1918     else
1919     {
1920         print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1921     }
1922 }
1923
1924 void print_to(entity e, string s)
1925 {
1926     if (e)
1927         sprint(e, strcat(s, "\n"));
1928     else
1929         print(s, "\n");
1930 }
1931
1932 string getrecords()
1933 {
1934     float rec;
1935     string h;
1936     float r;
1937     float i;
1938     string s;
1939
1940     rec = 0;
1941
1942     s = "";
1943
1944     if (g_ctf)
1945     {
1946         for (i = 0; i < MapInfo_count; ++i)
1947         {
1948             if (MapInfo_Get_ByID(i))
1949             {
1950                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
1951                 if (r == 0)
1952                     continue;
1953                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
1954                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
1955                 ++rec;
1956             }
1957         }
1958     }
1959
1960     if (g_race)
1961     {
1962         for (i = 0; i < MapInfo_count; ++i)
1963         {
1964             if (MapInfo_Get_ByID(i))
1965             {
1966                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/time")));
1967                 if (r == 0)
1968                     continue;
1969                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/netname"));
1970                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, mmsss(r)), " ", h, "\n");
1971                 ++rec;
1972             }
1973         }
1974     }
1975     MapInfo_ClearTemps();
1976
1977     if (s == "")
1978         return "No records are available on this server.\n";
1979     else
1980         return strcat("Records on this server:\n", s);
1981 }
1982
1983 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1984 {
1985     float m, i;
1986     vector start, org, delta, end, enddown, mstart;
1987
1988     m = e.dphitcontentsmask;
1989     e.dphitcontentsmask = goodcontents | badcontents;
1990
1991     org = world.mins;
1992     delta = world.maxs - world.mins;
1993
1994     for (i = 0; i < attempts; ++i)
1995     {
1996         start_x = org_x + random() * delta_x;
1997         start_y = org_y + random() * delta_y;
1998         start_z = org_z + random() * delta_z;
1999
2000         // rule 1: start inside world bounds, and outside
2001         // solid, and don't start from somewhere where you can
2002         // fall down to evil
2003         tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta_z, MOVE_NORMAL, e);
2004         if (trace_fraction >= 1)
2005             continue;
2006         if (trace_startsolid)
2007             continue;
2008         if (trace_dphitcontents & badcontents)
2009             continue;
2010         if (trace_dphitq3surfaceflags & badsurfaceflags)
2011             continue;
2012
2013         // rule 2: if we are too high, lower the point
2014         if (trace_fraction * delta_z > maxaboveground)
2015             start = trace_endpos + '0 0 1' * maxaboveground;
2016         enddown = trace_endpos;
2017
2018         // rule 3: make sure we aren't outside the map. This only works
2019         // for somewhat well formed maps. A good rule of thumb is that
2020         // the map should have a convex outside hull.
2021         // these can be traceLINES as we already verified the starting box
2022         mstart = start + 0.5 * (e.mins + e.maxs);
2023         traceline(mstart, mstart + '1 0 0' * delta_x, MOVE_NORMAL, e);
2024         if (trace_fraction >= 1)
2025             continue;
2026         traceline(mstart, mstart - '1 0 0' * delta_x, MOVE_NORMAL, e);
2027         if (trace_fraction >= 1)
2028             continue;
2029         traceline(mstart, mstart + '0 1 0' * delta_y, MOVE_NORMAL, e);
2030         if (trace_fraction >= 1)
2031             continue;
2032         traceline(mstart, mstart - '0 1 0' * delta_y, MOVE_NORMAL, e);
2033         if (trace_fraction >= 1)
2034             continue;
2035         traceline(mstart, mstart + '0 0 1' * delta_z, MOVE_NORMAL, e);
2036         if (trace_fraction >= 1)
2037             continue;
2038
2039         // find a random vector to "look at"
2040         end_x = org_x + random() * delta_x;
2041         end_y = org_y + random() * delta_y;
2042         end_z = org_z + random() * delta_z;
2043         end = start + normalize(end - start) * vlen(delta);
2044
2045         // rule 4: start TO end must not be too short
2046         tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
2047         if (trace_startsolid)
2048             continue;
2049         if (trace_fraction < minviewdistance / vlen(delta))
2050             continue;
2051
2052         // rule 5: don't want to look at sky
2053         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
2054             continue;
2055
2056         // rule 6: we must not end up in trigger_hurt
2057         if (tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
2058         {
2059             dprint("trigger_hurt! ouch! and nothing else could find it!\n");
2060             continue;
2061         }
2062
2063         break;
2064     }
2065
2066     e.dphitcontentsmask = m;
2067
2068     if (i < attempts)
2069     {
2070         setorigin(e, start);
2071         e.angles = vectoangles(end - start);
2072         dprint("Needed ", ftos(i + 1), " attempts\n");
2073         return TRUE;
2074     }
2075     else
2076         return FALSE;
2077 }
2078
2079 void zcurveparticles(float effectno, vector start, vector end, float end_dz, float spd)
2080 {
2081     WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
2082     WriteByte(MSG_BROADCAST, TE_CSQC_ZCURVEPARTICLES);
2083     WriteShort(MSG_BROADCAST, effectno);
2084     WriteCoord(MSG_BROADCAST, start_x);
2085     WriteCoord(MSG_BROADCAST, start_y);
2086     WriteCoord(MSG_BROADCAST, start_z);
2087     WriteCoord(MSG_BROADCAST, end_x);
2088     WriteCoord(MSG_BROADCAST, end_y);
2089     WriteCoord(MSG_BROADCAST, end_z);
2090     WriteCoord(MSG_BROADCAST, end_dz);
2091     WriteShort(MSG_BROADCAST, spd / 16);
2092 }
2093
2094 void zcurveparticles_from_tracetoss(float effectno, vector start, vector end, vector vel)
2095 {
2096     float end_dz;
2097     vector vecxy, velxy;
2098
2099     vecxy = end - start;
2100     vecxy_z = 0;
2101     velxy = vel;
2102     velxy_z = 0;
2103
2104     if (vlen(velxy) < 0.000001 * fabs(vel_z))
2105     {
2106         trailparticles(world, effectno, start, end);
2107         return;
2108     }
2109
2110     end_dz = vlen(vecxy) / vlen(velxy) * vel_z - (end_z - start_z);
2111     zcurveparticles(effectno, start, end, end_dz, vlen(vel));
2112 }
2113
2114 string GetGametype(); // g_world.qc
2115 void write_recordmarker(entity pl, float tstart, float dt)
2116 {
2117     GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt / 10)));
2118
2119     // also write a marker into demo files for demotc-race-record-extractor to find
2120     stuffcmd(pl,
2121              strcat(
2122                  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", mmsss(dt * 10)),
2123                  " ", ftos(tstart), " ", ftos(dt), "\n"));
2124 }
2125
2126 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
2127 {
2128         string s;
2129         vector v;
2130
2131         if (cvar("g_shootfromclient"))
2132         {
2133                 switch(self.owner.cvar_cl_gunalign)
2134                 {
2135                         case 1: // right
2136                                 break;
2137
2138                         case 2: // left
2139                                 vecs_y = -vecs_y;
2140                                 break;
2141
2142                         default:
2143                         case 3: // center
2144                                 vecs_y = 0;
2145                                 vecs_z -= 4;
2146                                 break;
2147                 }
2148         }
2149         else if (cvar("g_shootfromeye"))
2150         {
2151                 if (visual)
2152                 {
2153                         vecs_y = 0;
2154                         vecs_z -= 4;
2155                 }
2156                 else
2157                 {
2158                         vecs_y = 0;
2159                         vecs_z = 0;
2160                 }
2161         }
2162         else if (cvar("g_shootfromcenter"))
2163         {
2164                 vecs_y = 0;
2165                 vecs_z -= 4;
2166         }
2167         else if ((s = cvar_string("g_shootfromfixedorigin")) != "")
2168         {
2169                 v = stov(s);
2170                 if (y_is_right)
2171                         v_y = -v_y;
2172                 if (v_x != 0)
2173                         vecs_x = v_x;
2174                 vecs_y = v_y;
2175                 vecs_z = v_z;
2176         }
2177         return vecs;
2178 }
2179
2180
2181
2182 void attach_sameorigin(entity e, entity to, string tag)
2183 {
2184     vector org, t_forward, t_left, t_up, e_forward, e_up;
2185     vector org0, ang0;
2186     float tagscale;
2187
2188     ang0 = e.angles;
2189     org0 = e.origin;
2190
2191     org = e.origin - gettaginfo(to, gettagindex(to, tag));
2192     tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
2193     t_forward = v_forward * tagscale;
2194     t_left = v_right * -tagscale;
2195     t_up = v_up * tagscale;
2196
2197     e.origin_x = org * t_forward;
2198     e.origin_y = org * t_left;
2199     e.origin_z = org * t_up;
2200
2201     // current forward and up directions
2202     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2203         e.angles_x = -e.angles_x;
2204     fixedmakevectors(e.angles);
2205
2206     // untransform forward, up!
2207     e_forward_x = v_forward * t_forward;
2208     e_forward_y = v_forward * t_left;
2209     e_forward_z = v_forward * t_up;
2210     e_up_x = v_up * t_forward;
2211     e_up_y = v_up * t_left;
2212     e_up_z = v_up * t_up;
2213
2214     e.angles = fixedvectoangles2(e_forward, e_up);
2215     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2216         e.angles_x = -e.angles_x;
2217
2218     setattachment(e, to, tag);
2219     setorigin(e, e.origin);
2220 }
2221
2222 void detach_sameorigin(entity e)
2223 {
2224     vector org;
2225     org = gettaginfo(e, 0);
2226     e.angles = fixedvectoangles2(v_forward, v_up);
2227     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
2228         e.angles_x = -e.angles_x;
2229     e.origin = org;
2230     setattachment(e, world, "");
2231     setorigin(e, e.origin);
2232 }
2233
2234 void follow_sameorigin(entity e, entity to)
2235 {
2236     e.movetype = MOVETYPE_FOLLOW; // make the hole follow
2237     e.aiment = to; // make the hole follow bmodel
2238     e.punchangle = to.angles; // the original angles of bmodel
2239     e.view_ofs = e.origin - to.origin; // relative origin
2240     e.v_angle = e.angles - to.angles; // relative angles
2241 }
2242
2243 void unfollow_sameorigin(entity e)
2244 {
2245     e.movetype = MOVETYPE_NONE;
2246 }
2247
2248 entity gettaginfo_relative_ent;
2249 vector gettaginfo_relative(entity e, float tag)
2250 {
2251     if (!gettaginfo_relative_ent)
2252     {
2253         gettaginfo_relative_ent = spawn();
2254         gettaginfo_relative_ent.effects = EF_NODRAW;
2255     }
2256     gettaginfo_relative_ent.model = e.model;
2257     gettaginfo_relative_ent.modelindex = e.modelindex;
2258     gettaginfo_relative_ent.frame = e.frame;
2259     return gettaginfo(gettaginfo_relative_ent, tag);
2260 }
2261
2262 void SoundEntity_StartSound(entity pl, float chan, string samp, float vol, float attn)
2263 {
2264     float p;
2265     p = pow(2, chan);
2266     if (pl.soundentity.cnt & p)
2267         return;
2268     soundtoat(MSG_ALL, pl.soundentity, gettaginfo(pl.soundentity, 0), chan, samp, vol, attn);
2269     pl.soundentity.cnt |= p;
2270 }
2271
2272 void SoundEntity_StopSound(entity pl, float chan)
2273 {
2274     float p;
2275     p = pow(2, chan);
2276     if (pl.soundentity.cnt & p)
2277     {
2278         stopsoundto(MSG_ALL, pl.soundentity, chan);
2279         pl.soundentity.cnt &~= p;
2280     }
2281 }
2282
2283 void SoundEntity_Attach(entity pl)
2284 {
2285     pl.soundentity = spawn();
2286     pl.soundentity.classname = "soundentity";
2287     pl.soundentity.owner = pl;
2288     setattachment(pl.soundentity, pl, "");
2289     setmodel(pl.soundentity, "null");
2290 }
2291
2292 void SoundEntity_Detach(entity pl)
2293 {
2294     float i;
2295     for (i = 0; i <= 7; ++i)
2296         SoundEntity_StopSound(pl, i);
2297 }
2298
2299
2300 float ParseCommandPlayerSlotTarget_firsttoken;
2301 entity GetCommandPlayerSlotTargetFromTokenizedCommand(float tokens, float idx) // idx = start index
2302 {
2303     string s;
2304     entity e, head;
2305     float n;
2306
2307     s = string_null;
2308
2309     ParseCommandPlayerSlotTarget_firsttoken = -1;
2310
2311     if (tokens > idx)
2312     {
2313         if (substring(argv(idx), 0, 1) == "#")
2314         {
2315             s = substring(argv(idx), 1, -1);
2316             ++idx;
2317             if (s == "")
2318                 if (tokens > idx)
2319                 {
2320                     s = argv(idx);
2321                     ++idx;
2322                 }
2323             if (s == ftos(stof(s)))
2324             {
2325                 e = edict_num(stof(s));
2326                 if (e.flags & FL_CLIENT)
2327                 {
2328                     ParseCommandPlayerSlotTarget_firsttoken = idx;
2329                     return e;
2330                 }
2331             }
2332         }
2333         else
2334         {
2335             // it must be a nick name
2336             s = argv(idx);
2337             ++idx;
2338
2339             n = 0;
2340             FOR_EACH_CLIENT(head)
2341             if (head.netname == s)
2342             {
2343                 e = head;
2344                 ++n;
2345             }
2346             if (n == 1)
2347             {
2348                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2349                 return e;
2350             }
2351
2352             s = strdecolorize(s);
2353             n = 0;
2354             FOR_EACH_CLIENT(head)
2355             if (strdecolorize(head.netname) == s)
2356             {
2357                 e = head;
2358                 ++n;
2359             }
2360             if (n == 1)
2361             {
2362                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2363                 return e;
2364             }
2365         }
2366     }
2367
2368     return world;
2369 }