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