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