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