]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/miscfunctions.qc
PLAYER MODEL FORCING. Highly experimental. cl_forcemodels shows all enemies as your...
[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         GetCvars_handleFloat(s, f, cvar_cl_forceplayermodels, "cl_forceplayermodels");
530         GetCvars_handleFloat(s, f, cvar_cl_forceplayermodelsfromnexuiz, "cl_forceplayermodelsfromnexuiz");
531
532         // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
533         if(f > 0)
534         {
535                 if(s == "cl_weaponpriority")
536                         self.switchweapon = w_getbestweapon(self);
537         }
538 }
539
540 float fexists(string f)
541 {
542         float fh;
543         fh = fopen(f, FILE_READ);
544         if(fh < 0)
545                 return FALSE;
546         fclose(fh);
547         return TRUE;
548 }
549
550 void backtrace(string msg)
551 {
552         float dev;
553         dev = cvar("developer");
554         cvar_set("developer", "1");
555         dprint("\n");
556         dprint("--- CUT HERE ---\nWARNING: ");
557         dprint(msg);
558         dprint("\n");
559         remove(world); // isn't there any better way to cause a backtrace?
560         dprint("\n--- CUT UNTIL HERE ---\n");
561         cvar_set("developer", ftos(dev));
562 }
563
564 string Team_ColorCode(float teamid)
565 {
566         if(teamid == COLOR_TEAM1)
567                 return "^1";
568         else if(teamid == COLOR_TEAM2)
569                 return "^4";
570         else if(teamid == COLOR_TEAM3)
571                 return "^3";
572         else if(teamid == COLOR_TEAM4)
573                 return "^6";
574         else
575                 return "^7";
576 }
577 string Team_ColorName(float t)
578 {
579         // fixme: Search for team entities and get their .netname's!
580         if(t == COLOR_TEAM1)
581                 return "Red";
582         if(t == COLOR_TEAM2)
583                 return "Blue";
584         if(t == COLOR_TEAM3)
585                 return "Yellow";
586         if(t == COLOR_TEAM4)
587                 return "Pink";
588         return "Neutral";
589 }
590 string Team_ColorNameLowerCase(float t)
591 {
592         // fixme: Search for team entities and get their .netname's!
593         if(t == COLOR_TEAM1)
594                 return "red";
595         if(t == COLOR_TEAM2)
596                 return "blue";
597         if(t == COLOR_TEAM3)
598                 return "yellow";
599         if(t == COLOR_TEAM4)
600                 return "pink";
601         return "neutral";
602 }
603
604 #define CENTERPRIO_POINT 1
605 #define CENTERPRIO_SPAM 2
606 #define CENTERPRIO_VOTE 4
607 #define CENTERPRIO_NORMAL 5
608 #define CENTERPRIO_SHIELDING 7
609 #define CENTERPRIO_MAPVOTE 9
610 #define CENTERPRIO_IDLEKICK 50
611 #define CENTERPRIO_ADMIN 99
612 .float centerprint_priority;
613 .float centerprint_expires;
614 void centerprint_atprio(entity e, float prio, string s)
615 {
616         if(intermission_running)
617                 if(prio < CENTERPRIO_MAPVOTE)
618                         return;
619         if(time > e.centerprint_expires)
620                 e.centerprint_priority = 0;
621         if(prio >= e.centerprint_priority)
622         {
623                 e.centerprint_priority = prio;
624                 if(timeoutStatus == 2)
625                         e.centerprint_expires = time + (e.cvar_scr_centertime * TIMEOUT_SLOWMO_VALUE);
626                 else
627                         e.centerprint_expires = time + e.cvar_scr_centertime;
628                 centerprint_builtin(e, s);
629         }
630 }
631 void centerprint_expire(entity e, float prio)
632 {
633         if(prio == e.centerprint_priority)
634         {
635                 e.centerprint_priority = 0;
636                 centerprint_builtin(e, "");
637         }
638 }
639 void centerprint(entity e, string s)
640 {
641         centerprint_atprio(e, CENTERPRIO_NORMAL, s);
642 }
643
644 // decolorizes and team colors the player name when needed
645 string playername(entity p)
646 {
647         string t;
648         if(teams_matter && !intermission_running && p.classname == "player")
649         {
650                 t = Team_ColorCode(p.team);
651                 return strcat(t, strdecolorize(p.netname));
652         }
653         else
654                 return p.netname;
655 }
656
657 vector randompos(vector m1, vector m2)
658 {
659         local vector v;
660         m2 = m2 - m1;
661         v_x = m2_x * random() + m1_x;
662         v_y = m2_y * random() + m1_y;
663         v_z = m2_z * random() + m1_z;
664         return  v;
665 };
666
667 float g_pickup_shells;
668 float g_pickup_shells_max;
669 float g_pickup_nails;
670 float g_pickup_nails_max;
671 float g_pickup_rockets;
672 float g_pickup_rockets_max;
673 float g_pickup_cells;
674 float g_pickup_cells_max;
675 float g_pickup_fuel;
676 float g_pickup_fuel_jetpack;
677 float g_pickup_fuel_max;
678 float g_pickup_armorsmall;
679 float g_pickup_armorsmall_max;
680 float g_pickup_armormedium;
681 float g_pickup_armormedium_max;
682 float g_pickup_armorbig;
683 float g_pickup_armorbig_max;
684 float g_pickup_armorlarge;
685 float g_pickup_armorlarge_max;
686 float g_pickup_healthsmall;
687 float g_pickup_healthsmall_max;
688 float g_pickup_healthmedium;
689 float g_pickup_healthmedium_max;
690 float g_pickup_healthlarge;
691 float g_pickup_healthlarge_max;
692 float g_pickup_healthmega;
693 float g_pickup_healthmega_max;
694 float g_weaponarena;
695 string g_weaponarena_list;
696 float g_weaponspeedfactor;
697 float g_weapondamagefactor;
698
699 float start_weapons;
700 float start_items;
701 float start_ammo_shells;
702 float start_ammo_nails;
703 float start_ammo_rockets;
704 float start_ammo_cells;
705 float start_ammo_fuel;
706 float start_health;
707 float start_armorvalue;
708 float warmup_start_weapons;
709 float warmup_start_ammo_shells;
710 float warmup_start_ammo_nails;
711 float warmup_start_ammo_rockets;
712 float warmup_start_ammo_cells;
713 float warmup_start_ammo_fuel;
714 float warmup_start_health;
715 float warmup_start_armorvalue;
716 float g_weapon_stay;
717
718 entity get_weaponinfo(float w);
719
720 float NixNex_CanChooseWeapon(float wpn);
721 void readplayerstartcvars()
722 {
723         entity e;
724         float i, j, t;
725         string s;
726
727         // initialize starting values for players
728         start_weapons = 0;
729         start_items = 0;
730         start_ammo_shells = 0;
731         start_ammo_nails = 0;
732         start_ammo_rockets = 0;
733         start_ammo_cells = 0;
734         start_health = cvar("g_balance_health_start");
735         start_armorvalue = cvar("g_balance_armor_start");
736
737         g_weaponarena = 0;
738         s = cvar_string("g_weaponarena");
739         if(s == "0")
740         {
741         }
742         else if(s == "all")
743         {
744                 g_weaponarena_list = "All Weapons";
745                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
746                 {
747                         e = get_weaponinfo(j);
748                         g_weaponarena |= e.weapons;
749                         weapon_action(e.weapon, WR_PRECACHE);
750                 }
751         }
752         else if(s == "most")
753         {
754                 g_weaponarena_list = "Most Weapons";
755                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
756                 {
757                         e = get_weaponinfo(j);
758                         if(e.spawnflags & WEPSPAWNFLAG_NORMAL)
759                         {
760                                 g_weaponarena |= e.weapons;
761                                 weapon_action(e.weapon, WR_PRECACHE);
762                         }
763                 }
764         }
765         else if(s == "none")
766         {
767                 g_weaponarena_list = "No Weapons";
768                 g_weaponarena = WEPBIT_ALL + 1; // this supports no single weapon bit!
769         }
770         else
771         {
772                 t = tokenize_console(s);
773                 g_weaponarena_list = "";
774                 for(i = 0; i < t; ++i)
775                 {
776                         s = argv(i);
777                         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
778                         {
779                                 e = get_weaponinfo(j);
780                                 if(e.netname == s)
781                                 {
782                                         g_weaponarena |= e.weapons;
783                                         weapon_action(e.weapon, WR_PRECACHE);
784                                         g_weaponarena_list = strcat(g_weaponarena_list, e.message, " & ");
785                                         break;
786                                 }
787                         }
788                         if(j > WEP_LAST)
789                         {
790                                 print("The weapon mutator list contains an unknown weapon ", s, ". Skipped.\n");
791                         }
792                 }
793                 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
794         }
795
796         if(g_nixnex)
797         {
798                 start_weapons = 0;
799                 // will be done later
800                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
801                         if(NixNex_CanChooseWeapon(i))
802                                 weapon_action(i, WR_PRECACHE);
803         }
804         else if(g_weaponarena)
805         {
806                 start_weapons = g_weaponarena;
807                 start_ammo_rockets = 999;
808                 start_ammo_shells = 999;
809                 start_ammo_cells = 999;
810                 start_ammo_nails = 999;
811                 start_ammo_fuel = 999;
812                 start_items |= IT_UNLIMITED_AMMO;
813         }
814         else if(g_minstagib)
815         {
816                 start_health = 100;
817                 start_armorvalue = 0;
818                 start_weapons = WEPBIT_MINSTANEX;
819                 weapon_action(WEP_MINSTANEX, WR_PRECACHE);
820                 start_ammo_cells = cvar("g_minstagib_ammo_start");
821                 g_minstagib_invis_alpha = cvar("g_minstagib_invis_alpha");
822                 start_ammo_fuel = cvar("g_start_ammo_fuel");
823
824                 if(g_minstagib_invis_alpha <= 0)
825                         g_minstagib_invis_alpha = -1;
826         }
827         else
828         {
829                 if(g_lms)
830                 {
831                         start_ammo_shells = cvar("g_lms_start_ammo_shells");
832                         start_ammo_nails = cvar("g_lms_start_ammo_nails");
833                         start_ammo_rockets = cvar("g_lms_start_ammo_rockets");
834                         start_ammo_cells = cvar("g_lms_start_ammo_cells");
835                         start_ammo_fuel = cvar("g_lms_start_ammo_fuel");
836                         start_health = cvar("g_lms_start_health");
837                         start_armorvalue = cvar("g_lms_start_armor");
838                 } else if (cvar("g_use_ammunition")) {
839                         start_ammo_shells = cvar("g_start_ammo_shells");
840                         start_ammo_nails = cvar("g_start_ammo_nails");
841                         start_ammo_rockets = cvar("g_start_ammo_rockets");
842                         start_ammo_cells = cvar("g_start_ammo_cells");
843                         start_ammo_fuel = cvar("g_start_ammo_fuel");
844                 } else {
845                         start_ammo_shells = cvar("g_pickup_shells_max");
846                         start_ammo_nails = cvar("g_pickup_nails_max");
847                         start_ammo_rockets = cvar("g_pickup_rockets_max");
848                         start_ammo_cells = cvar("g_pickup_cells_max");
849                         start_ammo_fuel = cvar("g_pickup_fuel_max");
850                         start_items |= IT_UNLIMITED_AMMO;
851                 }
852
853                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
854                 {
855                         e = get_weaponinfo(i);
856                         if(!(e.weapon))
857                                 continue;
858
859                         t = cvar(strcat("g_start_weapon_", e.netname));
860
861                         if(t < 0) // "default" weapon selection
862                         {
863                                 if(g_lms)
864                                         t = (e.spawnflags & WEPSPAWNFLAG_NORMAL);
865                                 else if(g_race)
866                                         t = (i == WEP_LASER);
867                                 else if(g_nexball)
868                                         t = 0; // weapon is set a few lines later
869                                 else
870                                         t = (i == WEP_LASER || i == WEP_SHOTGUN);
871                                 if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook
872                                         t += (i == WEP_HOOK);
873                         }
874
875                         if(g_nexball && i == WEP_PORTO)
876                                         t=1;
877
878                         if(t)
879                         {
880                                 start_weapons |= e.weapons;
881                                 weapon_action(e.weapon, WR_PRECACHE);
882                         }
883                 }
884         }
885
886         if(inWarmupStage)
887         {
888                 warmup_start_ammo_shells = start_ammo_shells;
889                 warmup_start_ammo_nails = start_ammo_nails;
890                 warmup_start_ammo_rockets = start_ammo_rockets;
891                 warmup_start_ammo_cells = start_ammo_cells;
892                 warmup_start_health = start_health;
893                 warmup_start_armorvalue = start_armorvalue;
894                 warmup_start_weapons = start_weapons;
895
896                 if(!g_weaponarena && !g_nixnex && !g_minstagib)
897                 {
898                         if(cvar("g_use_ammunition"))
899                         {
900                                 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
901                                 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
902                                 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
903                                 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
904                         }
905                         warmup_start_health = cvar("g_warmup_start_health");
906                         warmup_start_armorvalue = cvar("g_warmup_start_armor");
907                         if(cvar("g_warmup_allguns"))
908                         {
909                                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
910                                 {
911                                         e = get_weaponinfo(i);
912                                         if(!(e.weapon))
913                                                 continue;
914                                         if(e.spawnflags & WEPSPAWNFLAG_NORMAL)
915                                         {
916                                                 warmup_start_weapons |= e.weapons;
917                                                 weapon_action(e.weapon, WR_PRECACHE);
918                                         }
919                                 }
920                         }
921                 }
922         }
923
924         if(g_jetpack || (g_grappling_hook && (start_weapons & WEPBIT_HOOK)))
925         {
926                 g_grappling_hook = 0; // these two can't coexist, as they use the same button
927                 start_items |= IT_FUEL_REGEN;
928                 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_stable"));
929         }
930
931         if(g_jetpack)
932                 start_items |= IT_JETPACK;
933
934         if(g_weapon_stay == 2)
935         {
936                 if(!start_ammo_shells) start_ammo_shells = g_pickup_shells;
937                 if(!start_ammo_nails) start_ammo_nails = g_pickup_nails;
938                 if(!start_ammo_cells) start_ammo_cells = g_pickup_cells;
939                 if(!start_ammo_rockets) start_ammo_rockets = g_pickup_rockets;
940                 if(!start_ammo_fuel) start_ammo_fuel = g_pickup_fuel;
941                 if(!warmup_start_ammo_shells) warmup_start_ammo_shells = g_pickup_shells;
942                 if(!warmup_start_ammo_nails) warmup_start_ammo_nails = g_pickup_nails;
943                 if(!warmup_start_ammo_cells) warmup_start_ammo_cells = g_pickup_cells;
944                 if(!warmup_start_ammo_rockets) warmup_start_ammo_rockets = g_pickup_rockets;
945                 if(!warmup_start_ammo_fuel) warmup_start_ammo_fuel = g_pickup_fuel;
946         }
947
948         start_ammo_shells = max(0, start_ammo_shells);
949         start_ammo_nails = max(0, start_ammo_nails);
950         start_ammo_cells = max(0, start_ammo_cells);
951         start_ammo_rockets = max(0, start_ammo_rockets);
952         start_ammo_fuel = max(0, start_ammo_fuel);
953
954         warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
955         warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
956         warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
957         warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
958         warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
959 }
960
961 float g_bugrigs;
962 float g_bugrigs_planar_movement;
963 float g_bugrigs_planar_movement_car_jumping;
964 float g_bugrigs_reverse_spinning;
965 float g_bugrigs_reverse_speeding;
966 float g_bugrigs_reverse_stopping;
967 float g_bugrigs_air_steering;
968 float g_bugrigs_angle_smoothing;
969 float g_bugrigs_friction_floor;
970 float g_bugrigs_friction_brake;
971 float g_bugrigs_friction_air;
972 float g_bugrigs_accel;
973 float g_bugrigs_speed_ref;
974 float g_bugrigs_speed_pow;
975 float g_bugrigs_steer;
976
977 float g_touchexplode;
978 float g_touchexplode_radius;
979 float g_touchexplode_damage;
980 float g_touchexplode_edgedamage;
981 float g_touchexplode_force;
982
983 void readlevelcvars(void)
984 {
985         g_bugrigs = cvar("g_bugrigs");
986         g_bugrigs_planar_movement = cvar("g_bugrigs_planar_movement");
987         g_bugrigs_planar_movement_car_jumping = cvar("g_bugrigs_planar_movement_car_jumping");
988         g_bugrigs_reverse_spinning = cvar("g_bugrigs_reverse_spinning");
989         g_bugrigs_reverse_speeding = cvar("g_bugrigs_reverse_speeding");
990         g_bugrigs_reverse_stopping = cvar("g_bugrigs_reverse_stopping");
991         g_bugrigs_air_steering = cvar("g_bugrigs_air_steering");
992         g_bugrigs_angle_smoothing = cvar("g_bugrigs_angle_smoothing");
993         g_bugrigs_friction_floor = cvar("g_bugrigs_friction_floor");
994         g_bugrigs_friction_brake = cvar("g_bugrigs_friction_brake");
995         g_bugrigs_friction_air = cvar("g_bugrigs_friction_air");
996         g_bugrigs_accel = cvar("g_bugrigs_accel");
997         g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref");
998         g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow");
999         g_bugrigs_steer = cvar("g_bugrigs_steer");
1000
1001         g_touchexplode = cvar("g_touchexplode");
1002         g_touchexplode_radius = cvar("g_touchexplode_radius");
1003         g_touchexplode_damage = cvar("g_touchexplode_damage");
1004         g_touchexplode_edgedamage = cvar("g_touchexplode_edgedamage");
1005         g_touchexplode_force = cvar("g_touchexplode_force");
1006
1007         sv_clones = cvar("sv_clones");
1008         sv_cheats = cvar("sv_cheats");
1009         sv_gentle = cvar("sv_gentle");
1010         sv_foginterval = cvar("sv_foginterval");
1011         g_cloaked = cvar("g_cloaked");
1012         g_jump_grunt = cvar("g_jump_grunt");
1013         g_footsteps = cvar("g_footsteps");
1014         g_grappling_hook = cvar("g_grappling_hook");
1015         g_jetpack = cvar("g_jetpack");
1016         g_laserguided_missile = cvar("g_laserguided_missile");
1017         g_midair = cvar("g_midair");
1018         g_minstagib = cvar("g_minstagib");
1019         g_nixnex = cvar("g_nixnex");
1020         g_nixnex_with_laser = cvar("g_nixnex_with_laser");
1021         g_norecoil = cvar("g_norecoil");
1022         g_vampire = cvar("g_vampire");
1023         g_bloodloss = cvar("g_bloodloss");
1024         sv_maxidle = cvar("sv_maxidle");
1025         sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
1026         sv_pogostick = cvar("sv_pogostick");
1027         sv_doublejump = cvar("sv_doublejump");
1028         g_maplist_allow_hidden = cvar("g_maplist_allow_hidden");
1029         g_ctf_reverse = cvar("g_ctf_reverse");
1030
1031         inWarmupStage = cvar("g_warmup");
1032         g_warmup_limit = cvar("g_warmup_limit");
1033         g_warmup_allguns = cvar("g_warmup_allguns");
1034         g_warmup_allow_timeout = cvar("g_warmup_allow_timeout");
1035
1036         if(g_race && g_race_qualifying == 2 || g_arena || g_assault || cvar("g_campaign"))
1037                 inWarmupStage = 0; // these modes cannot work together, sorry
1038
1039         g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
1040         g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
1041         g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
1042         g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
1043         g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
1044         g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
1045         g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
1046         g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
1047         g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
1048         g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
1049         g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
1050         g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
1051
1052         if(g_minstagib) g_nixnex = g_weaponarena = 0;
1053         if(g_nixnex) g_weaponarena = 0;
1054         g_weaponarena = 0;
1055
1056         g_weaponspeedfactor = cvar("g_weaponspeedfactor");
1057         g_weapondamagefactor = cvar("g_weapondamagefactor");
1058
1059         g_pickup_shells                    = cvar("g_pickup_shells");
1060         g_pickup_shells_max                = cvar("g_pickup_shells_max");
1061         g_pickup_nails                     = cvar("g_pickup_nails");
1062         g_pickup_nails_max                 = cvar("g_pickup_nails_max");
1063         g_pickup_rockets                   = cvar("g_pickup_rockets");
1064         g_pickup_rockets_max               = cvar("g_pickup_rockets_max");
1065         g_pickup_cells                     = cvar("g_pickup_cells");
1066         g_pickup_cells_max                 = cvar("g_pickup_cells_max");
1067         g_pickup_fuel                     = cvar("g_pickup_fuel");
1068         g_pickup_fuel_jetpack             = cvar("g_pickup_fuel_jetpack");
1069         g_pickup_fuel_max                 = cvar("g_pickup_fuel_max");
1070         g_pickup_armorsmall                = cvar("g_pickup_armorsmall");
1071         g_pickup_armorsmall_max            = cvar("g_pickup_armorsmall_max");
1072         g_pickup_armormedium               = cvar("g_pickup_armormedium");
1073         g_pickup_armormedium_max           = cvar("g_pickup_armormedium_max");
1074         g_pickup_armorbig                  = cvar("g_pickup_armorbig");
1075         g_pickup_armorbig_max              = cvar("g_pickup_armorbig_max");
1076         g_pickup_armorlarge                = cvar("g_pickup_armorlarge");
1077         g_pickup_armorlarge_max            = cvar("g_pickup_armorlarge_max");
1078         g_pickup_healthsmall               = cvar("g_pickup_healthsmall");
1079         g_pickup_healthsmall_max           = cvar("g_pickup_healthsmall_max");
1080         g_pickup_healthmedium              = cvar("g_pickup_healthmedium");
1081         g_pickup_healthmedium_max          = cvar("g_pickup_healthmedium_max");
1082         g_pickup_healthlarge               = cvar("g_pickup_healthlarge");
1083         g_pickup_healthlarge_max           = cvar("g_pickup_healthlarge_max");
1084         g_pickup_healthmega                = cvar("g_pickup_healthmega");
1085         g_pickup_healthmega_max            = cvar("g_pickup_healthmega_max");
1086
1087         g_pinata = cvar("g_pinata");
1088
1089         g_weapon_stay = cvar("g_weapon_stay");
1090         if(!g_weapon_stay && (cvar("deathmatch") == 2))
1091                 g_weapon_stay = 1;
1092
1093         if not(inWarmupStage)
1094                 game_starttime                 = cvar("g_start_delay");
1095
1096         readplayerstartcvars();
1097 }
1098
1099 /*
1100 // TODO sound pack system
1101 string soundpack;
1102
1103 string precache_sound_builtin (string s) = #19;
1104 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1105 string precache_sound(string s)
1106 {
1107         return precache_sound_builtin(strcat(soundpack, s));
1108 }
1109 void play2(entity e, string filename)
1110 {
1111         stuffcmd(e, strcat("play2 ", soundpack, filename, "\n"));
1112 }
1113 void sound(entity e, float chan, string samp, float vol, float atten)
1114 {
1115         sound_builtin(e, chan, strcat(soundpack, samp), vol, atten);
1116 }
1117 */
1118
1119 // Sound functions
1120 string precache_sound (string s) = #19;
1121 void(entity e, float chan, string samp, float vol, float atten) sound_builtin = #8;
1122 float precache_sound_index (string s) = #19;
1123
1124 #define SND_VOLUME      1
1125 #define SND_ATTENUATION 2
1126 #define SND_LARGEENTITY 8
1127 #define SND_LARGESOUND  16
1128
1129 float sound_allowed(float dest, entity e)
1130 {
1131         // sounds from world may always pass
1132         for(;;)
1133         {
1134                 if(e.owner)
1135                         e = e.owner;
1136                 else if(e.classname == "body")
1137                         e = e.enemy;
1138                 else
1139                         break;
1140         }
1141         // sounds to self may always pass
1142         if(dest == MSG_ONE)
1143                 if(e == msg_entity)
1144                         return TRUE;
1145         // sounds by players can be removed
1146         if(cvar("bot_sound_monopoly"))
1147                 if(clienttype(e) == CLIENTTYPE_REAL)
1148                         return FALSE;
1149         // anything else may pass
1150         return TRUE;
1151 }
1152
1153 void sound(entity e, float chan, string samp, float vol, float atten)
1154 {
1155         if(!sound_allowed(MSG_BROADCAST, e))
1156                 return;
1157         sound_builtin(e, chan, samp, vol, atten);
1158 }
1159 void soundtoat(float dest, entity e, vector o, float chan, string samp, float vol, float atten)
1160 {
1161         float entno, idx;
1162
1163         if(!sound_allowed(dest, e))
1164                 return;
1165
1166         entno = num_for_edict(e);
1167         idx = precache_sound_index(samp);
1168
1169         float sflags;
1170         sflags = 0;
1171
1172         atten = floor(atten * 64);
1173         vol = floor(vol * 255);
1174
1175         if(vol != 255)
1176                 sflags |= SND_VOLUME;
1177         if(atten != 64)
1178                 sflags |= SND_ATTENUATION;
1179         if(entno >= 8192)
1180                 sflags |= SND_LARGEENTITY;
1181         if(idx >= 256)
1182                 sflags |= SND_LARGESOUND;
1183
1184         WriteByte(dest, SVC_SOUND);
1185         WriteByte(dest, sflags);
1186         if(sflags & SND_VOLUME)
1187                 WriteByte(dest, vol);
1188         if(sflags & SND_ATTENUATION)
1189                 WriteByte(dest, atten);
1190         if(sflags & SND_LARGEENTITY)
1191         {
1192                 WriteShort(dest, entno);
1193                 WriteByte(dest, chan);
1194         }
1195         else
1196         {
1197                 WriteShort(dest, entno * 8 + chan);
1198         }
1199         if(sflags & SND_LARGESOUND)
1200                 WriteShort(dest, idx);
1201         else
1202                 WriteByte(dest, idx);
1203
1204         WriteCoord(dest, o_x);
1205         WriteCoord(dest, o_y);
1206         WriteCoord(dest, o_z);
1207 }
1208 void soundto(float dest, entity e, float chan, string samp, float vol, float atten)
1209 {
1210         vector o;
1211
1212         if(!sound_allowed(dest, e))
1213                 return;
1214
1215         o = e.origin + 0.5 * (e.mins + e.maxs);
1216         soundtoat(dest, e, o, chan, samp, vol, atten);
1217 }
1218 void soundat(entity e, vector o, float chan, string samp, float vol, float atten)
1219 {
1220         soundtoat(MSG_BROADCAST, e, o, chan, samp, vol, atten);
1221 }
1222 void stopsoundto(float dest, entity e, float chan)
1223 {
1224         float entno;
1225
1226         if(!sound_allowed(dest, e))
1227                 return;
1228
1229         entno = num_for_edict(e);
1230
1231         if(entno >= 8192)
1232         {
1233                 float idx, sflags;
1234                 idx = precache_sound_index("misc/null.wav");
1235                 sflags = SND_LARGEENTITY;
1236                 if(idx >= 256)
1237                         sflags |= SND_LARGESOUND;
1238                 WriteByte(dest, SVC_SOUND);
1239                 WriteByte(dest, sflags);
1240                 WriteShort(dest, entno);
1241                 WriteByte(dest, chan);
1242                 if(sflags & SND_LARGESOUND)
1243                         WriteShort(dest, idx);
1244                 else
1245                         WriteByte(dest, idx);
1246                 WriteCoord(dest, e.origin_x);
1247                 WriteCoord(dest, e.origin_y);
1248                 WriteCoord(dest, e.origin_z);
1249         }
1250         else
1251         {
1252                 WriteByte(dest, SVC_STOPSOUND);
1253                 WriteShort(dest, entno * 8 + chan);
1254         }
1255 }
1256 void stopsound(entity e, float chan)
1257 {
1258         if(!sound_allowed(MSG_BROADCAST, e))
1259                 return;
1260
1261         stopsoundto(MSG_BROADCAST, e, chan); // unreliable, gets there fast
1262         stopsoundto(MSG_ALL, e, chan); // in case of packet loss
1263 }
1264
1265 void play2(entity e, string filename)
1266 {
1267         //stuffcmd(e, strcat("play2 ", filename, "\n"));
1268         msg_entity = e;
1269         soundtoat(MSG_ONE, world, '0 0 0', CHAN_AUTO, filename, VOL_BASE, ATTN_NONE);
1270 }
1271
1272 .float announcetime;
1273 float announce(entity player, string msg)
1274 {
1275         if(time > player.announcetime)
1276         if(clienttype(player) == CLIENTTYPE_REAL)
1277         {
1278                 player.announcetime = time + 0.8;
1279                 play2(player, msg);
1280                 return TRUE;
1281         }
1282         return FALSE;
1283 }
1284 // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame)
1285 float spamsound(entity e, float chan, string samp, float vol, float atten)
1286 {
1287         if(!sound_allowed(MSG_BROADCAST, e))
1288                 return FALSE;
1289
1290         if(time > e.announcetime)
1291         {
1292                 e.announcetime = time;
1293                 sound(e, chan, samp, vol, atten);
1294                 return TRUE;
1295         }
1296         return FALSE;
1297 }
1298
1299 void play2team(float t, string filename)
1300 {
1301         local entity head;
1302
1303         if(cvar("bot_sound_monopoly"))
1304                 return;
1305
1306         FOR_EACH_REALPLAYER(head)
1307         {
1308                 if (head.team == t)
1309                         play2(head, filename);
1310         }
1311 }
1312
1313 void play2all(string samp)
1314 {
1315         if(cvar("bot_sound_monopoly"))
1316                 return;
1317
1318         sound(world, CHAN_AUTO, samp, VOL_BASE, ATTN_NONE);
1319 }
1320
1321 void PrecachePlayerSounds(string f);
1322 void precache_all_models(string pattern)
1323 {
1324         float globhandle, i, n;
1325         string f;
1326
1327         globhandle = search_begin(pattern, TRUE, FALSE);
1328         if(globhandle < 0)
1329                 return;
1330         n = search_getsize(globhandle);
1331         for(i = 0; i < n; ++i)
1332         {
1333                 //print(search_getfilename(globhandle, i), "\n");
1334                 f = search_getfilename(globhandle, i);
1335                 precache_model(f);
1336                 PrecachePlayerSounds(strcat(f, ".sounds"));
1337         }
1338         search_end(globhandle);
1339 }
1340
1341 void precache()
1342 {
1343         // gamemode related things
1344         precache_model ("models/misc/chatbubble.spr");
1345         precache_model ("models/misc/teambubble.spr");
1346         if (g_runematch)
1347         {
1348                 precache_model ("models/runematch/curse.mdl");
1349                 precache_model ("models/runematch/rune.mdl");
1350         }
1351
1352 #ifdef TTURRETS_ENABLED
1353     if(cvar("g_turrets"))
1354         turrets_precash();
1355 #endif
1356
1357         // Precache all player models if desired
1358         if (cvar("sv_precacheplayermodels"))
1359         {
1360                 PrecachePlayerSounds("sound/player/default.sounds");
1361                 precache_all_models("models/player/*.zym");
1362                 precache_all_models("models/player/*.dpm");
1363                 precache_all_models("models/player/*.md3");
1364                 precache_all_models("models/player/*.psk");
1365                 //precache_model("models/player/carni.zym");
1366                 //precache_model("models/player/crash.zym");
1367                 //precache_model("models/player/grunt.zym");
1368                 //precache_model("models/player/headhunter.zym");
1369                 //precache_model("models/player/insurrectionist.zym");
1370                 //precache_model("models/player/jeandarc.zym");
1371                 //precache_model("models/player/lurk.zym");
1372                 //precache_model("models/player/lycanthrope.zym");
1373                 //precache_model("models/player/marine.zym");
1374                 //precache_model("models/player/nexus.zym");
1375                 //precache_model("models/player/pyria.zym");
1376                 //precache_model("models/player/shock.zym");
1377                 //precache_model("models/player/skadi.zym");
1378                 //precache_model("models/player/specop.zym");
1379                 //precache_model("models/player/visitant.zym");
1380         }
1381
1382         if(cvar("sv_defaultcharacter"))
1383         {
1384                 string s;
1385                 s = cvar_string("sv_defaultplayermodel_red");
1386                 if(s != "")
1387                 {
1388                         precache_model(s);
1389                         PrecachePlayerSounds(strcat(s, ".sounds"));
1390                 }
1391                 s = cvar_string("sv_defaultplayermodel_blue");
1392                 if(s != "")
1393                 {
1394                         precache_model(s);
1395                         PrecachePlayerSounds(strcat(s, ".sounds"));
1396                 }
1397                 s = cvar_string("sv_defaultplayermodel_yellow");
1398                 if(s != "")
1399                 {
1400                         precache_model(s);
1401                         PrecachePlayerSounds(strcat(s, ".sounds"));
1402                 }
1403                 s = cvar_string("sv_defaultplayermodel_pink");
1404                 if(s != "")
1405                 {
1406                         precache_model(s);
1407                         PrecachePlayerSounds(strcat(s, ".sounds"));
1408                 }
1409                 s = cvar_string("sv_defaultplayermodel");
1410                 if(s != "")
1411                 {
1412                         precache_model(s);
1413                         PrecachePlayerSounds(strcat(s, ".sounds"));
1414                 }
1415         }
1416
1417         if (g_footsteps)
1418         {
1419                 PrecacheGlobalSound((globalsound_step = "misc/footstep0 6"));
1420                 PrecacheGlobalSound((globalsound_metalstep = "misc/metalfootstep0 6"));
1421         }
1422
1423         // gore and miscellaneous sounds
1424         //precache_sound ("misc/h2ohit.wav");
1425         precache_model ("models/hook.md3");
1426         precache_sound ("misc/armorimpact.wav");
1427         precache_sound ("misc/bodyimpact1.wav");
1428         precache_sound ("misc/bodyimpact2.wav");
1429         precache_sound ("misc/gib.wav");
1430         precache_sound ("misc/gib_splat01.wav");
1431         precache_sound ("misc/gib_splat02.wav");
1432         precache_sound ("misc/gib_splat03.wav");
1433         precache_sound ("misc/gib_splat04.wav");
1434         precache_sound ("misc/hit.wav");
1435         PrecacheGlobalSound((globalsound_fall = "misc/hitground 4"));
1436         PrecacheGlobalSound((globalsound_metalfall = "misc/metalhitground 4"));
1437         precache_sound ("misc/null.wav");
1438         precache_sound ("misc/spawn.wav");
1439         precache_sound ("misc/talk.wav");
1440         precache_sound ("misc/teleport.wav");
1441         precache_sound ("misc/poweroff.wav");
1442         precache_sound ("player/lava.wav");
1443         precache_sound ("player/slime.wav");
1444         
1445         if(g_jetpack)
1446                 precache_sound ("misc/jetpack_fly.wav");
1447
1448         // announcer sounds - male
1449         precache_sound ("announcer/male/electrobitch.wav");
1450         precache_sound ("announcer/male/airshot.wav");
1451         precache_sound ("announcer/male/03kills.wav");
1452         precache_sound ("announcer/male/05kills.wav");
1453         precache_sound ("announcer/male/10kills.wav");
1454         precache_sound ("announcer/male/15kills.wav");
1455         precache_sound ("announcer/male/20kills.wav");
1456         precache_sound ("announcer/male/25kills.wav");
1457         precache_sound ("announcer/male/30kills.wav");
1458         precache_sound ("announcer/male/botlike.wav");
1459         precache_sound ("announcer/male/yoda.wav");
1460         precache_sound ("announcer/male/amazing.wav");
1461         precache_sound ("announcer/male/awesome.wav");
1462         precache_sound ("announcer/male/headshot.wav");
1463         precache_sound ("announcer/male/impressive.wav");
1464
1465         // announcer sounds - robotic
1466         precache_sound ("announcer/robotic/prepareforbattle.wav");
1467         precache_sound ("announcer/robotic/begin.wav");
1468         precache_sound ("announcer/robotic/timeoutcalled.wav");
1469         precache_sound ("announcer/robotic/1fragleft.wav");
1470         precache_sound ("announcer/robotic/2fragsleft.wav");
1471         precache_sound ("announcer/robotic/3fragsleft.wav");
1472         if (g_minstagib)
1473         {
1474                 precache_sound ("announcer/robotic/lastsecond.wav");
1475                 precache_sound ("announcer/robotic/narrowly.wav");
1476         }
1477
1478         precache_model ("models/sprites/1.spr32");
1479         precache_model ("models/sprites/2.spr32");
1480         precache_model ("models/sprites/3.spr32");
1481         precache_model ("models/sprites/4.spr32");
1482         precache_model ("models/sprites/5.spr32");
1483         precache_model ("models/sprites/6.spr32");
1484         precache_model ("models/sprites/7.spr32");
1485         precache_model ("models/sprites/8.spr32");
1486         precache_model ("models/sprites/9.spr32");
1487         precache_model ("models/sprites/10.spr32");
1488         precache_sound ("announcer/robotic/1.wav");
1489         precache_sound ("announcer/robotic/2.wav");
1490         precache_sound ("announcer/robotic/3.wav");
1491         precache_sound ("announcer/robotic/4.wav");
1492         precache_sound ("announcer/robotic/5.wav");
1493         precache_sound ("announcer/robotic/6.wav");
1494         precache_sound ("announcer/robotic/7.wav");
1495         precache_sound ("announcer/robotic/8.wav");
1496         precache_sound ("announcer/robotic/9.wav");
1497         precache_sound ("announcer/robotic/10.wav");
1498
1499         // common weapon precaches
1500         precache_sound ("weapons/weapon_switch.wav");
1501         precache_sound ("weapons/weaponpickup.wav");
1502         precache_sound ("weapons/unavailable.wav");
1503         if(g_grappling_hook)
1504         {
1505                 precache_sound ("weapons/hook_fire.wav"); // hook
1506                 precache_sound ("weapons/hook_impact.wav"); // hook
1507         }
1508
1509         if (cvar("sv_precacheweapons") || g_nixnex)
1510         {
1511                 //precache weapon models/sounds
1512                 local float wep;
1513                 wep = WEP_FIRST;
1514                 while (wep <= WEP_LAST)
1515                 {
1516                         weapon_action(wep, WR_PRECACHE);
1517                         wep = wep + 1;
1518                 }
1519         }
1520
1521         precache_model("models/elaser.mdl");
1522         precache_model("models/laser.mdl");
1523         precache_model("models/ebomb.mdl");
1524
1525 #if 0
1526         // Disabled this code because it simply does not work (e.g. ignores bgmvolume, overlaps with "cd loop" controlled tracks).
1527
1528         if (!self.noise && self.music) // quake 3 uses the music field
1529                 self.noise = self.music;
1530
1531         // plays music for the level if there is any
1532         if (self.noise)
1533         {
1534                 precache_sound (self.noise);
1535                 ambientsound ('0 0 0', self.noise, VOL_BASE, ATTN_NONE);
1536         }
1537 #endif
1538 }
1539
1540 // sorry, but using \ in macros breaks line numbers
1541 #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
1542 #define WRITESPECTATABLE_MSG_ONE(statement) WRITESPECTATABLE_MSG_ONE_VARNAME(oldmsg_entity, statement)
1543 #define WRITESPECTATABLE(msg,statement) if(msg == MSG_ONE) { WRITESPECTATABLE_MSG_ONE(statement); } else statement float WRITESPECTATABLE_workaround = 0
1544
1545 vector ExactTriggerHit_mins;
1546 vector ExactTriggerHit_maxs;
1547 float ExactTriggerHit_Recurse()
1548 {
1549         float s;
1550         entity se;
1551         float f;
1552
1553         tracebox('0 0 0', ExactTriggerHit_mins, ExactTriggerHit_maxs, '0 0 0', MOVE_NORMAL, other);
1554         if not(trace_ent)
1555                 return 0;
1556         if(trace_ent == self)
1557                 return 1;
1558
1559         se = trace_ent;
1560         s = se.solid;
1561         se.solid = SOLID_NOT;
1562         f = ExactTriggerHit_Recurse();
1563         se.solid = s;
1564
1565         return f;
1566 }
1567
1568 float ExactTriggerHit()
1569 {
1570         float f, s;
1571
1572         if not(self.modelindex)
1573                 return 1;
1574
1575         s = self.solid;
1576         self.solid = SOLID_BSP;
1577         ExactTriggerHit_mins = other.absmin;
1578         ExactTriggerHit_maxs = other.absmax;
1579         f = ExactTriggerHit_Recurse();
1580         self.solid = s;
1581
1582         return f;
1583 }
1584
1585 // WARNING: this kills the trace globals
1586 #define EXACTTRIGGER_TOUCH if not(ExactTriggerHit()) return
1587 #define EXACTTRIGGER_INIT  InitSolidBSPTrigger(); self.solid = SOLID_TRIGGER
1588
1589 #define INITPRIO_FIRST              0
1590 #define INITPRIO_GAMETYPE           0
1591 #define INITPRIO_GAMETYPE_FALLBACK  1
1592 #define INITPRIO_CVARS              5
1593 #define INITPRIO_FINDTARGET        10
1594 #define INITPRIO_DROPTOFLOOR       20
1595 #define INITPRIO_SETLOCATION       90
1596 #define INITPRIO_LINKDOORS         91
1597 #define INITPRIO_LAST              99
1598
1599 .void(void) initialize_entity;
1600 .float initialize_entity_order;
1601 .entity initialize_entity_next;
1602 entity initialize_entity_first;
1603
1604 void make_safe_for_remove(entity e)
1605 {
1606         if(e.initialize_entity)
1607         {
1608                 entity ent, prev;
1609                 for(ent = initialize_entity_first; ent; )
1610                 {
1611                         if((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
1612                         {
1613                                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
1614                                 // skip it in linked list
1615                                 if(prev)
1616                                 {
1617                                         prev.initialize_entity_next = ent.initialize_entity_next;
1618                                         ent = prev.initialize_entity_next;
1619                                 }
1620                                 else
1621                                 {
1622                                         initialize_entity_first = ent.initialize_entity_next;
1623                                         ent = initialize_entity_first;
1624                                 }
1625                         }
1626                         else
1627                         {
1628                                 prev = ent;
1629                                 ent = ent.initialize_entity_next;
1630                         }
1631                 }
1632         }
1633 }
1634
1635 void objerror(string s)
1636 {
1637         make_safe_for_remove(self);
1638         objerror_builtin(s);
1639 }
1640
1641 void remove_unsafely(entity e)
1642
1643         remove_builtin(e);
1644 }
1645
1646 void remove_safely(entity e)
1647 {
1648         make_safe_for_remove(e);
1649         remove_builtin(e);
1650 }
1651
1652 void InitializeEntity(entity e, void(void) func, float order)
1653 {
1654         entity prev, cur;
1655
1656         if(!e || e.initialize_entity)
1657         {
1658                 // make a proxy initializer entity
1659                 entity e_old;
1660                 e_old = e;
1661                 e = spawn();
1662                 e.classname = "initialize_entity";
1663                 e.enemy = e_old;
1664         }
1665
1666         e.initialize_entity = func;
1667         e.initialize_entity_order = order;
1668
1669         cur = initialize_entity_first;
1670         for(;;)
1671         {
1672                 if(!cur || cur.initialize_entity_order > order)
1673                 {
1674                         // insert between prev and cur
1675                         if(prev)
1676                                 prev.initialize_entity_next = e;
1677                         else
1678                                 initialize_entity_first = e;
1679                         e.initialize_entity_next = cur;
1680                         return;
1681                 }
1682                 prev = cur;
1683                 cur = cur.initialize_entity_next;
1684         }
1685 }
1686 void InitializeEntitiesRun()
1687 {
1688         entity startoflist;
1689         startoflist = initialize_entity_first;
1690         initialize_entity_first = world;
1691         for(self = startoflist; self; )
1692         {
1693                 entity e;
1694                 var void(void) func;
1695                 e = self.initialize_entity_next;
1696                 func = self.initialize_entity;
1697                 self.initialize_entity_order = 0;
1698                 self.initialize_entity = func_null;
1699                 self.initialize_entity_next = world;
1700                 if(self.classname == "initialize_entity")
1701                 {
1702                         entity e_old;
1703                         e_old = self.enemy;
1704                         remove_builtin(self);
1705                         self = e_old;
1706                 }
1707                 //dprint("Delayed initialization: ", self.classname, "\n");
1708                 func();
1709                 self = e;
1710         }
1711 }
1712
1713 .float uncustomizeentityforclient_set;
1714 .void(void) uncustomizeentityforclient;
1715 void(void) SUB_Nullpointer = #0;
1716 void UncustomizeEntitiesRun()
1717 {
1718         entity oldself;
1719         oldself = self;
1720         for(self = world; (self = findfloat(self, uncustomizeentityforclient_set, 1)); )
1721                 self.uncustomizeentityforclient();
1722         self = oldself;
1723 }
1724 void SetCustomizer(entity e, float(void) customizer, void(void) uncustomizer)
1725 {
1726         e.customizeentityforclient = customizer;
1727         e.uncustomizeentityforclient = uncustomizer;
1728         e.uncustomizeentityforclient_set = (uncustomizer != SUB_Nullpointer);
1729 }
1730
1731 .float nottargeted;
1732 #define IFTARGETED if(!self.nottargeted && self.targetname != "")
1733
1734 void() SUB_Remove;
1735 void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
1736 {
1737         vector mi, ma;
1738
1739         if(e.classname == "")
1740                 e.classname = "net_linked";
1741
1742         if(e.model == "" || self.modelindex == 0)
1743         {
1744                 mi = e.mins;
1745                 ma = e.maxs;
1746                 setmodel(e, "null");
1747                 setsize(e, mi, ma);
1748         }
1749
1750         e.SendEntity = sendfunc;
1751         e.SendFlags = 0xFFFFFF;
1752
1753         if(!docull)
1754                 e.effects |= EF_NODEPTHTEST;
1755
1756         if(dt)
1757         {
1758                 e.nextthink = time + dt;
1759                 e.think = SUB_Remove;
1760         }
1761 }
1762
1763 void adaptor_think2touch()
1764 {
1765         entity o;
1766         o = other;
1767         other = world;
1768         self.touch();
1769         other = o;
1770 }
1771
1772 void adaptor_think2use()
1773 {
1774         entity o, a;
1775         o = other;
1776         a = activator;
1777         activator = world;
1778         other = world;
1779         self.use();
1780         other = o;
1781         activator = a;
1782 }
1783
1784 // deferred dropping
1785 void DropToFloor_Handler()
1786 {
1787         droptofloor_builtin();
1788         self.dropped_origin = self.origin;
1789 }
1790
1791 void droptofloor()
1792 {
1793         InitializeEntity(self, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1794 }
1795
1796
1797
1798 float trace_hits_box_a0, trace_hits_box_a1;
1799
1800 float trace_hits_box_1d(float end, float thmi, float thma)
1801 {
1802         if(end == 0)
1803         {
1804                 // just check if x is in range
1805                 if(0 < thmi)
1806                         return FALSE;
1807                 if(0 > thma)
1808                         return FALSE;
1809         }
1810         else
1811         {
1812                 // do the trace with respect to x
1813                 // 0 -> end has to stay in thmi -> thma
1814                 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1815                 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1816                 if(trace_hits_box_a0 > trace_hits_box_a1)
1817                         return FALSE;
1818         }
1819         return TRUE;
1820 }
1821
1822 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1823 {
1824         end -= start;
1825         thmi -= start;
1826         thma -= start;
1827         // now it is a trace from 0 to end
1828
1829         trace_hits_box_a0 = 0;
1830         trace_hits_box_a1 = 1;
1831
1832         if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
1833                 return FALSE;
1834         if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
1835                 return FALSE;
1836         if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
1837                 return FALSE;
1838
1839         return TRUE;
1840 }
1841
1842 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1843 {
1844         return trace_hits_box(start, end, thmi - ma, thma - mi);
1845 }
1846
1847 float SUB_NoImpactCheck()
1848 {
1849         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1850                 return 1;
1851         if(other == world && self.size != '0 0 0')
1852         {
1853                 vector tic;
1854                 tic = self.velocity * sys_ticrate;
1855                 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
1856                 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
1857                 if(trace_fraction >= 1)
1858                 {
1859                         dprint("Odd... did not hit...?\n");
1860                 }
1861                 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1862                 {
1863                         dprint("Detected and prevented the sky-grapple bug.\n");
1864                         return 1;
1865                 }
1866         }
1867
1868         return 0;
1869 }
1870
1871 #define SUB_OwnerCheck() (other && (other == self.owner))
1872
1873 #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)
1874
1875 float MAX_IPBAN_URIS = 16;
1876
1877 float URI_GET_DISCARD   = 0;
1878 float URI_GET_IPBAN     = 1;
1879 float URI_GET_IPBAN_END = 16;
1880
1881 void URI_Get_Callback(float id, float status, string data)
1882 {
1883         dprint("Received HTTP request data for id ", ftos(id), "; status is ", ftos(status), "\nData is:\n");
1884         dprint(data);
1885         dprint("\nEnd of data.\n");
1886
1887         if(id == URI_GET_DISCARD)
1888         {
1889                 // discard
1890         }
1891         else if(id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1892         {
1893                 // online ban list
1894                 OnlineBanList_URI_Get_Callback(id, status, data);
1895         }
1896         else
1897         {
1898                 print("Received HTTP request data for an invalid id ", ftos(id), ".\n");
1899         }
1900 }
1901
1902 void print_to(entity e, string s)
1903 {
1904         if(e)
1905                 sprint(e, strcat(s, "\n"));
1906         else
1907                 print(s, "\n");
1908 }
1909
1910 string getrecords()
1911 {
1912         float rec;
1913         string h;
1914         float r;
1915         float i;
1916         string s;
1917
1918         rec = 0;
1919         
1920         s = "";
1921
1922         if(g_ctf)
1923         {
1924                 for(i = 0; i < MapInfo_count; ++i)
1925                 {
1926                         if(MapInfo_Get_ByID(i))
1927                         {
1928                                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
1929                                 if(r == 0)
1930                                         continue;
1931                                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
1932                                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
1933                                 ++rec;
1934                         }
1935                 }
1936         }
1937
1938         if(g_race)
1939         {
1940                 for(i = 0; i < MapInfo_count; ++i)
1941                 {
1942                         if(MapInfo_Get_ByID(i))
1943                         {
1944                                 r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/time")));
1945                                 if(r == 0)
1946                                         continue;
1947                                 h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/racerecord/netname"));
1948                                 s = strcat(s, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, mmsss(r)), " ", h, "\n");
1949                                 ++rec;
1950                         }
1951                 }
1952         }
1953         MapInfo_ClearTemps();
1954
1955         if(s == "")
1956                 return "No records are available on this server.\n";
1957         else
1958                 return strcat("Records on this server:\n", s);
1959 }
1960
1961 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1962 {
1963         float m, i;
1964         vector start, org, delta, end, enddown, mstart;
1965
1966         m = e.dphitcontentsmask;
1967         e.dphitcontentsmask = goodcontents | badcontents;
1968
1969         org = world.mins;
1970         delta = world.maxs - world.mins;
1971
1972         for(i = 0; i < attempts; ++i)
1973         {
1974                 start_x = org_x + random() * delta_x;
1975                 start_y = org_y + random() * delta_y;
1976                 start_z = org_z + random() * delta_z;
1977
1978                 // rule 1: start inside world bounds, and outside
1979                 // solid, and don't start from somewhere where you can
1980                 // fall down to evil
1981                 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta_z, MOVE_NORMAL, e);
1982                 if(trace_fraction >= 1)
1983                         continue;
1984                 if(trace_startsolid)
1985                         continue;
1986                 if(trace_dphitcontents & badcontents)
1987                         continue;
1988                 if(trace_dphitq3surfaceflags & badsurfaceflags)
1989                         continue;
1990
1991                 // rule 2: if we are too high, lower the point
1992                 if(trace_fraction * delta_z > maxaboveground)
1993                         start = trace_endpos + '0 0 1' * maxaboveground;
1994                 enddown = trace_endpos;
1995
1996                 // rule 3: make sure we aren't outside the map. This only works
1997                 // for somewhat well formed maps. A good rule of thumb is that
1998                 // the map should have a convex outside hull.
1999                 // these can be traceLINES as we already verified the starting box
2000                 mstart = start + 0.5 * (e.mins + e.maxs);
2001                 traceline(mstart, mstart + '1 0 0' * delta_x, MOVE_NORMAL, e);
2002                 if(trace_fraction >= 1)
2003                         continue;
2004                 traceline(mstart, mstart - '1 0 0' * delta_x, MOVE_NORMAL, e);
2005                 if(trace_fraction >= 1)
2006                         continue;
2007                 traceline(mstart, mstart + '0 1 0' * delta_y, MOVE_NORMAL, e);
2008                 if(trace_fraction >= 1)
2009                         continue;
2010                 traceline(mstart, mstart - '0 1 0' * delta_y, MOVE_NORMAL, e);
2011                 if(trace_fraction >= 1)
2012                         continue;
2013                 traceline(mstart, mstart + '0 0 1' * delta_z, MOVE_NORMAL, e);
2014                 if(trace_fraction >= 1)
2015                         continue;
2016
2017                 // find a random vector to "look at"
2018                 end_x = org_x + random() * delta_x;
2019                 end_y = org_y + random() * delta_y;
2020                 end_z = org_z + random() * delta_z;
2021                 end = start + normalize(end - start) * vlen(delta);
2022
2023                 // rule 4: start TO end must not be too short
2024                 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
2025                 if(trace_startsolid)
2026                         continue;
2027                 if(trace_fraction < minviewdistance / vlen(delta))
2028                         continue;
2029
2030                 // rule 5: don't want to look at sky
2031                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
2032                         continue;
2033
2034                 // rule 6: we must not end up in trigger_hurt
2035                 if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
2036                 {
2037                         dprint("trigger_hurt! ouch! and nothing else could find it!\n");
2038                         continue;
2039                 }
2040
2041                 break;
2042         }
2043
2044         e.dphitcontentsmask = m;
2045
2046         if(i < attempts)
2047         {
2048                 setorigin(e, start);
2049                 e.angles = vectoangles(end - start);
2050                 dprint("Needed ", ftos(i + 1), " attempts\n");
2051                 return TRUE;
2052         }
2053         else
2054                 return FALSE;
2055 }
2056
2057 void zcurveparticles(float effectno, vector start, vector end, float end_dz, float spd)
2058 {
2059         WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
2060         WriteByte(MSG_BROADCAST, TE_CSQC_ZCURVEPARTICLES);
2061         WriteShort(MSG_BROADCAST, effectno);
2062         WriteCoord(MSG_BROADCAST, start_x);
2063         WriteCoord(MSG_BROADCAST, start_y);
2064         WriteCoord(MSG_BROADCAST, start_z);
2065         WriteCoord(MSG_BROADCAST, end_x);
2066         WriteCoord(MSG_BROADCAST, end_y);
2067         WriteCoord(MSG_BROADCAST, end_z);
2068         WriteCoord(MSG_BROADCAST, end_dz);
2069         WriteShort(MSG_BROADCAST, spd / 16);
2070 }
2071
2072 void zcurveparticles_from_tracetoss(float effectno, vector start, vector end, vector vel)
2073 {
2074         float end_dz;
2075         vector vecxy, velxy;
2076
2077         vecxy = end - start; vecxy_z = 0;
2078         velxy = vel;         velxy_z = 0;
2079
2080         if(vlen(velxy) < 0.000001 * fabs(vel_z))
2081         {
2082                 trailparticles(world, effectno, start, end);
2083                 return;
2084         }
2085
2086         end_dz = vlen(vecxy) / vlen(velxy) * vel_z - (end_z - start_z);
2087         zcurveparticles(effectno, start, end, end_dz, vlen(vel));
2088 }
2089
2090 string GetGametype(); // g_world.qc
2091 void write_recordmarker(entity pl, float tstart, float dt)
2092 {
2093         GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt / 10)));
2094
2095         // also write a marker into demo files for demotc-race-record-extractor to find
2096         stuffcmd(pl,
2097                 strcat(
2098                         strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", mmsss(dt * 10)),
2099                         " ", ftos(tstart), " ", ftos(dt), "\n"));
2100 }
2101
2102 vector shotorg_adjust(vector vecs, float y_is_right, float visual)
2103 {
2104         string s;
2105         vector v;
2106         if (cvar("g_shootfromeye"))
2107         {
2108                 if(visual)
2109                 {
2110                         vecs_y = 0;
2111                         vecs_z -= 4;
2112                 }
2113                 else
2114                 {
2115                         vecs_y = 0;
2116                         vecs_z = 0;
2117                 }
2118         }
2119         else if (cvar("g_shootfromcenter"))
2120         {
2121                 vecs_y = 0;
2122                 vecs_z -= 4;
2123         }
2124         else if((s = cvar_string("g_shootfromfixedorigin")) != "")
2125         {
2126                 v = stov(s);
2127                 if(y_is_right)
2128                         v_y = -v_y;
2129                 if(v_x != 0)
2130                         vecs_x = v_x;
2131                 vecs_y = v_y;
2132                 vecs_z = v_z;
2133         }
2134         return vecs;
2135 }
2136
2137
2138
2139 void attach_sameorigin(entity e, entity to, string tag)
2140 {
2141         vector org, t_forward, t_left, t_up, e_forward, e_up;
2142         vector org0, ang0;
2143         float tagscale;
2144
2145         ang0 = e.angles;
2146         org0 = e.origin;
2147
2148         org = e.origin - gettaginfo(to, gettagindex(to, tag));
2149         tagscale = pow(vlen(v_forward), -2); // undo a scale on the tag
2150         t_forward = v_forward * tagscale;
2151         t_left = v_right * -tagscale;
2152         t_up = v_up * tagscale;
2153
2154         e.origin_x = org * t_forward;
2155         e.origin_y = org * t_left;
2156         e.origin_z = org * t_up;
2157
2158         // current forward and up directions
2159         if(substring(e.model, 0, 1) == "*") // bmodels have their own rules
2160                 e.angles_x = -e.angles_x;
2161         fixedmakevectors(e.angles);
2162
2163         // untransform forward, up!
2164         e_forward_x = v_forward * t_forward;
2165         e_forward_y = v_forward * t_left;
2166         e_forward_z = v_forward * t_up;
2167         e_up_x = v_up * t_forward;
2168         e_up_y = v_up * t_left;
2169         e_up_z = v_up * t_up;
2170
2171         e.angles = fixedvectoangles2(e_forward, e_up);
2172         if(substring(e.model, 0, 1) == "*") // bmodels have their own rules
2173                 e.angles_x = -e.angles_x;
2174
2175         setattachment(e, to, tag);
2176         setorigin(e, e.origin);
2177 }
2178
2179 void detach_sameorigin(entity e)
2180 {
2181         vector org;
2182         org = gettaginfo(e, 0);
2183         e.angles = fixedvectoangles2(v_forward, v_up);
2184         if(substring(e.model, 0, 1) == "*") // bmodels have their own rules
2185                 e.angles_x = -e.angles_x;
2186         e.origin = org;
2187         setattachment(e, world, "");
2188         setorigin(e, e.origin);
2189 }
2190
2191 void follow_sameorigin(entity e, entity to)
2192 {
2193         e.movetype = MOVETYPE_FOLLOW; // make the hole follow
2194         e.aiment = to; // make the hole follow bmodel
2195         e.punchangle = to.angles; // the original angles of bmodel
2196         e.view_ofs = e.origin - to.origin; // relative origin
2197         e.v_angle = e.angles - to.angles; // relative angles
2198 }
2199
2200 void unfollow_sameorigin(entity e)
2201 {
2202         e.movetype = MOVETYPE_NONE;
2203 }
2204
2205 entity gettaginfo_relative_ent;
2206 vector gettaginfo_relative(entity e, float tag)
2207 {
2208         if(!gettaginfo_relative_ent)
2209         {
2210                 gettaginfo_relative_ent = spawn();
2211                 gettaginfo_relative_ent.effects = EF_NODRAW;
2212         }
2213         gettaginfo_relative_ent.model = e.model;
2214         gettaginfo_relative_ent.modelindex = e.modelindex;
2215         gettaginfo_relative_ent.frame = e.frame;
2216         return gettaginfo(gettaginfo_relative_ent, tag);
2217 }
2218
2219 void SoundEntity_StartSound(entity pl, float chan, string samp, float vol, float attn)
2220 {
2221         float p;
2222         p = pow(2, chan);
2223         if(pl.soundentity.cnt & p)
2224                 return;
2225         soundtoat(MSG_ALL, pl.soundentity, gettaginfo(pl.soundentity, 0), chan, samp, vol, attn);
2226         pl.soundentity.cnt |= p;
2227 }
2228
2229 void SoundEntity_StopSound(entity pl, float chan)
2230 {
2231         float p;
2232         p = pow(2, chan);
2233         if(pl.soundentity.cnt & p)
2234         {
2235                 stopsoundto(MSG_ALL, pl.soundentity, chan);
2236                 pl.soundentity.cnt &~= p;
2237         }
2238 }
2239
2240 void SoundEntity_Attach(entity pl)
2241 {
2242         pl.soundentity = spawn();
2243         pl.soundentity.classname = "soundentity";
2244         pl.soundentity.owner = pl;
2245         setattachment(pl.soundentity, pl, "");
2246         setmodel(pl.soundentity, "null");
2247 }
2248
2249 void SoundEntity_Detach(entity pl)
2250 {
2251         float i;
2252         for(i = 0; i <= 7; ++i)
2253                 SoundEntity_StopSound(pl, i);
2254 }
2255
2256
2257 float ParseCommandPlayerSlotTarget_firsttoken;
2258 entity GetCommandPlayerSlotTargetFromTokenizedCommand(float tokens, float idx) // idx = start index
2259 {
2260         string s;
2261         entity e, head;
2262         float n;
2263
2264         s = string_null;
2265
2266         ParseCommandPlayerSlotTarget_firsttoken = -1;
2267
2268         if(tokens > idx)
2269         {
2270                 if(substring(argv(idx), 0, 1) == "#")
2271                 {
2272                         s = substring(argv(idx), 1, -1);
2273                         ++idx;
2274                         if(s == "")
2275                         if(tokens > idx)
2276                         {
2277                                 s = argv(idx);
2278                                 ++idx;
2279                         }
2280                         if(s == ftos(stof(s)))
2281                         {
2282                                 e = edict_num(stof(s));
2283                                 if(e.flags & FL_CLIENT)
2284                                 {
2285                                         ParseCommandPlayerSlotTarget_firsttoken = idx;
2286                                         return e;
2287                                 }
2288                         }
2289                 }
2290                 else
2291                 {
2292                         // it must be a nick name
2293                         s = argv(idx);
2294                         ++idx;
2295
2296                         n = 0;
2297                         FOR_EACH_CLIENT(head)
2298                                 if(head.netname == s)
2299                                 {
2300                                         e = head;
2301                                         ++n;
2302                                 }
2303                         if(n == 1)
2304                         {
2305                                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2306                                 return e;
2307                         }
2308
2309                         s = strdecolorize(s);
2310                         n = 0;
2311                         FOR_EACH_CLIENT(head)
2312                                 if(strdecolorize(head.netname) == s)
2313                                 {
2314                                         e = head;
2315                                         ++n;
2316                                 }
2317                         if(n == 1)
2318                         {
2319                                 ParseCommandPlayerSlotTarget_firsttoken = idx;
2320                                 return e;
2321                         }
2322                 }
2323         }
2324
2325         return world;
2326 }