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