Reduce action blocker uses and add target entity to CanInteract (#6655)
This commit is contained in:
@@ -75,7 +75,7 @@ namespace Content.Shared.Interaction
|
||||
/// </summary>
|
||||
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
|
||||
{
|
||||
if (ev.Sender.AttachedEntity is not EntityUid user || !_actionBlockerSystem.CanInteract(user))
|
||||
if (ev.Sender.AttachedEntity is not EntityUid user || !_actionBlockerSystem.CanInteract(user, ev.Target))
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
@@ -108,6 +108,11 @@ namespace Content.Shared.Interaction
|
||||
return;
|
||||
}
|
||||
|
||||
// We won't bother to check that the target item is ACTUALLY in an inventory slot. UserInteraction() and
|
||||
// InteractionActivate() should check that the item is accessible. So.. if a user wants to lie about an
|
||||
// in-reach item being used in a slot... that should have no impact. This is functionally the same as if
|
||||
// they had somehow directly clicked on that item.
|
||||
|
||||
if (msg.AltInteract)
|
||||
// Use 'UserInteraction' function - behaves as if the user alt-clicked the item in the world.
|
||||
UserInteraction(user.Value, coords, msg.ItemUid, msg.AltInteract);
|
||||
@@ -139,7 +144,14 @@ namespace Content.Shared.Interaction
|
||||
/// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of
|
||||
/// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
|
||||
/// interaction. Having an item in the active hand also disables alternative interactions.</param>
|
||||
public async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid? target, bool altInteract = false)
|
||||
public void UserInteraction(
|
||||
EntityUid user,
|
||||
EntityCoordinates coordinates,
|
||||
EntityUid? target,
|
||||
bool altInteract = false,
|
||||
bool checkCanInteract = true,
|
||||
bool checkAccess = true,
|
||||
bool checkCanUse = true)
|
||||
{
|
||||
if (target != null && Deleted(target.Value))
|
||||
return;
|
||||
@@ -154,55 +166,71 @@ namespace Content.Shared.Interaction
|
||||
if (!ValidateInteractAndFace(user, coordinates))
|
||||
return;
|
||||
|
||||
if (!_actionBlockerSystem.CanInteract(user))
|
||||
if (altInteract && target != null)
|
||||
{
|
||||
// Perform alternative interactions, using context menu verbs.
|
||||
// These perform their own range, can-interact, and accessibility checks.
|
||||
AltInteract(user, target.Value);
|
||||
}
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
|
||||
return;
|
||||
|
||||
// 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 (target != null && !ContainerSystem.IsInSameOrParentContainer(user, target.Value) && !CanAccessViaStorage(user, target.Value))
|
||||
// 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))
|
||||
return;
|
||||
|
||||
// Verify user has a hand, and find what object they are currently holding in their active hand
|
||||
if (!TryComp(user, out SharedHandsComponent? hands))
|
||||
// Does the user have hands?
|
||||
Hand? hand;
|
||||
if (!TryComp(user, out SharedHandsComponent? hands) || !hands.TryGetActiveHand(out hand))
|
||||
return;
|
||||
|
||||
// ^ In future, things like looking at a UI & opening doors (i.e., Activate interactions) shouldn't neccesarily require hands.
|
||||
// But that would first involve some work with BUIs & making sure other activate-interactions check hands if they are required.
|
||||
|
||||
// Check range
|
||||
// TODO: Replace with body interaction range when we get something like arm length or telekinesis or something.
|
||||
var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);
|
||||
if (target == null || !inRangeUnobstructed)
|
||||
var inRangeUnobstructed = !checkAccess || user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true);
|
||||
|
||||
// empty-hand interactions
|
||||
if (hand.HeldEntity == null)
|
||||
{
|
||||
if (!hands.TryGetActiveHeldEntity(out var heldEntity) || !_actionBlockerSystem.CanUse(user))
|
||||
return;
|
||||
|
||||
if (await InteractUsingRanged(user, heldEntity.Value, target, coordinates, inRangeUnobstructed))
|
||||
return;
|
||||
|
||||
// Generate popup only if user actually tried to click on something.
|
||||
if (!inRangeUnobstructed && target != null)
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("interaction-system-user-interaction-cannot-reach"), Filter.Entities(user));
|
||||
}
|
||||
if (inRangeUnobstructed && target != null)
|
||||
InteractHand(user, target.Value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We are close to the nearby object.
|
||||
if (altInteract)
|
||||
// Can the user use the held entity?
|
||||
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
|
||||
return;
|
||||
|
||||
if (inRangeUnobstructed && target != null)
|
||||
{
|
||||
// Perform alternative interactions, using context menu verbs.
|
||||
AltInteract(user, target.Value);
|
||||
}
|
||||
else if (!hands.TryGetActiveHeldEntity(out var heldEntity))
|
||||
{
|
||||
// Since our hand is empty we will use InteractHand/Activate
|
||||
InteractHand(user, target.Value, checkActionBlocker: false);
|
||||
}
|
||||
else if (heldEntity != target && _actionBlockerSystem.CanUse(user))
|
||||
{
|
||||
await InteractUsing(user, heldEntity.Value, target.Value, coordinates, checkActionBlocker: false);
|
||||
InteractUsing(
|
||||
user,
|
||||
hand.HeldEntity.Value,
|
||||
target.Value,
|
||||
coordinates,
|
||||
checkCanInteract: false,
|
||||
checkCanUse: false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InteractUsingRanged(
|
||||
user,
|
||||
hand.HeldEntity.Value,
|
||||
target,
|
||||
coordinates,
|
||||
inRangeUnobstructed);
|
||||
}
|
||||
|
||||
public virtual void InteractHand(EntityUid user, EntityUid target, bool checkActionBlocker = true)
|
||||
public virtual void InteractHand(EntityUid user, EntityUid target)
|
||||
{
|
||||
// TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
|
||||
}
|
||||
@@ -213,11 +241,10 @@ namespace Content.Shared.Interaction
|
||||
// TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
|
||||
}
|
||||
|
||||
public virtual async Task<bool> InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target,
|
||||
public virtual void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target,
|
||||
EntityCoordinates clickLocation, bool inRangeUnobstructed)
|
||||
{
|
||||
// TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
|
||||
@@ -548,21 +575,21 @@ namespace Content.Shared.Interaction
|
||||
|
||||
if (!inRange && popup)
|
||||
{
|
||||
var message = Loc.GetString("shared-interaction-system-in-range-unobstructed-cannot-reach");
|
||||
var message = Loc.GetString("interaction-system-user-interaction-cannot-reach");
|
||||
origin.PopupMessage(message);
|
||||
}
|
||||
|
||||
return inRange;
|
||||
}
|
||||
|
||||
public bool InteractDoBefore(
|
||||
public bool RangedInteractDoBefore(
|
||||
EntityUid user,
|
||||
EntityUid used,
|
||||
EntityUid? target,
|
||||
EntityCoordinates clickLocation,
|
||||
bool canReach)
|
||||
{
|
||||
var ev = new BeforeInteractEvent(user, used, target, clickLocation, canReach);
|
||||
var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(used, ev, false);
|
||||
return ev.Handled;
|
||||
}
|
||||
@@ -572,12 +599,22 @@ namespace Content.Shared.Interaction
|
||||
/// Finds components with the InteractUsing interface and calls their function
|
||||
/// NOTE: Does not have an InRangeUnobstructed check
|
||||
/// </summary>
|
||||
public async Task InteractUsing(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation, bool predicted = false, bool checkActionBlocker = true)
|
||||
public async void InteractUsing(
|
||||
EntityUid user,
|
||||
EntityUid used,
|
||||
EntityUid target,
|
||||
EntityCoordinates clickLocation,
|
||||
bool predicted = false,
|
||||
bool checkCanInteract = true,
|
||||
bool checkCanUse = true)
|
||||
{
|
||||
if (checkActionBlocker && (!_actionBlockerSystem.CanInteract(user) || !_actionBlockerSystem.CanUse(user)))
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
|
||||
return;
|
||||
|
||||
if (InteractDoBefore(user, used, target, clickLocation, true))
|
||||
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
|
||||
return;
|
||||
|
||||
if (RangedInteractDoBefore(user, used, target, clickLocation, true))
|
||||
return;
|
||||
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
@@ -596,13 +633,13 @@ namespace Content.Shared.Interaction
|
||||
return;
|
||||
}
|
||||
|
||||
await InteractDoAfter(user, used, target, clickLocation, true);
|
||||
InteractDoAfter(user, used, target, clickLocation, canReach: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used when clicking on an entity resulted in no other interaction. Used for low-priority interactions.
|
||||
/// </summary>
|
||||
public async Task<bool> InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach)
|
||||
public async void InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach)
|
||||
{
|
||||
if (target is {Valid: false})
|
||||
target = null;
|
||||
@@ -610,7 +647,7 @@ namespace Content.Shared.Interaction
|
||||
var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(used, afterInteractEvent, false);
|
||||
if (afterInteractEvent.Handled)
|
||||
return true;
|
||||
return;
|
||||
|
||||
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, target, canReach);
|
||||
var afterInteracts = AllComps<IAfterInteract>(used).OrderByDescending(x => x.Priority).ToList();
|
||||
@@ -618,46 +655,49 @@ namespace Content.Shared.Interaction
|
||||
foreach (var afterInteract in afterInteracts)
|
||||
{
|
||||
if (await afterInteract.AfterInteract(afterInteractEventArgs))
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
return false;
|
||||
return;
|
||||
|
||||
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
|
||||
RaiseLocalEvent(target.Value, afterInteractUsingEvent, false);
|
||||
return afterInteractEvent.Handled;
|
||||
}
|
||||
|
||||
#region ActivateItemInWorld
|
||||
/// <summary>
|
||||
/// Activates the IActivate behavior of an object
|
||||
/// Verifies that the user is capable of doing the use interaction first
|
||||
/// Raises <see cref="ActivateInWorldEvent"/> events and activates the IActivate behavior of an object.
|
||||
/// </summary>
|
||||
public void TryInteractionActivate(EntityUid? user, EntityUid? used)
|
||||
/// <remarks>
|
||||
/// Does not check the can-use action blocker. In activations interacts can target entities outside of the users
|
||||
/// hands.
|
||||
/// </remarks>
|
||||
public bool InteractionActivate(
|
||||
EntityUid user,
|
||||
EntityUid used,
|
||||
bool checkCanInteract = true,
|
||||
bool checkUseDelay = true,
|
||||
bool checkAccess = true)
|
||||
{
|
||||
if (user == null || used == null)
|
||||
return;
|
||||
UseDelayComponent? delayComponent = null;
|
||||
if (checkUseDelay
|
||||
&& TryComp(used, out delayComponent)
|
||||
&& delayComponent.ActiveDelay)
|
||||
return false;
|
||||
|
||||
InteractionActivate(user.Value, used.Value);
|
||||
}
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
|
||||
return false;
|
||||
|
||||
protected void InteractionActivate(EntityUid user, EntityUid used)
|
||||
{
|
||||
if (TryComp(used, out UseDelayComponent? delayComponent) && delayComponent.ActiveDelay)
|
||||
return;
|
||||
|
||||
if (!_actionBlockerSystem.CanInteract(user) || !_actionBlockerSystem.CanUse(user))
|
||||
return;
|
||||
|
||||
// all activates should only fire when in range / unobstructed
|
||||
if (!InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
|
||||
return;
|
||||
if (checkAccess && !InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
|
||||
return false;
|
||||
|
||||
// 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 (!ContainerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
|
||||
return;
|
||||
if (checkAccess && !ContainerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
|
||||
return false;
|
||||
|
||||
var activateMsg = new ActivateInWorldEvent(user, used);
|
||||
RaiseLocalEvent(used, activateMsg);
|
||||
@@ -665,44 +705,47 @@ namespace Content.Shared.Interaction
|
||||
{
|
||||
BeginDelay(delayComponent);
|
||||
_adminLogSystem.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryComp(used, out IActivate? activateComp))
|
||||
return;
|
||||
return false;
|
||||
|
||||
var activateEventArgs = new ActivateEventArgs(user, used);
|
||||
activateComp.Activate(activateEventArgs);
|
||||
BeginDelay(delayComponent);
|
||||
_adminLogSystem.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); // No way to check success.
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Hands
|
||||
#region Use
|
||||
/// <summary>
|
||||
/// Attempt to perform a use-interaction on an entity. If no interaction occurs, it will instead attempt to
|
||||
/// activate the entity.
|
||||
/// </summary>
|
||||
public void TryUseInteraction(EntityUid user, EntityUid used)
|
||||
{
|
||||
if (_actionBlockerSystem.CanUse(user) && UseInteraction(user, used))
|
||||
return;
|
||||
|
||||
// no use-interaction occurred. Attempt to activate the item instead.
|
||||
InteractionActivate(user, used);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the IUse behaviors of an entity without first checking
|
||||
/// if the user is capable of doing the use interaction.
|
||||
/// Raises UseInHandEvents and activates the IUse behaviors of an entity
|
||||
/// Does not check accessibility or range, for obvious reasons
|
||||
/// </summary>
|
||||
/// <returns>True if the interaction was handled. False otherwise</returns>
|
||||
public bool UseInteraction(EntityUid user, EntityUid used)
|
||||
public bool UseInHandInteraction(
|
||||
EntityUid user,
|
||||
EntityUid used,
|
||||
bool checkCanUse = true,
|
||||
bool checkCanInteract = true,
|
||||
bool checkUseDelay = true)
|
||||
{
|
||||
if (TryComp(used, out UseDelayComponent? delayComponent) && delayComponent.ActiveDelay)
|
||||
UseDelayComponent? delayComponent = null;
|
||||
|
||||
if (checkUseDelay
|
||||
&& TryComp(used, out delayComponent)
|
||||
&& delayComponent.ActiveDelay)
|
||||
return true; // if the item is on cooldown, we consider this handled.
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
|
||||
return false;
|
||||
|
||||
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
|
||||
return false;
|
||||
|
||||
var useMsg = new UseInHandEvent(user, used);
|
||||
RaiseLocalEvent(used, useMsg);
|
||||
if (useMsg.Handled)
|
||||
@@ -724,7 +767,8 @@ namespace Content.Shared.Interaction
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// else, default to activating the item
|
||||
return InteractionActivate(user, used, false, false, false);
|
||||
}
|
||||
|
||||
protected virtual void BeginDelay(UseDelayComponent? component = null)
|
||||
@@ -854,7 +898,7 @@ namespace Content.Shared.Interaction
|
||||
/// Raised when a player attempts to activate an item in an inventory slot or hand slot
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class InteractInventorySlotEvent : EntityEventArgs
|
||||
public sealed class InteractInventorySlotEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that was interacted with.
|
||||
|
||||
Reference in New Issue
Block a user