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