Upstream (#148)
* Content changes for engine delta-state PR (#28134) * Update GasTileOverlayState * Update DecalGridState * Update NavMapState * poke * poke2 * poke3 * Poke dem tests * Update engine to v223.0.0 (#28239) * Update RobustToolbox * Improve InteractionSystem range & BUI checks (#27999) * Improve InteractionSystem range & BUI checks * Ghost fixes * AAA * Fix test * fix nullable * revert to broadcast event * Fixes for eengine PR * Ah buckle code * ) * Update engine to v223.0.0 * Update engine to v223.1.0 * Update engine to v223.1.1 * Hotfix for crashes from bad item names (#28256) * Fix weapon error logs (#28264) * Update engine to v223.1.2 (#28273) * Update RobustToolbox * Update RobustToolbox * Fix dud modular grenade visuals (#28265) * Fix not networking whitelist and blacklist in storage component (#28238) * fix id card console not updating records (#28237) * fix id card console not updating records * test --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * Remove the network tab (#28231) It is useless and bloat, if a user needs to change these settings they are free to modify their cvars manually via the clientconfig.toml file or via the cvar command. * antag objective issuing refactor (#28216) * add AntagObjectives from GenericAntag * add AntagRandomObjectives that traitor and thief can use * make ObjectivesSystem use initial character name which AntagSelection passes * make thief and traitor use AntagRandomObjectives * remove now unused locale * make sleeper agents rule use baseTraitorRule * restore dragon rule oop * bandaid for genericantag * real * typo --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * move nukie profile loading into its own rule (#28208) * move profile loading out of nukeops rule * make BaseNukeopsRule and use AntagLoadProfileRule * untroll --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * fix antagSelect * Objects ordered through cargo system shouldn't start anchored (#28115) * Order normal space heater instead of anchored variant * Make sure ordered objects don't spawn anchored * Order space heater flatpack instead of a regular space heater * Remove obsolete TODO * Remove unnecessary name --------- Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> * Move PendingZombieComponent to Shared (#28143) * Move PendingZombieComponent to Shared * network me boy --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> * Make it possible to hide full health bars below a total damage threshold (#28127) * Make it possible to hide full health bars below a total damage threshold * Fix not setting state * Fix storage UI interactions (#28291) * Fix storage UI interactions * Add VV support * Fix stripping not marking interactions as handled (#28292) * Make NetworkConfigurator use BoundUserInterfaceCheckRangeEvent (#28293) * Fix hypodarts not injecting with people that have ANY outerclothing (#28301) Update darts.yml * fix borg ui mispredict opening (#28305) move borg ui junk to shared * Add loadout group check (#28311) Forgot to add it back in one of the rewrites. * fix mirror server crashes (#28318) * Remove bogus C# finalizers (#28315) Begging people to learn how this programming language works before throwing random syntax into a file. None of these finalizers ever worked. I also checked whether they were memory leaks and needed *proper* shutdown logic, but they're all instantiated-once UI controls that last for the entire lifetime of the program so it's probably fine. * Cleans up some entity-related spawnmenu stuff (#28234) * cleans up a lot of stuff in the spawnmenu * skibidi dode * spawners update * Revert "spawners update" This reverts commit bc27d9f556b29f6fb1f89cebbe0ac37e28319fd0. * fix antag selection being evil (#28197) * fix antag selection being evil * fix test * untroll the other tests * remove role timer troll * Allow tests to modify antag preferences * Fix antag selection * Misc test fixes * Add AntagPreferenceTest * Fix lazy mistakes * Test cleanup * Try stop players in lobbies from being assigned mid-round antags * ranting * I am going insane --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com> * Revert "fix mirror server crashes (#28318)" This reverts commit bcb0e555b058a4049d0cdb32d64eaf86c35a67be. * fix * Update engine to 223.2.0 (#28329) * Update RobustToolbox * Update RobustToolbox --------- Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: Vasilis <vasilis@pikachu.systems> Co-authored-by: eoineoineoin <github@eoinrul.es> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es> Co-authored-by: Ady4ik <141335742+Ady4ik@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: Mr. 27 <45323883+Dutch-VanDerLinde@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com> Co-authored-by: Kara <lunarautomaton6@gmail.com>
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Input;
|
||||
@@ -15,16 +14,20 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Wall;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared._White.MeatyOre;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.UserInterface;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input;
|
||||
@@ -38,6 +41,8 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
#pragma warning disable 618
|
||||
|
||||
@@ -52,12 +57,11 @@ namespace Content.Shared.Interaction
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _broadphase = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
@@ -66,6 +70,18 @@ namespace Content.Shared.Interaction
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUiRangeQuery;
|
||||
private EntityQuery<FixturesComponent> _fixtureQuery;
|
||||
private EntityQuery<ItemComponent> _itemQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<HandsComponent> _handsQuery;
|
||||
private EntityQuery<InteractionRelayComponent> _relayQuery;
|
||||
private EntityQuery<CombatModeComponent> _combatQuery;
|
||||
private EntityQuery<WallMountComponent> _wallMountQuery;
|
||||
private EntityQuery<UseDelayComponent> _delayQuery;
|
||||
private EntityQuery<ActivatableUIComponent> _uiQuery;
|
||||
|
||||
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
|
||||
|
||||
@@ -78,6 +94,17 @@ namespace Content.Shared.Interaction
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_ignoreUiRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
|
||||
_fixtureQuery = GetEntityQuery<FixturesComponent>();
|
||||
_itemQuery = GetEntityQuery<ItemComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_handsQuery = GetEntityQuery<HandsComponent>();
|
||||
_relayQuery = GetEntityQuery<InteractionRelayComponent>();
|
||||
_combatQuery = GetEntityQuery<CombatModeComponent>();
|
||||
_wallMountQuery = GetEntityQuery<WallMountComponent>();
|
||||
_delayQuery = GetEntityQuery<UseDelayComponent>();
|
||||
_uiQuery = GetEntityQuery<ActivatableUIComponent>();
|
||||
|
||||
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
|
||||
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
|
||||
|
||||
@@ -113,34 +140,57 @@ namespace Content.Shared.Interaction
|
||||
/// </summary>
|
||||
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
|
||||
{
|
||||
var user = ev.Actor;
|
||||
_uiQuery.TryComp(ev.Target, out var uiComp);
|
||||
if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target))
|
||||
{
|
||||
// We permit ghosts to open uis unless explicitly blocked
|
||||
if (ev.Message is not OpenBoundInterfaceMessage || !HasComp<GhostComponent>(ev.Actor) || uiComp?.BlockSpectators == true)
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_actionBlockerSystem.CanInteract(user, ev.Target))
|
||||
var range = _ui.GetUiRange(ev.Target, ev.UiKey);
|
||||
|
||||
// As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range.
|
||||
DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range));
|
||||
|
||||
if (range <= 0 && !IsAccessible(ev.Actor, ev.Target))
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that
|
||||
// they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into
|
||||
// other people's backpacks).
|
||||
if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target)
|
||||
&& !CanAccessViaStorage(user, ev.Target)
|
||||
&& !_adminManager.HasAdminFlag(user, AdminFlags.Admin))
|
||||
if (uiComp == null)
|
||||
return;
|
||||
|
||||
if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor)
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (CompOrNull<IgnorBUIInteractionRangeComponent>(ev.Target) != null)
|
||||
{
|
||||
if (!uiComp.RequireHands)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InRangeUnobstructed(user, ev.Target))
|
||||
{
|
||||
if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0)
|
||||
ev.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private bool UiRangeCheck(Entity<TransformComponent?> user, Entity<TransformComponent?> target, float range)
|
||||
{
|
||||
if (!Resolve(target, ref target.Comp))
|
||||
return false;
|
||||
|
||||
if (user.Owner == target.Owner)
|
||||
return true;
|
||||
|
||||
// Fast check: if the user is the parent of the entity (e.g., holding it), we always assume that it is in range
|
||||
if (target.Comp.ParentUid == user.Owner)
|
||||
return true;
|
||||
|
||||
return InRangeAndAccessible(user, target, range) || _ignoreUiRangeQuery.HasComp(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -197,10 +247,7 @@ namespace Content.Shared.Interaction
|
||||
if (!InRangeUnobstructed(userEntity.Value, uid, popup: true))
|
||||
return false;
|
||||
|
||||
if (!TryComp(uid, out PullableComponent? pull))
|
||||
return false;
|
||||
|
||||
_pullSystem.TogglePull(uid, userEntity.Value, pull);
|
||||
_pullSystem.TogglePull(uid, userEntity.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -276,7 +323,7 @@ namespace Content.Shared.Interaction
|
||||
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
|
||||
{
|
||||
// Always allow attack in these cases
|
||||
if (target == null || !TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
|
||||
if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
|
||||
return false;
|
||||
|
||||
// Only eat input if:
|
||||
@@ -284,7 +331,7 @@ namespace Content.Shared.Interaction
|
||||
// - Target doesn't cancel should-interact event
|
||||
// This is intended to allow items to be picked up in combat mode,
|
||||
// but to also allow items to force attacks anyway (like mobs which are items, e.g. mice)
|
||||
if (!HasComp<ItemComponent>(target))
|
||||
if (!_itemQuery.HasComp(target))
|
||||
return false;
|
||||
|
||||
var combatEv = new CombatModeShouldHandInteractEvent();
|
||||
@@ -314,7 +361,7 @@ namespace Content.Shared.Interaction
|
||||
bool checkAccess = true,
|
||||
bool checkCanUse = true)
|
||||
{
|
||||
if (TryComp<InteractionRelayComponent>(user, out var relay) && relay.RelayEntity is not null)
|
||||
if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null)
|
||||
{
|
||||
// TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways.
|
||||
if (_actionBlockerSystem.CanInteract(user, target))
|
||||
@@ -328,7 +375,7 @@ namespace Content.Shared.Interaction
|
||||
if (target != null && Deleted(target.Value))
|
||||
return;
|
||||
|
||||
if (!altInteract && TryComp<CombatModeComponent>(user, out var combatMode) && combatMode.IsInCombatMode)
|
||||
if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode)
|
||||
{
|
||||
if (!CombatModeCanHandInteract(user, target))
|
||||
return;
|
||||
@@ -350,10 +397,7 @@ namespace Content.Shared.Interaction
|
||||
|
||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||
// Also checks if the item is accessible via some storage UI (e.g., open backpack)
|
||||
if (checkAccess
|
||||
&& target != null
|
||||
&& !_containerSystem.IsInSameOrParentContainer(user, target.Value)
|
||||
&& !CanAccessViaStorage(user, target.Value))
|
||||
if (checkAccess && target != null && !IsAccessible(user, target.Value))
|
||||
return;
|
||||
|
||||
var inRangeUnobstructed = target == null
|
||||
@@ -361,7 +405,7 @@ namespace Content.Shared.Interaction
|
||||
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
|
||||
|
||||
// Does the user have hands?
|
||||
if (!TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand == null)
|
||||
if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null)
|
||||
{
|
||||
var ev = new InteractNoHandEvent(user, target, coordinates);
|
||||
RaiseLocalEvent(user, ev);
|
||||
@@ -501,7 +545,7 @@ namespace Content.Shared.Interaction
|
||||
|
||||
predicate ??= _ => false;
|
||||
var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask);
|
||||
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList();
|
||||
var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList();
|
||||
|
||||
if (rayResults.Count == 0)
|
||||
return dir.Length();
|
||||
@@ -564,23 +608,29 @@ namespace Content.Shared.Interaction
|
||||
}
|
||||
|
||||
var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask);
|
||||
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
|
||||
var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
|
||||
|
||||
return rayResults.Count == 0;
|
||||
}
|
||||
|
||||
public bool InRangeUnobstructed(
|
||||
EntityUid origin,
|
||||
EntityUid other,
|
||||
Entity<TransformComponent?> origin,
|
||||
Entity<TransformComponent?> other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||
Ignored? predicate = null,
|
||||
bool popup = false)
|
||||
{
|
||||
if (!TryComp(other, out TransformComponent? otherXform))
|
||||
if (!Resolve(other, ref other.Comp))
|
||||
return false;
|
||||
|
||||
return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate,
|
||||
return InRangeUnobstructed(origin,
|
||||
other,
|
||||
other.Comp.Coordinates,
|
||||
other.Comp.LocalRotation,
|
||||
range,
|
||||
collisionMask,
|
||||
predicate,
|
||||
popup);
|
||||
}
|
||||
|
||||
@@ -612,8 +662,8 @@ namespace Content.Shared.Interaction
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
EntityUid origin,
|
||||
EntityUid other,
|
||||
Entity<TransformComponent?> origin,
|
||||
Entity<TransformComponent?> other,
|
||||
EntityCoordinates otherCoordinates,
|
||||
Angle otherAngle,
|
||||
float range = InteractionRange,
|
||||
@@ -621,10 +671,10 @@ namespace Content.Shared.Interaction
|
||||
Ignored? predicate = null,
|
||||
bool popup = false)
|
||||
{
|
||||
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
||||
Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false);
|
||||
var inRange = true;
|
||||
MapCoordinates originPos = default;
|
||||
var targetPos = otherCoordinates.ToMap(EntityManager, _transform);
|
||||
var targetPos = _transform.ToMapCoordinates(otherCoordinates);
|
||||
Angle targetRot = default;
|
||||
|
||||
// So essentially:
|
||||
@@ -634,23 +684,30 @@ namespace Content.Shared.Interaction
|
||||
// Alternatively we could check centre distances first though
|
||||
// that means we wouldn't be able to easily check overlap interactions.
|
||||
if (range > 0f &&
|
||||
TryComp<FixturesComponent>(origin, out var fixtureA) &&
|
||||
_fixtureQuery.TryComp(origin, out var fixtureA) &&
|
||||
// These fixture counts are stuff that has the component but no fixtures for <reasons> (e.g. buttons).
|
||||
// At least until they get removed.
|
||||
fixtureA.FixtureCount > 0 &&
|
||||
TryComp<FixturesComponent>(other, out var fixtureB) &&
|
||||
_fixtureQuery.TryComp(other, out var fixtureB) &&
|
||||
fixtureB.FixtureCount > 0 &&
|
||||
TryComp(origin, out TransformComponent? xformA))
|
||||
Resolve(origin, ref origin.Comp))
|
||||
{
|
||||
var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation();
|
||||
var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation();
|
||||
var xfA = new Transform(worldPosA, worldRotA);
|
||||
var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
|
||||
var xfB = new Transform(targetPos.Position, parentRotB + otherAngle);
|
||||
|
||||
// Different map or the likes.
|
||||
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
|
||||
out _, out _, out var distance,
|
||||
xfA, xfB, fixtureA, fixtureB))
|
||||
if (!_broadphase.TryGetNearest(
|
||||
origin,
|
||||
other,
|
||||
out _,
|
||||
out _,
|
||||
out var distance,
|
||||
xfA,
|
||||
xfB,
|
||||
fixtureA,
|
||||
fixtureB))
|
||||
{
|
||||
inRange = false;
|
||||
}
|
||||
@@ -672,15 +729,15 @@ namespace Content.Shared.Interaction
|
||||
else
|
||||
{
|
||||
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
|
||||
originPos = _transform.GetMapCoordinates(origin, xform: xformA);
|
||||
originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp);
|
||||
range = (originPos.Position - targetPos.Position).Length();
|
||||
}
|
||||
}
|
||||
// No fixtures, e.g. wallmounts.
|
||||
else
|
||||
{
|
||||
originPos = _transform.GetMapCoordinates(origin);
|
||||
var otherParent = Transform(other).ParentUid;
|
||||
originPos = _transform.GetMapCoordinates(origin, origin);
|
||||
var otherParent = (other.Comp ?? Transform(other)).ParentUid;
|
||||
targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle;
|
||||
}
|
||||
|
||||
@@ -731,13 +788,13 @@ namespace Content.Shared.Interaction
|
||||
{
|
||||
HashSet<EntityUid> ignored = new();
|
||||
|
||||
if (HasComp<ItemComponent>(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide)
|
||||
if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide)
|
||||
{
|
||||
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck
|
||||
// inside of walls, users can still pick them up.
|
||||
ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics));
|
||||
ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics));
|
||||
}
|
||||
else if (TryComp(target, out WallMountComponent? wallMount))
|
||||
else if (_wallMountQuery.TryComp(target, out var wallMount))
|
||||
{
|
||||
// wall-mount exemptions may be restricted to a specific angle range.da
|
||||
|
||||
@@ -755,13 +812,7 @@ namespace Content.Shared.Interaction
|
||||
ignored.UnionWith(grid.GetAnchoredEntities(targetCoords));
|
||||
}
|
||||
|
||||
Ignored combinedPredicate = e =>
|
||||
{
|
||||
return e == target
|
||||
|| (predicate?.Invoke(e) ?? false)
|
||||
|| ignored.Contains(e);
|
||||
};
|
||||
|
||||
Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e);
|
||||
return combinedPredicate;
|
||||
}
|
||||
|
||||
@@ -958,10 +1009,8 @@ namespace Content.Shared.Interaction
|
||||
bool checkUseDelay = true,
|
||||
bool checkAccess = true)
|
||||
{
|
||||
UseDelayComponent? delayComponent = null;
|
||||
if (checkUseDelay
|
||||
&& TryComp(used, out delayComponent)
|
||||
&& _useDelay.IsDelayed((used, delayComponent)))
|
||||
_delayQuery.TryComp(used, out var delayComponent);
|
||||
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
|
||||
return false;
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
|
||||
@@ -972,11 +1021,11 @@ namespace Content.Shared.Interaction
|
||||
|
||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
|
||||
if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
|
||||
if (checkAccess && !IsAccessible(user, used))
|
||||
return false;
|
||||
|
||||
// Does the user have hands?
|
||||
if (!HasComp<HandsComponent>(user))
|
||||
if (!_handsQuery.HasComp(user))
|
||||
return false;
|
||||
|
||||
var activateMsg = new ActivateInWorldEvent(user, used);
|
||||
@@ -986,7 +1035,9 @@ namespace Content.Shared.Interaction
|
||||
|
||||
DoContactInteraction(user, used, activateMsg);
|
||||
// Still need to call this even without checkUseDelay in case this gets relayed from Activate.
|
||||
_useDelay.TryResetDelay(used, component: delayComponent);
|
||||
if (delayComponent != null)
|
||||
_useDelay.TryResetDelay(used, component: delayComponent);
|
||||
|
||||
if (!activateMsg.WasLogged)
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
return true;
|
||||
@@ -1007,11 +1058,8 @@ namespace Content.Shared.Interaction
|
||||
bool checkCanInteract = true,
|
||||
bool checkUseDelay = true)
|
||||
{
|
||||
UseDelayComponent? delayComponent = null;
|
||||
|
||||
if (checkUseDelay
|
||||
&& TryComp(used, out delayComponent)
|
||||
&& _useDelay.IsDelayed((used, delayComponent)))
|
||||
_delayQuery.TryComp(used, out var delayComponent);
|
||||
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
|
||||
return true; // if the item is on cooldown, we consider this handled.
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
|
||||
@@ -1073,11 +1121,60 @@ namespace Content.Shared.Interaction
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Check if a user can access a target (stored in the same containers) and is in range without obstructions.
|
||||
/// </summary>
|
||||
public bool InRangeAndAccessible(
|
||||
Entity<TransformComponent?> user,
|
||||
Entity<TransformComponent?> target,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||
Ignored? predicate = null)
|
||||
{
|
||||
if (user == target)
|
||||
return true;
|
||||
|
||||
if (!Resolve(user, ref user.Comp))
|
||||
return false;
|
||||
|
||||
if (!Resolve(target, ref target.Comp))
|
||||
return false;
|
||||
|
||||
return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a user can access a target or if they are stored in different containers.
|
||||
/// </summary>
|
||||
public bool IsAccessible(Entity<TransformComponent?> user, Entity<TransformComponent?> target)
|
||||
{
|
||||
if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container))
|
||||
return true;
|
||||
|
||||
return container != null && CanAccessViaStorage(user, target, container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This
|
||||
/// checks if the user can access the item in these situations.
|
||||
/// </summary>
|
||||
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
|
||||
public bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!_containerSystem.TryGetContainingContainer(target, out var container))
|
||||
return false;
|
||||
|
||||
return CanAccessViaStorage(user, target, container);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CanAccessViaStorage(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer container)
|
||||
{
|
||||
if (StorageComponent.ContainerId != container.ID)
|
||||
return false;
|
||||
|
||||
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||
return _ui.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't
|
||||
@@ -1158,19 +1255,15 @@ namespace Content.Shared.Interaction
|
||||
RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA));
|
||||
}
|
||||
|
||||
|
||||
private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
|
||||
{
|
||||
if (ev.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
return;
|
||||
|
||||
if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange))
|
||||
{
|
||||
ev.Result = BoundUserInterfaceRangeResult.Pass;
|
||||
}
|
||||
else
|
||||
{
|
||||
ev.Result = BoundUserInterfaceRangeResult.Fail;
|
||||
}
|
||||
ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange)
|
||||
? BoundUserInterfaceRangeResult.Pass
|
||||
: BoundUserInterfaceRangeResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user