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