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