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