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