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