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