1 // NOTE: Please do NOT add new functions to this file! It is a dumping ground that is in the process of being cleaned up, please find a proper home for your code!
3 #include "miscfunctions.qh"
6 #include "command/common.qh"
11 #include <server/gamelog.qh>
13 #include <server/intermission.qh>
14 #include <server/items/items.qh>
15 #include <server/mutators/_mod.qh>
16 #include <server/spawnpoints.qh>
17 #include <server/main.qh>
18 #include "mapvoting.qh"
19 #include "resources.qh"
20 #include <server/items/spawning.qh>
22 #include "weapons/accuracy.qh"
23 #include "weapons/common.qh"
24 #include "weapons/csqcprojectile.qh"
25 #include "weapons/selection.qh"
26 #include "../common/command/_mod.qh"
27 #include "../common/constants.qh"
28 #include <common/net_linked.qh>
29 #include <common/weapons/weapon/crylink.qh>
30 #include "../common/deathtypes/all.qh"
31 #include "../common/mapinfo.qh"
32 #include "../common/notifications/all.qh"
33 #include "../common/playerstats.qh"
34 #include "../common/teams.qh"
35 #include "../common/mapobjects/subs.qh"
36 #include <common/mapobjects/trigger/hurt.qh>
37 #include <common/mapobjects/target/location.qh>
38 #include "../common/util.qh"
39 #include "../common/turrets/sv_turrets.qh"
40 #include <common/weapons/_all.qh>
41 #include "../common/vehicles/sv_vehicles.qh"
42 #include "../common/vehicles/vehicle.qh"
43 #include "../common/items/_mod.qh"
44 #include "../common/state.qh"
45 #include "../common/effects/qc/globalsound.qh"
46 #include "../common/wepent.qh"
47 #include <common/weapons/weapon.qh>
48 #include "../lib/csqcmodel/sv_model.qh"
49 #include "../lib/warpzone/anglestransform.qh"
50 #include "../lib/warpzone/server.qh"
52 void crosshair_trace(entity pl)
54 traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
57 void crosshair_trace_plusvisibletriggers(entity pl)
59 crosshair_trace_plusvisibletriggers__is_wz(pl, false);
62 void WarpZone_crosshair_trace_plusvisibletriggers(entity pl)
64 crosshair_trace_plusvisibletriggers__is_wz(pl, true);
67 void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz)
69 FOREACH_ENTITY_FLOAT(solid, SOLID_TRIGGER,
74 IL_PUSH(g_ctrace_changed, it);
79 WarpZone_crosshair_trace(pl);
83 IL_EACH(g_ctrace_changed, true, { it.solid = SOLID_TRIGGER; });
85 IL_CLEAR(g_ctrace_changed);
88 void WarpZone_crosshair_trace(entity pl)
90 WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
93 float want_weapon(entity weaponinfo, float allguns)
96 bool allow_mutatorblocked = false;
101 bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked);
102 d = M_ARGV(1, float);
103 allguns = M_ARGV(2, bool);
104 allow_mutatorblocked = M_ARGV(3, bool);
107 d = boolean((weaponinfo.spawnflags & WEP_FLAG_NORMAL) && !(weaponinfo.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)));
108 else if(!mutator_returnvalue)
109 d = !(!weaponinfo.weaponstart);
111 if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
114 float t = weaponinfo.weaponstartoverride;
116 //LOG_INFOF("want_weapon: %s - d: %d t: %d\n", weaponinfo.netname, d, t);
121 // 4: is set by default?
130 /// Weapons the player normally starts with outside weapon arena.
131 WepSet weapons_start()
133 WepSet ret = '0 0 0';
134 FOREACH(Weapons, it != WEP_Null, {
135 int w = want_weapon(it, false);
144 WepSet ret = '0 0 0';
145 FOREACH(Weapons, it != WEP_Null, {
146 if (!(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)))
152 WepSet weapons_devall()
154 WepSet ret = '0 0 0';
155 FOREACH(Weapons, it != WEP_Null,
162 WepSet weapons_most()
164 WepSet ret = '0 0 0';
165 FOREACH(Weapons, it != WEP_Null, {
166 if ((it.spawnflags & WEP_FLAG_NORMAL) && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)))
172 void weaponarena_available_all_update(entity this)
176 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_all());
180 // if no weapons are available on the map, just fall back to all weapons arena
181 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_all();
185 void weaponarena_available_devall_update(entity this)
189 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | weaponsInMapAll;
193 // if no weapons are available on the map, just fall back to devall weapons arena
194 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_devall();
198 void weaponarena_available_most_update(entity this)
202 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_most());
206 // if no weapons are available on the map, just fall back to most weapons arena
207 start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_most();
211 void readplayerstartcvars()
213 // initialize starting values for players
214 start_weapons = '0 0 0';
215 start_weapons_default = '0 0 0';
216 start_weapons_defaultmask = '0 0 0';
218 start_ammo_shells = 0;
219 start_ammo_nails = 0;
220 start_ammo_rockets = 0;
221 start_ammo_cells = 0;
222 start_ammo_plasma = 0;
223 if (random_start_ammo == NULL)
225 random_start_ammo = spawn();
227 start_health = cvar("g_balance_health_start");
228 start_armorvalue = cvar("g_balance_armor_start");
231 g_weaponarena_weapons = '0 0 0';
233 string s = cvar_string("g_weaponarena");
235 MUTATOR_CALLHOOK(SetWeaponArena, s);
236 s = M_ARGV(0, string);
238 if (s == "0" || s == "")
244 // forcibly turn off weaponarena
246 else if (s == "all" || s == "1")
249 g_weaponarena_list = "All Weapons";
250 g_weaponarena_weapons = weapons_all();
252 else if (s == "devall")
255 g_weaponarena_list = "Dev All Weapons";
256 g_weaponarena_weapons = weapons_devall();
258 else if (s == "most")
261 g_weaponarena_list = "Most Weapons";
262 g_weaponarena_weapons = weapons_most();
264 else if (s == "all_available")
267 g_weaponarena_list = "All Available Weapons";
269 // this needs to run after weaponsInMapAll is initialized
270 InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
272 else if (s == "devall_available")
275 g_weaponarena_list = "Dev All Available Weapons";
277 // this needs to run after weaponsInMapAll is initialized
278 InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
280 else if (s == "most_available")
283 g_weaponarena_list = "Most Available Weapons";
285 // this needs to run after weaponsInMapAll is initialized
286 InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
288 else if (s == "none")
291 g_weaponarena_list = "No Weapons";
296 float t = tokenize_console(s);
297 g_weaponarena_list = "";
298 for (int j = 0; j < t; ++j)
301 Weapon wep = Weapon_from_name(s);
304 g_weaponarena_weapons |= (wep.m_wepset);
305 g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
308 g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
313 g_weapon_stay = 0; // incompatible
314 start_weapons = g_weaponarena_weapons;
315 start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
319 FOREACH(Weapons, it != WEP_Null, {
320 int w = want_weapon(it, false);
321 WepSet s = it.m_wepset;
325 start_weapons_default |= s;
327 start_weapons_defaultmask |= s;
331 if(cvar("g_balance_superweapons_time") < 0)
332 start_items |= IT_UNLIMITED_SUPERWEAPONS;
334 if(!cvar("g_use_ammunition"))
335 start_items |= IT_UNLIMITED_AMMO;
337 if(start_items & IT_UNLIMITED_AMMO)
339 start_ammo_shells = 999;
340 start_ammo_nails = 999;
341 start_ammo_rockets = 999;
342 start_ammo_cells = 999;
343 start_ammo_plasma = 999;
344 start_ammo_fuel = 999;
348 start_ammo_shells = cvar("g_start_ammo_shells");
349 start_ammo_nails = cvar("g_start_ammo_nails");
350 start_ammo_rockets = cvar("g_start_ammo_rockets");
351 start_ammo_cells = cvar("g_start_ammo_cells");
352 start_ammo_plasma = cvar("g_start_ammo_plasma");
353 start_ammo_fuel = cvar("g_start_ammo_fuel");
354 random_start_weapons_count = cvar("g_random_start_weapons_count");
355 SetResource(random_start_ammo, RES_SHELLS, cvar("g_random_start_shells"));
356 SetResource(random_start_ammo, RES_BULLETS, cvar("g_random_start_bullets"));
357 SetResource(random_start_ammo, RES_ROCKETS,cvar("g_random_start_rockets"));
358 SetResource(random_start_ammo, RES_CELLS, cvar("g_random_start_cells"));
359 SetResource(random_start_ammo, RES_PLASMA, cvar("g_random_start_plasma"));
362 warmup_start_ammo_shells = start_ammo_shells;
363 warmup_start_ammo_nails = start_ammo_nails;
364 warmup_start_ammo_rockets = start_ammo_rockets;
365 warmup_start_ammo_cells = start_ammo_cells;
366 warmup_start_ammo_plasma = start_ammo_plasma;
367 warmup_start_ammo_fuel = start_ammo_fuel;
368 warmup_start_health = start_health;
369 warmup_start_armorvalue = start_armorvalue;
370 warmup_start_weapons = start_weapons;
371 warmup_start_weapons_default = start_weapons_default;
372 warmup_start_weapons_defaultmask = start_weapons_defaultmask;
376 warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
377 warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
378 warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
379 warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
380 warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
381 warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
382 warmup_start_health = cvar("g_warmup_start_health");
383 warmup_start_armorvalue = cvar("g_warmup_start_armor");
384 warmup_start_weapons = '0 0 0';
385 warmup_start_weapons_default = '0 0 0';
386 warmup_start_weapons_defaultmask = '0 0 0';
387 FOREACH(Weapons, it != WEP_Null, {
388 int w = want_weapon(it, autocvar_g_warmup_allguns);
389 WepSet s = it.m_wepset;
391 warmup_start_weapons |= s;
393 warmup_start_weapons_default |= s;
395 warmup_start_weapons_defaultmask |= s;
399 if (autocvar_g_jetpack)
400 start_items |= ITEM_Jetpack.m_itemid;
402 MUTATOR_CALLHOOK(SetStartItems);
404 if (start_items & ITEM_Jetpack.m_itemid)
406 start_items |= ITEM_JetpackRegen.m_itemid;
407 start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
408 warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
411 start_ammo_shells = max(0, start_ammo_shells);
412 start_ammo_nails = max(0, start_ammo_nails);
413 start_ammo_rockets = max(0, start_ammo_rockets);
414 start_ammo_cells = max(0, start_ammo_cells);
415 start_ammo_plasma = max(0, start_ammo_plasma);
416 start_ammo_fuel = max(0, start_ammo_fuel);
417 SetResource(random_start_ammo, RES_SHELLS,
418 max(0, GetResource(random_start_ammo, RES_SHELLS)));
419 SetResource(random_start_ammo, RES_BULLETS,
420 max(0, GetResource(random_start_ammo, RES_BULLETS)));
421 SetResource(random_start_ammo, RES_ROCKETS,
422 max(0, GetResource(random_start_ammo, RES_ROCKETS)));
423 SetResource(random_start_ammo, RES_CELLS,
424 max(0, GetResource(random_start_ammo, RES_CELLS)));
425 SetResource(random_start_ammo, RES_PLASMA,
426 max(0, GetResource(random_start_ammo, RES_PLASMA)));
428 warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
429 warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
430 warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
431 warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
432 warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
433 warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
436 void InitializeEntity(entity e, void(entity this) func, int order)
440 if (!e || e.initialize_entity)
442 // make a proxy initializer entity
444 e = new(initialize_entity);
448 e.initialize_entity = func;
449 e.initialize_entity_order = order;
451 cur = initialize_entity_first;
455 if (!cur || cur.initialize_entity_order > order)
457 // insert between prev and cur
459 prev.initialize_entity_next = e;
461 initialize_entity_first = e;
462 e.initialize_entity_next = cur;
466 cur = cur.initialize_entity_next;
469 void InitializeEntitiesRun()
471 entity startoflist = initialize_entity_first;
472 initialize_entity_first = NULL;
473 delete_fn = remove_except_protected;
474 for (entity e = startoflist; e; e = e.initialize_entity_next)
476 e.remove_except_protected_forbidden = 1;
478 for (entity e = startoflist; e; )
480 e.remove_except_protected_forbidden = 0;
481 e.initialize_entity_order = 0;
482 entity next = e.initialize_entity_next;
483 e.initialize_entity_next = NULL;
484 var void(entity this) func = e.initialize_entity;
485 e.initialize_entity = func_null;
486 if (e.classname == "initialize_entity")
488 entity wrappee = e.enemy;
492 //dprint("Delayed initialization: ", e.classname, "\n");
500 backtrace(strcat("Null function in: ", e.classname, "\n"));
504 delete_fn = remove_unsafely;
507 float trace_hits_box_a0, trace_hits_box_a1;
509 float trace_hits_box_1d(float end, float thmi, float thma)
513 // just check if x is in range
521 // do the trace with respect to x
522 // 0 -> end has to stay in thmi -> thma
523 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
524 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
525 if (trace_hits_box_a0 > trace_hits_box_a1)
531 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
536 // now it is a trace from 0 to end
538 trace_hits_box_a0 = 0;
539 trace_hits_box_a1 = 1;
541 if (!trace_hits_box_1d(end.x, thmi.x, thma.x))
543 if (!trace_hits_box_1d(end.y, thmi.y, thma.y))
545 if (!trace_hits_box_1d(end.z, thmi.z, thma.z))
551 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
553 return trace_hits_box(start, end, thmi - ma, thma - mi);
556 /** engine callback */
557 void URI_Get_Callback(float id, float status, string data)
559 if(url_URI_Get_Callback(id, status, data))
563 else if (id == URI_GET_DISCARD)
567 else if (id >= URI_GET_CURL && id <= URI_GET_CURL_END)
570 Curl_URI_Get_Callback(id, status, data);
572 else if (id >= URI_GET_IPBAN && id <= URI_GET_IPBAN_END)
575 OnlineBanList_URI_Get_Callback(id, status, data);
577 else if (MUTATOR_CALLHOOK(URI_GetCallback, id, status, data))
579 // handled by a mutator
583 LOG_INFO("Received HTTP request data for an invalid id ", ftos(id), ".");
587 string uid2name(string myuid)
589 string s = db_get(ServerProgsDB, strcat("/uid2name/", myuid));
591 // FIXME remove this later after 0.6 release
592 // convert old style broken records to correct style
595 s = db_get(ServerProgsDB, strcat("uid2name", myuid));
598 db_put(ServerProgsDB, strcat("/uid2name/", myuid), s);
599 db_remove(ServerProgsDB, strcat("uid2name", myuid));
604 s = "^1Unregistered Player";
608 bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
610 float m = e.dphitcontentsmask;
611 e.dphitcontentsmask = goodcontents | badcontents;
613 vector org = boundmin;
614 vector delta = boundmax - boundmin;
618 int j; // used after the loop
619 for(j = 0; j < attempts; ++j)
621 start.x = org.x + random() * delta.x;
622 start.y = org.y + random() * delta.y;
623 start.z = org.z + random() * delta.z;
625 // rule 1: start inside world bounds, and outside
626 // solid, and don't start from somewhere where you can
628 tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
629 if (trace_fraction >= 1)
631 if (trace_startsolid)
633 if (trace_dphitcontents & badcontents)
635 if (trace_dphitq3surfaceflags & badsurfaceflags)
638 // rule 2: if we are too high, lower the point
639 if (trace_fraction * delta.z > maxaboveground)
640 start = trace_endpos + '0 0 1' * maxaboveground;
641 vector enddown = trace_endpos;
643 // rule 3: make sure we aren't outside the map. This only works
644 // for somewhat well formed maps. A good rule of thumb is that
645 // the map should have a convex outside hull.
646 // these can be traceLINES as we already verified the starting box
647 vector mstart = start + 0.5 * (e.mins + e.maxs);
648 traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
649 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
651 traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
652 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
654 traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
655 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
657 traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
658 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
660 traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
661 if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
664 // rule 4: we must "see" some spawnpoint or item
666 IL_EACH(g_spawnpoints, checkpvs(mstart, it),
668 if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
676 int items_checked = 0;
677 IL_EACH(g_items, checkpvs(mstart, it),
679 if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
686 if(items_checked >= attempts)
694 // find a random vector to "look at"
695 end.x = org.x + random() * delta.x;
696 end.y = org.y + random() * delta.y;
697 end.z = org.z + random() * delta.z;
698 end = start + normalize(end - start) * vlen(delta);
700 // rule 4: start TO end must not be too short
701 tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
704 if(trace_fraction < minviewdistance / vlen(delta))
707 // rule 5: don't want to look at sky
708 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
711 // rule 6: we must not end up in trigger_hurt
712 if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
718 e.dphitcontentsmask = m;
723 e.angles = vectoangles(end - start);
724 LOG_DEBUG("Needed ", ftos(j + 1), " attempts");
730 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
732 return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);