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