]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/miscfunctions.qc
Fixed jingle purechanges
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / miscfunctions.qc
1 #include "miscfunctions.qh"
2
3 #include "antilag.qh"
4 #include "command/common.qh"
5 #include "client.qh"
6 #include "damage.qh"
7 #include "hook.qh"
8 #include "world.qh"
9 #include <server/gamelog.qh>
10 #include "ipban.qh"
11 #include <server/items/items.qh>
12 #include <server/mutators/_mod.qh>
13 #include <server/spawnpoints.qh>
14 #include <server/main.qh>
15 #include "mapvoting.qh"
16 #include "resources.qh"
17 #include <server/items/spawning.qh>
18 #include "player.qh"
19 #include "weapons/accuracy.qh"
20 #include "weapons/common.qh"
21 #include "weapons/csqcprojectile.qh"
22 #include "weapons/selection.qh"
23 #include "../common/command/_mod.qh"
24 #include "../common/constants.qh"
25 #include <common/net_linked.qh>
26 #include <common/weapons/weapon/crylink.qh>
27 #include "../common/deathtypes/all.qh"
28 #include "../common/mapinfo.qh"
29 #include "../common/notifications/all.qh"
30 #include "../common/playerstats.qh"
31 #include "../common/teams.qh"
32 #include "../common/mapobjects/subs.qh"
33 #include <common/mapobjects/trigger/hurt.qh>
34 #include <common/mapobjects/target/location.qh>
35 #include "../common/util.qh"
36 #include "../common/turrets/sv_turrets.qh"
37 #include <common/weapons/_all.qh>
38 #include "../common/vehicles/sv_vehicles.qh"
39 #include "../common/vehicles/vehicle.qh"
40 #include "../common/items/_mod.qh"
41 #include "../common/state.qh"
42 #include "../common/effects/qc/globalsound.qh"
43 #include "../common/wepent.qh"
44 #include <common/weapons/weapon.qh>
45 #include "../lib/csqcmodel/sv_model.qh"
46 #include "../lib/warpzone/anglestransform.qh"
47 #include "../lib/warpzone/server.qh"
48
49 void crosshair_trace(entity pl)
50 {
51         traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
52 }
53
54 void crosshair_trace_plusvisibletriggers(entity pl)
55 {
56         crosshair_trace_plusvisibletriggers__is_wz(pl, false);
57 }
58
59 void WarpZone_crosshair_trace_plusvisibletriggers(entity pl)
60 {
61         crosshair_trace_plusvisibletriggers__is_wz(pl, true);
62 }
63
64 void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz)
65 {
66         FOREACH_ENTITY_FLOAT(solid, SOLID_TRIGGER,
67         {
68                 if(it.model != "")
69                 {
70                         it.solid = SOLID_BSP;
71                         IL_PUSH(g_ctrace_changed, it);
72                 }
73         });
74
75         if (is_wz)
76                 WarpZone_crosshair_trace(pl);
77         else
78                 crosshair_trace(pl);
79
80         IL_EACH(g_ctrace_changed, true, { it.solid = SOLID_TRIGGER; });
81
82         IL_CLEAR(g_ctrace_changed);
83 }
84
85 void WarpZone_crosshair_trace(entity pl)
86 {
87         WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
88 }
89
90 void dedicated_print(string input)
91 {
92         if (server_is_dedicated) print(input);
93 }
94
95 entity findnearest(vector point, bool checkitems, vector axismod)
96 {
97     vector dist;
98     int num_nearest = 0;
99
100     IL_EACH(((checkitems) ? g_items : g_locations), ((checkitems) ? (it.target == "###item###") : (it.classname == "target_location")),
101     {
102         if ((it.items == IT_KEY1 || it.items == IT_KEY2) && it.target == "###item###")
103             dist = it.oldorigin;
104         else
105             dist = it.origin;
106         dist = dist - point;
107         dist = dist.x * axismod.x * '1 0 0' + dist.y * axismod.y * '0 1 0' + dist.z * axismod.z * '0 0 1';
108         float len = vlen2(dist);
109
110         int l;
111         for (l = 0; l < num_nearest; ++l)
112         {
113             if (len < nearest_length[l])
114                 break;
115         }
116
117         // now i tells us where to insert at
118         //   INSERTION SORT! YOU'VE SEEN IT! RUN!
119         if (l < NUM_NEAREST_ENTITIES)
120         {
121             for (int j = NUM_NEAREST_ENTITIES - 1; j >= l; --j)
122             {
123                 nearest_length[j + 1] = nearest_length[j];
124                 nearest_entity[j + 1] = nearest_entity[j];
125             }
126             nearest_length[l] = len;
127             nearest_entity[l] = it;
128             if (num_nearest < NUM_NEAREST_ENTITIES)
129                 num_nearest = num_nearest + 1;
130         }
131     });
132
133     // now use the first one from our list that we can see
134     for (int j = 0; j < num_nearest; ++j)
135     {
136         traceline(point, nearest_entity[j].origin, true, NULL);
137         if (trace_fraction == 1)
138         {
139             if (j != 0)
140                 LOG_TRACEF("Nearest point (%s) is not visible, using a visible one.", nearest_entity[0].netname);
141             return nearest_entity[j];
142         }
143     }
144
145     if (num_nearest == 0)
146         return NULL;
147
148     LOG_TRACE("Not seeing any location point, using nearest as fallback.");
149     /* DEBUGGING CODE:
150     dprint("Candidates were: ");
151     for(j = 0; j < num_nearest; ++j)
152     {
153         if(j != 0)
154                 dprint(", ");
155         dprint(nearest_entity[j].netname);
156     }
157     dprint("\n");
158     */
159
160     return nearest_entity[0];
161 }
162
163 string NearestLocation(vector p)
164 {
165     string ret = "somewhere";
166     entity loc = findnearest(p, false, '1 1 1');
167     if (loc)
168         ret = loc.message;
169     else
170     {
171         loc = findnearest(p, true, '1 1 4');
172         if (loc)
173             ret = loc.netname;
174     }
175     return ret;
176 }
177
178 string PlayerHealth(entity this)
179 {
180         float myhealth = floor(GetResource(this, RES_HEALTH));
181         if(myhealth == -666)
182                 return "spectating";
183         else if(myhealth == -2342 || (myhealth == 2342 && mapvote_initialized))
184                 return "observing";
185         else if(myhealth <= 0 || IS_DEAD(this))
186                 return "dead";
187         return ftos(myhealth);
188 }
189
190 string WeaponNameFromWeaponentity(entity this, .entity weaponentity)
191 {
192         entity wepent = this.(weaponentity);
193         if(!wepent)
194                 return "none";
195         else if(wepent.m_weapon != WEP_Null)
196                 return wepent.m_weapon.m_name;
197         else if(wepent.m_switchweapon != WEP_Null)
198                 return wepent.m_switchweapon.m_name;
199         return "none"; //REGISTRY_GET(Weapons, wepent.cnt).m_name;
200 }
201
202 string formatmessage(entity this, string msg)
203 {
204         float p, p1, p2;
205         float n;
206         vector cursor = '0 0 0';
207         entity cursor_ent = NULL;
208         string escape;
209         string replacement;
210         p = 0;
211         n = 7;
212         bool traced = false;
213
214         MUTATOR_CALLHOOK(PreFormatMessage, this, msg);
215         msg = M_ARGV(1, string);
216
217         while (1) {
218                 if (n < 1)
219                         break; // too many replacements
220
221                 n = n - 1;
222                 p1 = strstrofs(msg, "%", p); // NOTE: this destroys msg as it's a tempstring!
223                 p2 = strstrofs(msg, "\\", p); // NOTE: this destroys msg as it's a tempstring!
224
225                 if (p1 < 0)
226                         p1 = p2;
227
228                 if (p2 < 0)
229                         p2 = p1;
230
231                 p = min(p1, p2);
232
233                 if (p < 0)
234                         break;
235
236                 if(!traced)
237                 {
238                         WarpZone_crosshair_trace_plusvisibletriggers(this);
239                         cursor = trace_endpos;
240                         cursor_ent = trace_ent;
241                         traced = true;
242                 }
243
244                 replacement = substring(msg, p, 2);
245                 escape = substring(msg, p + 1, 1);
246
247                 .entity weaponentity = weaponentities[0]; // TODO: unhardcode
248
249                 switch(escape)
250                 {
251                         case "%": replacement = "%"; break;
252                         case "\\":replacement = "\\"; break;
253                         case "n": replacement = "\n"; break;
254                         case "a": replacement = ftos(floor(GetResource(this, RES_ARMOR))); break;
255                         case "h": replacement = PlayerHealth(this); break;
256                         case "l": replacement = NearestLocation(this.origin); break;
257                         case "y": replacement = NearestLocation(cursor); break;
258                         case "d": replacement = NearestLocation(this.death_origin); break;
259                         case "w": replacement = WeaponNameFromWeaponentity(this, weaponentity); break;
260                         case "W": replacement = GetAmmoName(this.(weaponentity).m_weapon.ammo_type); break;
261                         case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break;
262                         case "s": replacement = ftos(vlen(this.velocity - this.velocity_z * '0 0 1')); break;
263                         case "S": replacement = ftos(vlen(this.velocity)); break;
264                         case "t": replacement = seconds_tostring(ceil(max(0, autocvar_timelimit * 60 + game_starttime - time))); break;
265                         case "T": replacement = seconds_tostring(floor(time - game_starttime)); break;
266                         default:
267                         {
268                                 MUTATOR_CALLHOOK(FormatMessage, this, escape, replacement, msg);
269                                 replacement = M_ARGV(2, string);
270                                 break;
271                         }
272                 }
273
274                 msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2)));
275                 p = p + strlen(replacement);
276         }
277         return msg;
278 }
279
280 /*
281 =============
282 GetCvars
283 =============
284 Called with:
285   0:  sends the request
286   >0: receives a cvar from name=argv(f) value=argv(f+1)
287 */
288 void GetCvars_handleString(entity this, entity store, string thisname, float f, .string field, string name)
289 {
290         if (f < 0)
291         {
292                 strfree(store.(field));
293         }
294         else if (f > 0)
295         {
296                 if (thisname == name)
297                 {
298                         strcpy(store.(field), argv(f + 1));
299                 }
300         }
301         else
302                 stuffcmd(this, strcat("cl_cmd sendcvar ", name, "\n"));
303 }
304 void GetCvars_handleString_Fixup(entity this, entity store, string thisname, float f, .string field, string name, string(entity, string) func)
305 {
306         GetCvars_handleString(this, store, thisname, f, field, name);
307         if (f >= 0) // also initialize to the fitting value for "" when sending cvars out
308                 if (thisname == name)
309                 {
310                         string s = func(this, strcat1(store.(field)));
311                         if (s != store.(field))
312                         {
313                                 strcpy(store.(field), s);
314                         }
315                 }
316 }
317 void GetCvars_handleFloat(entity this, entity store, string thisname, float f, .float field, string name)
318 {
319         if (f < 0)
320         {
321         }
322         else if (f > 0)
323         {
324                 if (thisname == name)
325                         store.(field) = stof(argv(f + 1));
326         }
327         else
328                 stuffcmd(this, strcat("cl_cmd sendcvar ", name, "\n"));
329 }
330 void GetCvars_handleFloatOnce(entity this, entity store, string thisname, float f, .float field, string name)
331 {
332         if (f < 0)
333         {
334         }
335         else if (f > 0)
336         {
337                 if (thisname == name)
338                 {
339                         if (!store.(field))
340                         {
341                                 store.(field) = stof(argv(f + 1));
342                                 if (!store.(field))
343                                         store.(field) = -1;
344                         }
345                 }
346         }
347         else
348         {
349                 if (!store.(field))
350                         stuffcmd(this, strcat("cl_cmd sendcvar ", name, "\n"));
351         }
352 }
353 string W_FixWeaponOrder_ForceComplete_AndBuildImpulseList(entity this, string wo)
354 {
355         string o = W_FixWeaponOrder_ForceComplete(wo);
356         strcpy(CS(this).weaponorder_byimpulse, W_FixWeaponOrder_BuildImpulseList(o));
357         return o;
358 }
359
360 /**
361  * @param f -1: cleanup, 0: request, 1: receive
362  */
363 void GetCvars(entity this, entity store, int f)
364 {
365         string s = string_null;
366
367         if (f == 0)
368                 LOG_INFO("Warning: requesting cvar values is deprecated. Client should send them automatically using REPLICATE.\n");
369
370         if (f > 0)
371                 s = strcat1(argv(f));
372
373         get_cvars_f = f;
374         get_cvars_s = s;
375         MUTATOR_CALLHOOK(GetCvars);
376
377         Notification_GetCvars(this);
378
379         ReplicateVars(this, store, s, f);
380
381         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
382         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[0], "cl_weaponpriority0", W_FixWeaponOrder_AllowIncomplete);
383         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[1], "cl_weaponpriority1", W_FixWeaponOrder_AllowIncomplete);
384         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[2], "cl_weaponpriority2", W_FixWeaponOrder_AllowIncomplete);
385         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[3], "cl_weaponpriority3", W_FixWeaponOrder_AllowIncomplete);
386         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[4], "cl_weaponpriority4", W_FixWeaponOrder_AllowIncomplete);
387         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[5], "cl_weaponpriority5", W_FixWeaponOrder_AllowIncomplete);
388         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[6], "cl_weaponpriority6", W_FixWeaponOrder_AllowIncomplete);
389         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[7], "cl_weaponpriority7", W_FixWeaponOrder_AllowIncomplete);
390         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[8], "cl_weaponpriority8", W_FixWeaponOrder_AllowIncomplete);
391         GetCvars_handleString_Fixup(this, store, s, f, cvar_cl_weaponpriorities[9], "cl_weaponpriority9", W_FixWeaponOrder_AllowIncomplete);
392
393         GetCvars_handleFloat(this, store, s, f, cvar_cl_allow_uidtracking, "cl_allow_uidtracking");
394
395         // fixup of switchweapon (needed for LMS or when spectating is disabled, as PutClientInServer comes too early)
396         if (f > 0)
397         {
398                 if (s == "cl_weaponpriority")
399                 {
400                         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
401                         {
402                                 .entity weaponentity = weaponentities[slot];
403                                 if (this.(weaponentity) && (this.(weaponentity).m_weapon != WEP_Null || slot == 0))
404                                         this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity);
405                         }
406                 }
407                 if (s == "cl_allow_uidtracking")
408                         PlayerStats_GameReport_AddPlayer(this);
409                 //if (s == "cl_gunalign")
410                         //W_ResetGunAlign(this, store.cvar_cl_gunalign);
411         }
412 }
413
414 // decolorizes and team colors the player name when needed
415 string playername(entity p, bool team_colorize)
416 {
417     //string t;
418     //if (team_colorize && teamplay && !intermission_running && IS_PLAYER(p))
419     //{
420     //    t = Team_ColorCode(p.team);
421     //    return strcat(t, strdecolorize(p.netname));
422     //}
423     //else
424     //    return p.netname;
425         
426         if (team_colorize && teamplay && p.team != -1)
427                 return strcat(Team_ColorCode(p.team), strdecolorize(p.netname));
428         else
429                 return p.netname;
430                 
431         //return sprintf("%s (%d) %s", p.classname, p.team, t);
432 }
433
434 float want_weapon(entity weaponinfo, float allguns)
435 {
436         int d = 0;
437         bool allow_mutatorblocked = false;
438
439         if(!weaponinfo.m_id)
440                 return 0;
441
442         bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked);
443         d = M_ARGV(1, float);
444         allguns = M_ARGV(2, bool);
445         allow_mutatorblocked = M_ARGV(3, bool);
446
447         if(allguns)
448                 d = boolean((weaponinfo.spawnflags & WEP_FLAG_NORMAL) && !(weaponinfo.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)));
449         else if(!mutator_returnvalue)
450                 d = !(!weaponinfo.weaponstart);
451
452         if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
453                 d = 0;
454
455         float t = weaponinfo.weaponstartoverride;
456
457         //LOG_INFOF("want_weapon: %s - d: %d t: %d\n", weaponinfo.netname, d, t);
458
459         // bit order in t:
460         // 1: want or not
461         // 2: is default?
462         // 4: is set by default?
463         if(t < 0)
464                 t = 4 | (3 * d);
465         else
466                 t |= (2 * d);
467
468         return t;
469 }
470
471 /// Weapons the player normally starts with outside weapon arena.
472 WepSet weapons_start()
473 {
474         WepSet ret = '0 0 0';
475         FOREACH(Weapons, it != WEP_Null, {
476                 int w = want_weapon(it, false);
477                 if (w & 1)
478                         ret |= it.m_wepset;
479         });
480         return ret;
481 }
482
483 WepSet weapons_all()
484 {
485         WepSet ret = '0 0 0';
486         FOREACH(Weapons, it != WEP_Null, {
487                 if (!(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)))
488                         ret |= it.m_wepset;
489         });
490         return ret;
491 }
492
493 WepSet weapons_devall()
494 {
495         WepSet ret = '0 0 0';
496         FOREACH(Weapons, it != WEP_Null,
497         {
498                 ret |= it.m_wepset;
499         });
500         return ret;
501 }
502
503 WepSet weapons_most()
504 {
505         WepSet ret = '0 0 0';
506         FOREACH(Weapons, it != WEP_Null, {
507                 if ((it.spawnflags & WEP_FLAG_NORMAL) && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)))
508                         ret |= it.m_wepset;
509         });
510         return ret;
511 }
512
513 void weaponarena_available_all_update(entity this)
514 {
515         if (weaponsInMapAll)
516         {
517                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_all());
518         }
519         else
520         {
521                 // if no weapons are available on the map, just fall back to all weapons arena
522                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_all();
523         }
524 }
525
526 void weaponarena_available_devall_update(entity this)
527 {
528         if (weaponsInMapAll)
529         {
530                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | weaponsInMapAll;
531         }
532         else
533         {
534                 // if no weapons are available on the map, just fall back to devall weapons arena
535                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_devall();
536         }
537 }
538
539 void weaponarena_available_most_update(entity this)
540 {
541         if (weaponsInMapAll)
542         {
543                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_most());
544         }
545         else
546         {
547                 // if no weapons are available on the map, just fall back to most weapons arena
548                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_most();
549         }
550 }
551
552 void readplayerstartcvars()
553 {
554         // initialize starting values for players
555         start_weapons = '0 0 0';
556         start_weapons_default = '0 0 0';
557         start_weapons_defaultmask = '0 0 0';
558         start_items = 0;
559         start_ammo_shells = 0;
560         start_ammo_nails = 0;
561         start_ammo_rockets = 0;
562         start_ammo_cells = 0;
563         start_ammo_plasma = 0;
564         if (random_start_ammo == NULL)
565         {
566                 random_start_ammo = spawn();
567         }
568         start_health = cvar("g_balance_health_start");
569         start_armorvalue = cvar("g_balance_armor_start");
570
571         g_weaponarena = 0;
572         g_weaponarena_weapons = '0 0 0';
573
574         string s = cvar_string("g_weaponarena");
575
576         MUTATOR_CALLHOOK(SetWeaponArena, s);
577         s = M_ARGV(0, string);
578
579         if (s == "0" || s == "")
580         {
581                 // no arena
582         }
583         else if (s == "off")
584         {
585                 // forcibly turn off weaponarena
586         }
587         else if (s == "all" || s == "1")
588         {
589                 g_weaponarena = 1;
590                 g_weaponarena_list = "All Weapons";
591                 g_weaponarena_weapons = weapons_all();
592         }
593         else if (s == "devall")
594         {
595                 g_weaponarena = 1;
596                 g_weaponarena_list = "Dev All Weapons";
597                 g_weaponarena_weapons = weapons_devall();
598         }
599         else if (s == "most")
600         {
601                 g_weaponarena = 1;
602                 g_weaponarena_list = "Most Weapons";
603                 g_weaponarena_weapons = weapons_most();
604         }
605         else if (s == "all_available")
606         {
607                 g_weaponarena = 1;
608                 g_weaponarena_list = "All Available Weapons";
609
610                 // this needs to run after weaponsInMapAll is initialized
611                 InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
612         }
613         else if (s == "devall_available")
614         {
615                 g_weaponarena = 1;
616                 g_weaponarena_list = "Dev All Available Weapons";
617
618                 // this needs to run after weaponsInMapAll is initialized
619                 InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
620         }
621         else if (s == "most_available")
622         {
623                 g_weaponarena = 1;
624                 g_weaponarena_list = "Most Available Weapons";
625
626                 // this needs to run after weaponsInMapAll is initialized
627                 InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
628         }
629         else if (s == "none")
630         {
631                 g_weaponarena = 1;
632                 g_weaponarena_list = "No Weapons";
633         }
634         else
635         {
636                 g_weaponarena = 1;
637                 float t = tokenize_console(s);
638                 g_weaponarena_list = "";
639                 for (int j = 0; j < t; ++j)
640                 {
641                         s = argv(j);
642                         Weapon wep = Weapon_from_name(s);
643                         if(wep != WEP_Null)
644                         {
645                                 g_weaponarena_weapons |= (wep.m_wepset);
646                                 g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
647                         }
648                 }
649                 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
650         }
651
652         if (g_weaponarena)
653         {
654                 g_weapon_stay = 0; // incompatible
655                 start_weapons = g_weaponarena_weapons;
656                 start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
657         }
658         else
659         {
660                 FOREACH(Weapons, it != WEP_Null, {
661                         int w = want_weapon(it, false);
662                         WepSet s = it.m_wepset;
663                         if(w & 1)
664                                 start_weapons |= s;
665                         if(w & 2)
666                                 start_weapons_default |= s;
667                         if(w & 4)
668                                 start_weapons_defaultmask |= s;
669                 });
670         }
671
672         if(cvar("g_balance_superweapons_time") < 0)
673                 start_items |= IT_UNLIMITED_SUPERWEAPONS;
674
675         if(!cvar("g_use_ammunition"))
676                 start_items |= IT_UNLIMITED_AMMO;
677
678         if(start_items & IT_UNLIMITED_AMMO)
679         {
680                 start_ammo_shells = 999;
681                 start_ammo_nails = 999;
682                 start_ammo_rockets = 999;
683                 start_ammo_cells = 999;
684                 start_ammo_plasma = 999;
685                 start_ammo_fuel = 999;
686         }
687         else
688         {
689                 start_ammo_shells = cvar("g_start_ammo_shells");
690                 start_ammo_nails = cvar("g_start_ammo_nails");
691                 start_ammo_rockets = cvar("g_start_ammo_rockets");
692                 start_ammo_cells = cvar("g_start_ammo_cells");
693                 start_ammo_plasma = cvar("g_start_ammo_plasma");
694                 start_ammo_fuel = cvar("g_start_ammo_fuel");
695                 random_start_weapons_count = cvar("g_random_start_weapons_count");
696                 SetResource(random_start_ammo, RES_SHELLS, cvar("g_random_start_shells"));
697                 SetResource(random_start_ammo, RES_BULLETS, cvar("g_random_start_bullets"));
698                 SetResource(random_start_ammo, RES_ROCKETS,cvar("g_random_start_rockets"));
699                 SetResource(random_start_ammo, RES_CELLS, cvar("g_random_start_cells"));
700                 SetResource(random_start_ammo, RES_PLASMA, cvar("g_random_start_plasma"));
701         }
702
703         warmup_start_ammo_shells = start_ammo_shells;
704         warmup_start_ammo_nails = start_ammo_nails;
705         warmup_start_ammo_rockets = start_ammo_rockets;
706         warmup_start_ammo_cells = start_ammo_cells;
707         warmup_start_ammo_plasma = start_ammo_plasma;
708         warmup_start_ammo_fuel = start_ammo_fuel;
709         warmup_start_health = start_health;
710         warmup_start_armorvalue = start_armorvalue;
711         warmup_start_weapons = start_weapons;
712         warmup_start_weapons_default = start_weapons_default;
713         warmup_start_weapons_defaultmask = start_weapons_defaultmask;
714
715         if (!g_weaponarena)
716         {
717                 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
718                 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
719                 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
720                 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
721                 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
722                 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
723                 warmup_start_health = cvar("g_warmup_start_health");
724                 warmup_start_armorvalue = cvar("g_warmup_start_armor");
725                 warmup_start_weapons = '0 0 0';
726                 warmup_start_weapons_default = '0 0 0';
727                 warmup_start_weapons_defaultmask = '0 0 0';
728                 FOREACH(Weapons, it != WEP_Null, {
729                         int w = want_weapon(it, g_warmup_allguns);
730                         WepSet s = it.m_wepset;
731                         if(w & 1)
732                                 warmup_start_weapons |= s;
733                         if(w & 2)
734                                 warmup_start_weapons_default |= s;
735                         if(w & 4)
736                                 warmup_start_weapons_defaultmask |= s;
737                 });
738         }
739
740         if (g_jetpack)
741                 start_items |= ITEM_Jetpack.m_itemid;
742
743         MUTATOR_CALLHOOK(SetStartItems);
744
745         if (start_items & ITEM_Jetpack.m_itemid)
746         {
747                 start_items |= ITEM_JetpackRegen.m_itemid;
748                 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
749                 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
750         }
751
752         start_ammo_shells = max(0, start_ammo_shells);
753         start_ammo_nails = max(0, start_ammo_nails);
754         start_ammo_rockets = max(0, start_ammo_rockets);
755         start_ammo_cells = max(0, start_ammo_cells);
756         start_ammo_plasma = max(0, start_ammo_plasma);
757         start_ammo_fuel = max(0, start_ammo_fuel);
758         SetResource(random_start_ammo, RES_SHELLS,
759                 max(0, GetResource(random_start_ammo, RES_SHELLS)));
760         SetResource(random_start_ammo, RES_BULLETS,
761                 max(0, GetResource(random_start_ammo, RES_BULLETS)));
762         SetResource(random_start_ammo, RES_ROCKETS,
763                 max(0, GetResource(random_start_ammo, RES_ROCKETS)));
764         SetResource(random_start_ammo, RES_CELLS,
765                 max(0, GetResource(random_start_ammo, RES_CELLS)));
766         SetResource(random_start_ammo, RES_PLASMA,
767                 max(0, GetResource(random_start_ammo, RES_PLASMA)));
768
769         warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
770         warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
771         warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
772         warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
773         warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
774         warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
775 }
776
777 void precache_playermodel(string m)
778 {
779         int globhandle, i, n;
780         string f;
781
782         // remove :<skinnumber> suffix
783         int j = strstrofs(m, ":", 0);
784         if(j >= 0)
785                 m = substring(m, 0, j);
786
787         if(substring(m, -9, 5) == "_lod1")
788                 return;
789         if(substring(m, -9, 5) == "_lod2")
790                 return;
791         precache_model(m);
792         f = strcat(substring(m, 0, -5), "_lod1", substring(m, -4, -1));
793         if(fexists(f))
794                 precache_model(f);
795         f = strcat(substring(m, 0, -5), "_lod2", substring(m, -4, -1));
796         if(fexists(f))
797                 precache_model(f);
798
799         globhandle = search_begin(strcat(m, "_*.sounds"), true, false);
800         if (globhandle < 0)
801                 return;
802         n = search_getsize(globhandle);
803         for (i = 0; i < n; ++i)
804         {
805                 //print(search_getfilename(globhandle, i), "\n");
806                 f = search_getfilename(globhandle, i);
807                 PrecachePlayerSounds(f);
808         }
809         search_end(globhandle);
810 }
811 void precache_all_playermodels(string pattern)
812 {
813         int globhandle = search_begin(pattern, true, false);
814         if (globhandle < 0) return;
815         int n = search_getsize(globhandle);
816         for (int i = 0; i < n; ++i)
817         {
818                 string s = search_getfilename(globhandle, i);
819                 precache_playermodel(s);
820         }
821         search_end(globhandle);
822 }
823
824 void precache_playermodels(string s)
825 {
826         FOREACH_WORD(s, true, { precache_playermodel(it); });
827 }
828
829 PRECACHE(PlayerModels)
830 {
831     // Precache all player models if desired
832     if (autocvar_sv_precacheplayermodels)
833     {
834         PrecachePlayerSounds("sound/player/default.sounds");
835         precache_all_playermodels("models/player/*.zym");
836         precache_all_playermodels("models/player/*.dpm");
837         precache_all_playermodels("models/player/*.md3");
838         precache_all_playermodels("models/player/*.psk");
839         precache_all_playermodels("models/player/*.iqm");
840     }
841
842     if (autocvar_sv_defaultcharacter)
843     {
844                 precache_playermodels(autocvar_sv_defaultplayermodel_red);
845                 precache_playermodels(autocvar_sv_defaultplayermodel_blue);
846                 precache_playermodels(autocvar_sv_defaultplayermodel_yellow);
847                 precache_playermodels(autocvar_sv_defaultplayermodel_pink);
848                 precache_playermodels(autocvar_sv_defaultplayermodel);
849     }
850 }
851
852
853 void make_safe_for_remove(entity e)
854 {
855     if (e.initialize_entity)
856     {
857         entity ent, prev = NULL;
858         for (ent = initialize_entity_first; ent; )
859         {
860             if ((ent == e) || ((ent.classname == "initialize_entity") && (ent.enemy == e)))
861             {
862                 //print("make_safe_for_remove: getting rid of initializer ", etos(ent), "\n");
863                 // skip it in linked list
864                 if (prev)
865                 {
866                     prev.initialize_entity_next = ent.initialize_entity_next;
867                     ent = prev.initialize_entity_next;
868                 }
869                 else
870                 {
871                     initialize_entity_first = ent.initialize_entity_next;
872                     ent = initialize_entity_first;
873                 }
874             }
875             else
876             {
877                 prev = ent;
878                 ent = ent.initialize_entity_next;
879             }
880         }
881     }
882 }
883
884 .float remove_except_protected_forbidden;
885 void remove_except_protected(entity e)
886 {
887         if(e.remove_except_protected_forbidden)
888                 error("not allowed to remove this at this point");
889         builtin_remove(e);
890 }
891
892 void remove_unsafely(entity e)
893 {
894     if(e.classname == "spike")
895         error("Removing spikes is forbidden (crylink bug), please report");
896     builtin_remove(e);
897 }
898
899 void remove_safely(entity e)
900 {
901     make_safe_for_remove(e);
902     builtin_remove(e);
903 }
904
905 void InitializeEntity(entity e, void(entity this) func, int order)
906 {
907     entity prev, cur;
908
909     if (!e || e.initialize_entity)
910     {
911         // make a proxy initializer entity
912         entity e_old = e;
913         e = new(initialize_entity);
914         e.enemy = e_old;
915     }
916
917     e.initialize_entity = func;
918     e.initialize_entity_order = order;
919
920     cur = initialize_entity_first;
921     prev = NULL;
922     for (;;)
923     {
924         if (!cur || cur.initialize_entity_order > order)
925         {
926             // insert between prev and cur
927             if (prev)
928                 prev.initialize_entity_next = e;
929             else
930                 initialize_entity_first = e;
931             e.initialize_entity_next = cur;
932             return;
933         }
934         prev = cur;
935         cur = cur.initialize_entity_next;
936     }
937 }
938 void InitializeEntitiesRun()
939 {
940     entity startoflist = initialize_entity_first;
941     initialize_entity_first = NULL;
942     delete_fn = remove_except_protected;
943     for (entity e = startoflist; e; e = e.initialize_entity_next)
944     {
945                 e.remove_except_protected_forbidden = 1;
946     }
947     for (entity e = startoflist; e; )
948     {
949                 e.remove_except_protected_forbidden = 0;
950         e.initialize_entity_order = 0;
951         entity next = e.initialize_entity_next;
952         e.initialize_entity_next = NULL;
953         var void(entity this) func = e.initialize_entity;
954         e.initialize_entity = func_null;
955         if (e.classname == "initialize_entity")
956         {
957             entity wrappee = e.enemy;
958             builtin_remove(e);
959             e = wrappee;
960         }
961         //dprint("Delayed initialization: ", e.classname, "\n");
962         if (func)
963         {
964                 func(e);
965         }
966         else
967         {
968             eprint(e);
969             backtrace(strcat("Null function in: ", e.classname, "\n"));
970         }
971         e = next;
972     }
973     delete_fn = remove_unsafely;
974 }
975
976 .float(entity) isEliminated;
977 bool EliminatedPlayers_SendEntity(entity this, entity to, float sendflags)
978 {
979         Stream out = MSG_ENTITY;
980         WriteHeader(out, ENT_CLIENT_ELIMINATEDPLAYERS);
981         serialize(byte, out, sendflags);
982         if (sendflags & 1) {
983                 for (int i = 1; i <= maxclients; i += 8) {
984                         int f = 0;
985                         entity e = edict_num(i);
986                         for (int b = 0; b < 8; ++b, e = nextent(e)) {
987                                 if (eliminatedPlayers.isEliminated(e)) {
988                                         f |= BIT(b);
989                                 }
990                         }
991                         serialize(byte, out, f);
992                 }
993         }
994         return true;
995 }
996
997 void EliminatedPlayers_Init(float(entity) isEliminated_func)
998 {
999         if(eliminatedPlayers)
1000         {
1001                 backtrace("Can't spawn eliminatedPlayers again!");
1002                 return;
1003         }
1004         Net_LinkEntity(eliminatedPlayers = spawn(), false, 0, EliminatedPlayers_SendEntity);
1005         eliminatedPlayers.isEliminated = isEliminated_func;
1006 }
1007
1008
1009
1010
1011 void adaptor_think2use_hittype_splash(entity this) // for timed projectile detonation
1012 {
1013         if(!(IS_ONGROUND(this))) // if onground, we ARE touching something, but HITTYPE_SPLASH is to be networked if the damage causing projectile is not touching ANYTHING
1014                 this.projectiledeathtype |= HITTYPE_SPLASH;
1015         adaptor_think2use(this);
1016 }
1017
1018 // deferred dropping
1019 void DropToFloor_Handler(entity this)
1020 {
1021     WITHSELF(this, builtin_droptofloor());
1022     this.dropped_origin = this.origin;
1023 }
1024
1025 void droptofloor(entity this)
1026 {
1027     InitializeEntity(this, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
1028 }
1029
1030
1031
1032 float trace_hits_box_a0, trace_hits_box_a1;
1033
1034 float trace_hits_box_1d(float end, float thmi, float thma)
1035 {
1036     if (end == 0)
1037     {
1038         // just check if x is in range
1039         if (0 < thmi)
1040             return false;
1041         if (0 > thma)
1042             return false;
1043     }
1044     else
1045     {
1046         // do the trace with respect to x
1047         // 0 -> end has to stay in thmi -> thma
1048         trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
1049         trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
1050         if (trace_hits_box_a0 > trace_hits_box_a1)
1051             return false;
1052     }
1053     return true;
1054 }
1055
1056 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
1057 {
1058     end -= start;
1059     thmi -= start;
1060     thma -= start;
1061     // now it is a trace from 0 to end
1062
1063     trace_hits_box_a0 = 0;
1064     trace_hits_box_a1 = 1;
1065
1066     if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
1067         return false;
1068     if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
1069         return false;
1070     if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
1071         return false;
1072
1073     return true;
1074 }
1075
1076 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
1077 {
1078     return trace_hits_box(start, end, thmi - ma, thma - mi);
1079 }
1080
1081 bool SUB_NoImpactCheck(entity this, entity toucher)
1082 {
1083         // zero hitcontents = this is not the real impact, but either the
1084         // mirror-impact of something hitting the projectile instead of the
1085         // projectile hitting the something, or a touchareagrid one. Neither of
1086         // these stop the projectile from moving, so...
1087         if(trace_dphitcontents == 0)
1088         {
1089                 LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %i, classname: %s, origin: %v)", this, this.classname, this.origin);
1090                 checkclient(this); // TODO: .health is checked in the engine with this, possibly replace with a QC function?
1091         }
1092     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1093         return true;
1094     if (toucher == NULL && this.size != '0 0 0')
1095     {
1096         vector tic;
1097         tic = this.velocity * sys_frametime;
1098         tic = tic + normalize(tic) * vlen(this.maxs - this.mins);
1099         traceline(this.origin - tic, this.origin + tic, MOVE_NORMAL, this);
1100         if (trace_fraction >= 1)
1101         {
1102             LOG_TRACE("Odd... did not hit...?");
1103         }
1104         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
1105         {
1106             LOG_TRACE("Detected and prevented the sky-grapple bug.");
1107             return true;
1108         }
1109     }
1110
1111     return false;
1112 }
1113
1114 bool WarpZone_Projectile_Touch_ImpactFilter_Callback(entity this, entity toucher)
1115 {
1116         // owner check
1117         if(toucher && toucher == this.owner)
1118                 return true;
1119         if(SUB_NoImpactCheck(this, toucher))
1120         {
1121                 if(this.classname == "nade")
1122                         return false; // no checks here
1123                 else if(this.classname == "grapplinghook")
1124                         RemoveHook(this);
1125                 else
1126                         delete(this);
1127                 return true;
1128         }
1129         if(trace_ent && trace_ent.solid > SOLID_TRIGGER)
1130                 UpdateCSQCProjectile(this);
1131         return false;
1132 }
1133
1134 /** engine callback */
1135 void URI_Get_Callback(float id, float status, string data)
1136 {
1137         if(url_URI_Get_Callback(id, status, data))
1138         {
1139                 // handled
1140         }
1141         else if (id == URI_GET_DISCARD)
1142         {
1143                 // discard
1144         }
1145         else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
1146         {
1147                 // sv_cmd curl
1148                 Curl_URI_Get_Callback(id, status, data);
1149         }
1150         else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
1151         {
1152                 // online ban list
1153                 OnlineBanList_URI_Get_Callback(id, status, data);
1154         }
1155         else if (MUTATOR_CALLHOOK(URI_GetCallback, id, status, data))
1156         {
1157                 // handled by a mutator
1158         }
1159         else
1160         {
1161                 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".");
1162         }
1163 }
1164
1165 string uid2name(string myuid)
1166 {
1167         string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
1168
1169         // FIXME remove this later after 0.6 release
1170         // convert old style broken records to correct style
1171         if(s == "")
1172         {
1173                 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
1174                 if(s != "")
1175                 {
1176                         db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
1177                         db_remove(ServerProgsDB, strcat("uid2name", myuid));
1178                 }
1179         }
1180
1181         if(s == "")
1182                 s = "^1Unregistered Player";
1183         return s;
1184 }
1185
1186 bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
1187 {
1188     float m = e.dphitcontentsmask;
1189     e.dphitcontentsmask = goodcontents | badcontents;
1190
1191     vector org = boundmin;
1192     vector delta = boundmax - boundmin;
1193
1194     vector start, end;
1195     start = end = org;
1196     int j; // used after the loop
1197     for(j = 0; j < attempts; ++j)
1198     {
1199         start.x = org.x + random() * delta.x;
1200         start.y = org.y + random() * delta.y;
1201         start.z = org.z + random() * delta.z;
1202
1203         // rule 1: start inside world bounds, and outside
1204         // solid, and don't start from somewhere where you can
1205         // fall down to evil
1206         tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
1207         if (trace_fraction >= 1)
1208             continue;
1209         if (trace_startsolid)
1210             continue;
1211         if (trace_dphitcontents & badcontents)
1212             continue;
1213         if (trace_dphitq3surfaceflags & badsurfaceflags)
1214             continue;
1215
1216         // rule 2: if we are too high, lower the point
1217         if (trace_fraction * delta.z > maxaboveground)
1218             start = trace_endpos + '0 0 1' * maxaboveground;
1219         vector enddown = trace_endpos;
1220
1221         // rule 3: make sure we aren't outside the map. This only works
1222         // for somewhat well formed maps. A good rule of thumb is that
1223         // the map should have a convex outside hull.
1224         // these can be traceLINES as we already verified the starting box
1225         vector mstart = start + 0.5 * (e.mins + e.maxs);
1226         traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
1227         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1228             continue;
1229         traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
1230         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1231             continue;
1232         traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
1233         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1234             continue;
1235         traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
1236         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1237             continue;
1238         traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
1239         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
1240             continue;
1241
1242                 // rule 4: we must "see" some spawnpoint or item
1243             entity sp = NULL;
1244             IL_EACH(g_spawnpoints, checkpvs(mstart, it),
1245             {
1246                 if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
1247                 {
1248                         sp = it;
1249                         break;
1250                 }
1251             });
1252                 if(!sp)
1253                 {
1254                         int items_checked = 0;
1255                         IL_EACH(g_items, checkpvs(mstart, it),
1256                         {
1257                                 if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
1258                                 {
1259                                         sp = it;
1260                                         break;
1261                                 }
1262
1263                                 ++items_checked;
1264                                 if(items_checked >= attempts)
1265                                         break; // sanity
1266                         });
1267
1268                         if(!sp)
1269                                 continue;
1270                 }
1271
1272         // find a random vector to "look at"
1273         end.x = org.x + random() * delta.x;
1274         end.y = org.y + random() * delta.y;
1275         end.z = org.z + random() * delta.z;
1276         end = start + normalize(end - start) * vlen(delta);
1277
1278         // rule 4: start TO end must not be too short
1279         tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
1280         if(trace_startsolid)
1281             continue;
1282         if(trace_fraction < minviewdistance / vlen(delta))
1283             continue;
1284
1285         // rule 5: don't want to look at sky
1286         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
1287             continue;
1288
1289         // rule 6: we must not end up in trigger_hurt
1290         if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
1291             continue;
1292
1293         break;
1294     }
1295
1296     e.dphitcontentsmask = m;
1297
1298     if(j < attempts)
1299     {
1300         setorigin(e, start);
1301         e.angles = vectoangles(end - start);
1302         LOG_DEBUG("Needed ", ftos(j + 1), " attempts");
1303         return true;
1304     }
1305     return false;
1306 }
1307
1308 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
1309 {
1310         return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
1311 }
1312
1313 void write_recordmarker(entity pl, float tstart, float dt)
1314 {
1315     GameLogEcho(strcat(":recordset:", ftos(pl.playerid), ":", ftos(dt)));
1316
1317     // also write a marker into demo files for demotc-race-record-extractor to find
1318     stuffcmd(pl,
1319              strcat(
1320                  strcat("//", strconv(2, 0, 0, GetGametype()), " RECORD SET ", TIME_ENCODED_TOSTRING(TIME_ENCODE(dt))),
1321                  " ", ftos(tstart), " ", ftos(dt), "\n"));
1322 }
1323
1324 void attach_sameorigin(entity e, entity to, string tag)
1325 {
1326     vector org, t_forward, t_left, t_up, e_forward, e_up;
1327     float tagscale;
1328
1329     org = e.origin - gettaginfo(to, gettagindex(to, tag));
1330     tagscale = (vlen(v_forward) ** -2); // undo a scale on the tag
1331     t_forward = v_forward * tagscale;
1332     t_left = v_right * -tagscale;
1333     t_up = v_up * tagscale;
1334
1335     e.origin_x = org * t_forward;
1336     e.origin_y = org * t_left;
1337     e.origin_z = org * t_up;
1338
1339     // current forward and up directions
1340     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1341                 e.angles = AnglesTransform_FromVAngles(e.angles);
1342         else
1343                 e.angles = AnglesTransform_FromAngles(e.angles);
1344     fixedmakevectors(e.angles);
1345
1346     // untransform forward, up!
1347     e_forward.x = v_forward * t_forward;
1348     e_forward.y = v_forward * t_left;
1349     e_forward.z = v_forward * t_up;
1350     e_up.x = v_up * t_forward;
1351     e_up.y = v_up * t_left;
1352     e_up.z = v_up * t_up;
1353
1354     e.angles = fixedvectoangles2(e_forward, e_up);
1355     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1356                 e.angles = AnglesTransform_ToVAngles(e.angles);
1357         else
1358                 e.angles = AnglesTransform_ToAngles(e.angles);
1359
1360     setattachment(e, to, tag);
1361     setorigin(e, e.origin);
1362 }
1363
1364 void detach_sameorigin(entity e)
1365 {
1366     vector org;
1367     org = gettaginfo(e, 0);
1368     e.angles = fixedvectoangles2(v_forward, v_up);
1369     if (substring(e.model, 0, 1) == "*") // bmodels have their own rules
1370                 e.angles = AnglesTransform_ToVAngles(e.angles);
1371         else
1372                 e.angles = AnglesTransform_ToAngles(e.angles);
1373     setorigin(e, org);
1374     setattachment(e, NULL, "");
1375     setorigin(e, e.origin);
1376 }
1377
1378 void follow_sameorigin(entity e, entity to)
1379 {
1380     set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
1381     e.aiment = to; // make the hole follow bmodel
1382     e.punchangle = to.angles; // the original angles of bmodel
1383     e.view_ofs = e.origin - to.origin; // relative origin
1384     e.v_angle = e.angles - to.angles; // relative angles
1385 }
1386
1387 #if 0
1388 // TODO: unused, likely for a reason, possibly needs extensions (allow setting the new movetype as a parameter?)
1389 void unfollow_sameorigin(entity e)
1390 {
1391     set_movetype(e, MOVETYPE_NONE);
1392 }
1393 #endif
1394
1395 .string aiment_classname;
1396 .float aiment_deadflag;
1397 void SetMovetypeFollow(entity ent, entity e)
1398 {
1399         // FIXME this may not be warpzone aware
1400         set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
1401         ent.solid = SOLID_NOT; // MOVETYPE_FOLLOW is always non-solid - this means this cannot be teleported by warpzones any more! Instead, we must notice when our owner gets teleported.
1402         ent.aiment = e; // make the hole follow bmodel
1403         ent.punchangle = e.angles; // the original angles of bmodel
1404         ent.view_ofs = ent.origin - e.origin; // relative origin
1405         ent.v_angle = ent.angles - e.angles; // relative angles
1406         ent.aiment_classname = strzone(e.classname);
1407         ent.aiment_deadflag = e.deadflag;
1408 }
1409 void UnsetMovetypeFollow(entity ent)
1410 {
1411         set_movetype(ent, MOVETYPE_FLY);
1412         PROJECTILE_MAKETRIGGER(ent);
1413         ent.aiment = NULL;
1414 }
1415 float LostMovetypeFollow(entity ent)
1416 {
1417 /*
1418         if(ent.move_movetype != MOVETYPE_FOLLOW)
1419                 if(ent.aiment)
1420                         error("???");
1421 */
1422         if(ent.aiment)
1423         {
1424                 if(ent.aiment.classname != ent.aiment_classname)
1425                         return 1;
1426                 if(ent.aiment.deadflag != ent.aiment_deadflag)
1427                         return 1;
1428         }
1429         return 0;
1430 }
1431
1432 .bool pushable;
1433 bool isPushable(entity e)
1434 {
1435         if(e.pushable)
1436                 return true;
1437         if(IS_VEHICLE(e))
1438                 return false;
1439         if(e.iscreature)
1440                 return true;
1441         if (Item_IsLoot(e))
1442         {
1443                 return true;
1444         }
1445         switch(e.classname)
1446         {
1447                 case "body":
1448                         return true;
1449                 case "bullet": // antilagged bullets can't hit this either
1450                         return false;
1451         }
1452         if (e.projectiledeathtype)
1453                 return true;
1454         return false;
1455 }