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