]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/miscfunctions.qc
Further cleanup miscfunctions, document the need to use intrusive lists on entities...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / miscfunctions.qc
1 // NOTE: Please do NOT add new functions to this file! It is a dumping ground that is in the process of being cleaned up, please find a proper home for your code!
2
3 #include "miscfunctions.qh"
4
5 #include "antilag.qh"
6 #include "command/common.qh"
7 #include "client.qh"
8 #include "damage.qh"
9 #include "hook.qh"
10 #include "world.qh"
11 #include <server/gamelog.qh>
12 #include "ipban.qh"
13 #include <server/intermission.qh>
14 #include <server/items/items.qh>
15 #include <server/mutators/_mod.qh>
16 #include <server/spawnpoints.qh>
17 #include <server/main.qh>
18 #include "mapvoting.qh"
19 #include "resources.qh"
20 #include <server/items/spawning.qh>
21 #include "player.qh"
22 #include "weapons/accuracy.qh"
23 #include "weapons/common.qh"
24 #include "weapons/csqcprojectile.qh"
25 #include "weapons/selection.qh"
26 #include "../common/command/_mod.qh"
27 #include "../common/constants.qh"
28 #include <common/net_linked.qh>
29 #include <common/weapons/weapon/crylink.qh>
30 #include "../common/deathtypes/all.qh"
31 #include "../common/mapinfo.qh"
32 #include "../common/notifications/all.qh"
33 #include "../common/playerstats.qh"
34 #include "../common/teams.qh"
35 #include "../common/mapobjects/subs.qh"
36 #include <common/mapobjects/trigger/hurt.qh>
37 #include <common/mapobjects/target/location.qh>
38 #include "../common/util.qh"
39 #include "../common/turrets/sv_turrets.qh"
40 #include <common/weapons/_all.qh>
41 #include "../common/vehicles/sv_vehicles.qh"
42 #include "../common/vehicles/vehicle.qh"
43 #include "../common/items/_mod.qh"
44 #include "../common/state.qh"
45 #include "../common/effects/qc/globalsound.qh"
46 #include "../common/wepent.qh"
47 #include <common/weapons/weapon.qh>
48 #include "../lib/csqcmodel/sv_model.qh"
49 #include "../lib/warpzone/anglestransform.qh"
50 #include "../lib/warpzone/server.qh"
51
52 void crosshair_trace(entity pl)
53 {
54         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));
55 }
56
57 void crosshair_trace_plusvisibletriggers(entity pl)
58 {
59         crosshair_trace_plusvisibletriggers__is_wz(pl, false);
60 }
61
62 void WarpZone_crosshair_trace_plusvisibletriggers(entity pl)
63 {
64         crosshair_trace_plusvisibletriggers__is_wz(pl, true);
65 }
66
67 void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz)
68 {
69         FOREACH_ENTITY_FLOAT(solid, SOLID_TRIGGER,
70         {
71                 if(it.model != "")
72                 {
73                         it.solid = SOLID_BSP;
74                         IL_PUSH(g_ctrace_changed, it);
75                 }
76         });
77
78         if (is_wz)
79                 WarpZone_crosshair_trace(pl);
80         else
81                 crosshair_trace(pl);
82
83         IL_EACH(g_ctrace_changed, true, { it.solid = SOLID_TRIGGER; });
84
85         IL_CLEAR(g_ctrace_changed);
86 }
87
88 void WarpZone_crosshair_trace(entity pl)
89 {
90         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));
91 }
92
93 float want_weapon(entity weaponinfo, float allguns)
94 {
95         int d = 0;
96         bool allow_mutatorblocked = false;
97
98         if(!weaponinfo.m_id)
99                 return 0;
100
101         bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked);
102         d = M_ARGV(1, float);
103         allguns = M_ARGV(2, bool);
104         allow_mutatorblocked = M_ARGV(3, bool);
105
106         if(allguns)
107                 d = boolean((weaponinfo.spawnflags & WEP_FLAG_NORMAL) && !(weaponinfo.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)));
108         else if(!mutator_returnvalue)
109                 d = !(!weaponinfo.weaponstart);
110
111         if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
112                 d = 0;
113
114         float t = weaponinfo.weaponstartoverride;
115
116         //LOG_INFOF("want_weapon: %s - d: %d t: %d\n", weaponinfo.netname, d, t);
117
118         // bit order in t:
119         // 1: want or not
120         // 2: is default?
121         // 4: is set by default?
122         if(t < 0)
123                 t = 4 | (3 * d);
124         else
125                 t |= (2 * d);
126
127         return t;
128 }
129
130 /// Weapons the player normally starts with outside weapon arena.
131 WepSet weapons_start()
132 {
133         WepSet ret = '0 0 0';
134         FOREACH(Weapons, it != WEP_Null, {
135                 int w = want_weapon(it, false);
136                 if (w & 1)
137                         ret |= it.m_wepset;
138         });
139         return ret;
140 }
141
142 WepSet weapons_all()
143 {
144         WepSet ret = '0 0 0';
145         FOREACH(Weapons, it != WEP_Null, {
146                 if (!(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)))
147                         ret |= it.m_wepset;
148         });
149         return ret;
150 }
151
152 WepSet weapons_devall()
153 {
154         WepSet ret = '0 0 0';
155         FOREACH(Weapons, it != WEP_Null,
156         {
157                 ret |= it.m_wepset;
158         });
159         return ret;
160 }
161
162 WepSet weapons_most()
163 {
164         WepSet ret = '0 0 0';
165         FOREACH(Weapons, it != WEP_Null, {
166                 if ((it.spawnflags & WEP_FLAG_NORMAL) && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)))
167                         ret |= it.m_wepset;
168         });
169         return ret;
170 }
171
172 void weaponarena_available_all_update(entity this)
173 {
174         if (weaponsInMapAll)
175         {
176                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_all());
177         }
178         else
179         {
180                 // if no weapons are available on the map, just fall back to all weapons arena
181                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_all();
182         }
183 }
184
185 void weaponarena_available_devall_update(entity this)
186 {
187         if (weaponsInMapAll)
188         {
189                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | weaponsInMapAll;
190         }
191         else
192         {
193                 // if no weapons are available on the map, just fall back to devall weapons arena
194                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_devall();
195         }
196 }
197
198 void weaponarena_available_most_update(entity this)
199 {
200         if (weaponsInMapAll)
201         {
202                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_most());
203         }
204         else
205         {
206                 // if no weapons are available on the map, just fall back to most weapons arena
207                 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_most();
208         }
209 }
210
211 void readplayerstartcvars()
212 {
213         // initialize starting values for players
214         start_weapons = '0 0 0';
215         start_weapons_default = '0 0 0';
216         start_weapons_defaultmask = '0 0 0';
217         start_items = 0;
218         start_ammo_shells = 0;
219         start_ammo_nails = 0;
220         start_ammo_rockets = 0;
221         start_ammo_cells = 0;
222         start_ammo_plasma = 0;
223         if (random_start_ammo == NULL)
224         {
225                 random_start_ammo = spawn();
226         }
227         start_health = cvar("g_balance_health_start");
228         start_armorvalue = cvar("g_balance_armor_start");
229
230         g_weaponarena = 0;
231         g_weaponarena_weapons = '0 0 0';
232
233         string s = cvar_string("g_weaponarena");
234
235         MUTATOR_CALLHOOK(SetWeaponArena, s);
236         s = M_ARGV(0, string);
237
238         if (s == "0" || s == "")
239         {
240                 // no arena
241         }
242         else if (s == "off")
243         {
244                 // forcibly turn off weaponarena
245         }
246         else if (s == "all" || s == "1")
247         {
248                 g_weaponarena = 1;
249                 g_weaponarena_list = "All Weapons";
250                 g_weaponarena_weapons = weapons_all();
251         }
252         else if (s == "devall")
253         {
254                 g_weaponarena = 1;
255                 g_weaponarena_list = "Dev All Weapons";
256                 g_weaponarena_weapons = weapons_devall();
257         }
258         else if (s == "most")
259         {
260                 g_weaponarena = 1;
261                 g_weaponarena_list = "Most Weapons";
262                 g_weaponarena_weapons = weapons_most();
263         }
264         else if (s == "all_available")
265         {
266                 g_weaponarena = 1;
267                 g_weaponarena_list = "All Available Weapons";
268
269                 // this needs to run after weaponsInMapAll is initialized
270                 InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
271         }
272         else if (s == "devall_available")
273         {
274                 g_weaponarena = 1;
275                 g_weaponarena_list = "Dev All Available Weapons";
276
277                 // this needs to run after weaponsInMapAll is initialized
278                 InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
279         }
280         else if (s == "most_available")
281         {
282                 g_weaponarena = 1;
283                 g_weaponarena_list = "Most Available Weapons";
284
285                 // this needs to run after weaponsInMapAll is initialized
286                 InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
287         }
288         else if (s == "none")
289         {
290                 g_weaponarena = 1;
291                 g_weaponarena_list = "No Weapons";
292         }
293         else
294         {
295                 g_weaponarena = 1;
296                 float t = tokenize_console(s);
297                 g_weaponarena_list = "";
298                 for (int j = 0; j < t; ++j)
299                 {
300                         s = argv(j);
301                         Weapon wep = Weapon_from_name(s);
302                         if(wep != WEP_Null)
303                         {
304                                 g_weaponarena_weapons |= (wep.m_wepset);
305                                 g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
306                         }
307                 }
308                 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
309         }
310
311         if (g_weaponarena)
312         {
313                 g_weapon_stay = 0; // incompatible
314                 start_weapons = g_weaponarena_weapons;
315                 start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
316         }
317         else
318         {
319                 FOREACH(Weapons, it != WEP_Null, {
320                         int w = want_weapon(it, false);
321                         WepSet s = it.m_wepset;
322                         if(w & 1)
323                                 start_weapons |= s;
324                         if(w & 2)
325                                 start_weapons_default |= s;
326                         if(w & 4)
327                                 start_weapons_defaultmask |= s;
328                 });
329         }
330
331         if(cvar("g_balance_superweapons_time") < 0)
332                 start_items |= IT_UNLIMITED_SUPERWEAPONS;
333
334         if(!cvar("g_use_ammunition"))
335                 start_items |= IT_UNLIMITED_AMMO;
336
337         if(start_items & IT_UNLIMITED_AMMO)
338         {
339                 start_ammo_shells = 999;
340                 start_ammo_nails = 999;
341                 start_ammo_rockets = 999;
342                 start_ammo_cells = 999;
343                 start_ammo_plasma = 999;
344                 start_ammo_fuel = 999;
345         }
346         else
347         {
348                 start_ammo_shells = cvar("g_start_ammo_shells");
349                 start_ammo_nails = cvar("g_start_ammo_nails");
350                 start_ammo_rockets = cvar("g_start_ammo_rockets");
351                 start_ammo_cells = cvar("g_start_ammo_cells");
352                 start_ammo_plasma = cvar("g_start_ammo_plasma");
353                 start_ammo_fuel = cvar("g_start_ammo_fuel");
354                 random_start_weapons_count = cvar("g_random_start_weapons_count");
355                 SetResource(random_start_ammo, RES_SHELLS, cvar("g_random_start_shells"));
356                 SetResource(random_start_ammo, RES_BULLETS, cvar("g_random_start_bullets"));
357                 SetResource(random_start_ammo, RES_ROCKETS,cvar("g_random_start_rockets"));
358                 SetResource(random_start_ammo, RES_CELLS, cvar("g_random_start_cells"));
359                 SetResource(random_start_ammo, RES_PLASMA, cvar("g_random_start_plasma"));
360         }
361
362         warmup_start_ammo_shells = start_ammo_shells;
363         warmup_start_ammo_nails = start_ammo_nails;
364         warmup_start_ammo_rockets = start_ammo_rockets;
365         warmup_start_ammo_cells = start_ammo_cells;
366         warmup_start_ammo_plasma = start_ammo_plasma;
367         warmup_start_ammo_fuel = start_ammo_fuel;
368         warmup_start_health = start_health;
369         warmup_start_armorvalue = start_armorvalue;
370         warmup_start_weapons = start_weapons;
371         warmup_start_weapons_default = start_weapons_default;
372         warmup_start_weapons_defaultmask = start_weapons_defaultmask;
373
374         if (!g_weaponarena)
375         {
376                 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
377                 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
378                 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
379                 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
380                 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
381                 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
382                 warmup_start_health = cvar("g_warmup_start_health");
383                 warmup_start_armorvalue = cvar("g_warmup_start_armor");
384                 warmup_start_weapons = '0 0 0';
385                 warmup_start_weapons_default = '0 0 0';
386                 warmup_start_weapons_defaultmask = '0 0 0';
387                 FOREACH(Weapons, it != WEP_Null, {
388                         int w = want_weapon(it, autocvar_g_warmup_allguns);
389                         WepSet s = it.m_wepset;
390                         if(w & 1)
391                                 warmup_start_weapons |= s;
392                         if(w & 2)
393                                 warmup_start_weapons_default |= s;
394                         if(w & 4)
395                                 warmup_start_weapons_defaultmask |= s;
396                 });
397         }
398
399         if (autocvar_g_jetpack)
400                 start_items |= ITEM_Jetpack.m_itemid;
401
402         MUTATOR_CALLHOOK(SetStartItems);
403
404         if (start_items & ITEM_Jetpack.m_itemid)
405         {
406                 start_items |= ITEM_JetpackRegen.m_itemid;
407                 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
408                 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
409         }
410
411         start_ammo_shells = max(0, start_ammo_shells);
412         start_ammo_nails = max(0, start_ammo_nails);
413         start_ammo_rockets = max(0, start_ammo_rockets);
414         start_ammo_cells = max(0, start_ammo_cells);
415         start_ammo_plasma = max(0, start_ammo_plasma);
416         start_ammo_fuel = max(0, start_ammo_fuel);
417         SetResource(random_start_ammo, RES_SHELLS,
418                 max(0, GetResource(random_start_ammo, RES_SHELLS)));
419         SetResource(random_start_ammo, RES_BULLETS,
420                 max(0, GetResource(random_start_ammo, RES_BULLETS)));
421         SetResource(random_start_ammo, RES_ROCKETS,
422                 max(0, GetResource(random_start_ammo, RES_ROCKETS)));
423         SetResource(random_start_ammo, RES_CELLS,
424                 max(0, GetResource(random_start_ammo, RES_CELLS)));
425         SetResource(random_start_ammo, RES_PLASMA,
426                 max(0, GetResource(random_start_ammo, RES_PLASMA)));
427
428         warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
429         warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
430         warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
431         warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
432         warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
433         warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
434 }
435
436 void InitializeEntity(entity e, void(entity this) func, int order)
437 {
438     entity prev, cur;
439
440     if (!e || e.initialize_entity)
441     {
442         // make a proxy initializer entity
443         entity e_old = e;
444         e = new(initialize_entity);
445         e.enemy = e_old;
446     }
447
448     e.initialize_entity = func;
449     e.initialize_entity_order = order;
450
451     cur = initialize_entity_first;
452     prev = NULL;
453     for (;;)
454     {
455         if (!cur || cur.initialize_entity_order > order)
456         {
457             // insert between prev and cur
458             if (prev)
459                 prev.initialize_entity_next = e;
460             else
461                 initialize_entity_first = e;
462             e.initialize_entity_next = cur;
463             return;
464         }
465         prev = cur;
466         cur = cur.initialize_entity_next;
467     }
468 }
469 void InitializeEntitiesRun()
470 {
471     entity startoflist = initialize_entity_first;
472     initialize_entity_first = NULL;
473     delete_fn = remove_except_protected;
474     for (entity e = startoflist; e; e = e.initialize_entity_next)
475     {
476                 e.remove_except_protected_forbidden = 1;
477     }
478     for (entity e = startoflist; e; )
479     {
480                 e.remove_except_protected_forbidden = 0;
481         e.initialize_entity_order = 0;
482         entity next = e.initialize_entity_next;
483         e.initialize_entity_next = NULL;
484         var void(entity this) func = e.initialize_entity;
485         e.initialize_entity = func_null;
486         if (e.classname == "initialize_entity")
487         {
488             entity wrappee = e.enemy;
489             builtin_remove(e);
490             e = wrappee;
491         }
492         //dprint("Delayed initialization: ", e.classname, "\n");
493         if (func)
494         {
495                 func(e);
496         }
497         else
498         {
499             eprint(e);
500             backtrace(strcat("Null function in: ", e.classname, "\n"));
501         }
502         e = next;
503     }
504     delete_fn = remove_unsafely;
505 }
506
507 float trace_hits_box_a0, trace_hits_box_a1;
508
509 float trace_hits_box_1d(float end, float thmi, float thma)
510 {
511     if (end == 0)
512     {
513         // just check if x is in range
514         if (0 < thmi)
515             return false;
516         if (0 > thma)
517             return false;
518     }
519     else
520     {
521         // do the trace with respect to x
522         // 0 -> end has to stay in thmi -> thma
523         trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
524         trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
525         if (trace_hits_box_a0 > trace_hits_box_a1)
526             return false;
527     }
528     return true;
529 }
530
531 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
532 {
533     end -= start;
534     thmi -= start;
535     thma -= start;
536     // now it is a trace from 0 to end
537
538     trace_hits_box_a0 = 0;
539     trace_hits_box_a1 = 1;
540
541     if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
542         return false;
543     if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
544         return false;
545     if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
546         return false;
547
548     return true;
549 }
550
551 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
552 {
553     return trace_hits_box(start, end, thmi - ma, thma - mi);
554 }
555
556 /** engine callback */
557 void URI_Get_Callback(float id, float status, string data)
558 {
559         if(url_URI_Get_Callback(id, status, data))
560         {
561                 // handled
562         }
563         else if (id == URI_GET_DISCARD)
564         {
565                 // discard
566         }
567         else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
568         {
569                 // sv_cmd curl
570                 Curl_URI_Get_Callback(id, status, data);
571         }
572         else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
573         {
574                 // online ban list
575                 OnlineBanList_URI_Get_Callback(id, status, data);
576         }
577         else if (MUTATOR_CALLHOOK(URI_GetCallback, id, status, data))
578         {
579                 // handled by a mutator
580         }
581         else
582         {
583                 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".");
584         }
585 }
586
587 string uid2name(string myuid)
588 {
589         string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
590
591         // FIXME remove this later after 0.6 release
592         // convert old style broken records to correct style
593         if(s == "")
594         {
595                 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
596                 if(s != "")
597                 {
598                         db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
599                         db_remove(ServerProgsDB, strcat("uid2name", myuid));
600                 }
601         }
602
603         if(s == "")
604                 s = "^1Unregistered Player";
605         return s;
606 }
607
608 bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
609 {
610     float m = e.dphitcontentsmask;
611     e.dphitcontentsmask = goodcontents | badcontents;
612
613     vector org = boundmin;
614     vector delta = boundmax - boundmin;
615
616     vector start, end;
617     start = end = org;
618     int j; // used after the loop
619     for(j = 0; j < attempts; ++j)
620     {
621         start.x = org.x + random() * delta.x;
622         start.y = org.y + random() * delta.y;
623         start.z = org.z + random() * delta.z;
624
625         // rule 1: start inside world bounds, and outside
626         // solid, and don't start from somewhere where you can
627         // fall down to evil
628         tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
629         if (trace_fraction >= 1)
630             continue;
631         if (trace_startsolid)
632             continue;
633         if (trace_dphitcontents & badcontents)
634             continue;
635         if (trace_dphitq3surfaceflags & badsurfaceflags)
636             continue;
637
638         // rule 2: if we are too high, lower the point
639         if (trace_fraction * delta.z > maxaboveground)
640             start = trace_endpos + '0 0 1' * maxaboveground;
641         vector enddown = trace_endpos;
642
643         // rule 3: make sure we aren't outside the map. This only works
644         // for somewhat well formed maps. A good rule of thumb is that
645         // the map should have a convex outside hull.
646         // these can be traceLINES as we already verified the starting box
647         vector mstart = start + 0.5 * (e.mins + e.maxs);
648         traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
649         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
650             continue;
651         traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
652         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
653             continue;
654         traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
655         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
656             continue;
657         traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
658         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
659             continue;
660         traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
661         if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
662             continue;
663
664                 // rule 4: we must "see" some spawnpoint or item
665             entity sp = NULL;
666             IL_EACH(g_spawnpoints, checkpvs(mstart, it),
667             {
668                 if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
669                 {
670                         sp = it;
671                         break;
672                 }
673             });
674                 if(!sp)
675                 {
676                         int items_checked = 0;
677                         IL_EACH(g_items, checkpvs(mstart, it),
678                         {
679                                 if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
680                                 {
681                                         sp = it;
682                                         break;
683                                 }
684
685                                 ++items_checked;
686                                 if(items_checked >= attempts)
687                                         break; // sanity
688                         });
689
690                         if(!sp)
691                                 continue;
692                 }
693
694         // find a random vector to "look at"
695         end.x = org.x + random() * delta.x;
696         end.y = org.y + random() * delta.y;
697         end.z = org.z + random() * delta.z;
698         end = start + normalize(end - start) * vlen(delta);
699
700         // rule 4: start TO end must not be too short
701         tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
702         if(trace_startsolid)
703             continue;
704         if(trace_fraction < minviewdistance / vlen(delta))
705             continue;
706
707         // rule 5: don't want to look at sky
708         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
709             continue;
710
711         // rule 6: we must not end up in trigger_hurt
712         if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
713             continue;
714
715         break;
716     }
717
718     e.dphitcontentsmask = m;
719
720     if(j < attempts)
721     {
722         setorigin(e, start);
723         e.angles = vectoangles(end - start);
724         LOG_DEBUG("Needed ", ftos(j + 1), " attempts");
725         return true;
726     }
727     return false;
728 }
729
730 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
731 {
732         return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
733 }