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