]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/miscfunctions.qc
new cvar bot_sound_monopoly allows only bots to make noise; new sbar background
[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_builtin = #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 float sound_allowed(float dest, entity e)
1128 {
1129         // sounds from world may always pass
1130         if(!e)
1131         {
1132                 return TRUE;
1133         }
1134         // sounds to self may always pass
1135         if(dest == MSG_ONE)
1136         {
1137                 if(e == msg_entity)
1138                         return TRUE;
1139                 if(e.owner == msg_entity)
1140                         return TRUE;
1141         }
1142         // sounds by players can be removed
1143         if(cvar("bot_sound_monopoly"))
1144         {
1145                 if(clienttype(e) == CLIENTTYPE_REAL)
1146                         return FALSE;
1147                 if(clienttype(e.owner) == CLIENTTYPE_REAL)
1148                         return FALSE;
1149         }
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         setattachment(pl.soundentity, pl, "");
2246         setmodel(pl.soundentity, "null");
2247 }
2248
2249 void SoundEntity_Detach(entity pl)
2250 {
2251         float i;
2252         for(i = 0; i <= 7; ++i)
2253                 SoundEntity_StopSound(pl, i);
2254 }
2255
2256
2257 float ParseCommandPlayerSlotTarget_firsttoken;
2258 entity GetCommandPlayerSlotTargetFromTokenizedCommand(float tokens, float idx) // idx = start index
2259 {
2260         string s;
2261         entity e, head;
2262         float n;
2263
2264         s = string_null;
2265
2266         ParseCommandPlayerSlotTarget_firsttoken = -1;
2267
2268         if(tokens > idx)
2269         {
2270                 if(substring(argv(idx), 0, 1) == "#")
2271                 {
2272                         s = substring(argv(idx), 1, -1);
2273                         ++idx;
2274                         if(s == "")
2275                         if(tokens > idx)
2276                         {
2277                                 s = argv(idx);
2278                                 ++idx;
2279                         }
2280                         if(s == ftos(stof(s)))
2281                         {
2282                                 e = edict_num(stof(s));
2283                                 if(e.flags & FL_CLIENT)
2284                                 {
2285                                         ParseCommandPlayerSlotTarget_firsttoken = idx;
2286                                         return e;
2287                                 }
2288                         }
2289                 }
2290                 else
2291                 {
2292                         // it must be a nick name
2293                         s = argv(idx);
2294                         ++idx;
2295
2296                         n = 0;
2297                         FOR_EACH_CLIENT(head)
2298                                 if(head.netname == s)
2299                                 {
2300                                         e = head;
2301                                         ++n;
2302                                 }
2303                         if(n == 1)
2304                         {
2305                                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2306                                 return e;
2307                         }
2308
2309                         s = strdecolorize(s);
2310                         n = 0;
2311                         FOR_EACH_CLIENT(head)
2312                                 if(strdecolorize(head.netname) == s)
2313                                 {
2314                                         e = head;
2315                                         ++n;
2316                                 }
2317                         if(n == 1)
2318                         {
2319                                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2320                                 return e;
2321                         }
2322                 }
2323         }
2324
2325         return world;
2326 }