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