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