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