Reduce action blocker uses and add target entity to CanInteract (#6655)

This commit is contained in:
Leon Friedrich
2022-02-15 17:06:52 +13:00
committed by GitHub
parent 334568dad2
commit ad9ddf1552
60 changed files with 286 additions and 402 deletions

View File

@@ -5,6 +5,7 @@ using Content.Client.Viewport;
using Content.Shared.ActionBlocker;
using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Popups;
using JetBrains.Annotations;
@@ -428,11 +429,18 @@ namespace Content.Client.DragDrop
/// <returns>null if the target doesn't support IDragDropOn</returns>
private bool? ValidDragDrop(DragDropEvent eventArgs)
{
if (!_actionBlockerSystem.CanInteract(eventArgs.User))
if (!_actionBlockerSystem.CanInteract(eventArgs.User, eventArgs.Target))
{
return false;
}
// CanInteract() doesn't support checking a second "target" entity.
// Doing so manually:
var ev = new GettingInteractedWithAttemptEvent(eventArgs.User, eventArgs.Dragged);
RaiseLocalEvent(eventArgs.Dragged, ev);
if (ev.Cancelled)
return false;
var valid = CheckDragDropOn(eventArgs);
foreach (var comp in EntityManager.GetComponents<IDragDropOn>(eventArgs.Target))

View File

@@ -172,11 +172,6 @@ namespace Content.Server.AME.Components
if (playerEntity == default)
return false;
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
//Check if player can interact in their current state
if (!actionBlocker.CanInteract(playerEntity) || !actionBlocker.CanUse(playerEntity))
return false;
//Check if device is powered
if (needsPower && !Powered)
return false;

View File

@@ -45,7 +45,7 @@ namespace Content.Server.Actions.Spells
return;
}
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(caster)) return;
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(caster, null)) return;
// TODO: Nix when we get EntityPrototype serializers
if (!IoCManager.Resolve<IPrototypeManager>().HasIndex<EntityPrototype>(ItemProto))

View File

@@ -18,7 +18,7 @@ namespace Content.Server.Alert.Click
{
public void AlertClicked(EntityUid player)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(player))
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(player, null))
return;
if (IoCManager.Resolve<IEntityManager>().TryGetComponent<SharedPullableComponent?>(player, out var playerPullable))

View File

@@ -49,9 +49,6 @@ namespace Content.Server.Arcade.Components
if(!Powered || !IoCManager.Resolve<IEntityManager>().TryGetComponent(eventArgs.User, out ActorComponent? actor))
return;
if(!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
return;
UserInterface?.Toggle(actor.PlayerSession);
if (UserInterface?.SessionHasOpen(actor.PlayerSession) == true)
{
@@ -130,13 +127,6 @@ namespace Content.Server.Arcade.Components
case BlockGameMessages.BlockGamePlayerActionMessage playerActionMessage:
if (obj.Session != _player) break;
// TODO: Should this check if the Owner can interact...?
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(Owner))
{
DeactivePlayer(obj.Session);
break;
}
if (playerActionMessage.PlayerAction == BlockGamePlayerAction.NewGame)
{
if(_game?.Started == true) _game = new BlockGame(this);

View File

@@ -77,9 +77,6 @@ namespace Content.Server.Arcade.Components
if (!Powered || !IoCManager.Resolve<IEntityManager>().TryGetComponent(eventArgs.User, out ActorComponent? actor))
return;
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
return;
_game ??= new SpaceVillainGame(this);
if (_entityManager.TryGetComponent<WiresComponent>(Owner, out var wiresComponent) && wiresComponent.IsPanelOpen)

View File

@@ -207,11 +207,6 @@ namespace Content.Server.Atmos.Components
internal void ToggleInternals()
{
var user = GetInternalsComponent()?.Owner;
if (user == null || !EntitySystem.Get<ActionBlockerSystem>().CanUse(user.Value))
return;
if (IsConnected)
{
DisconnectFromInternals();
@@ -321,6 +316,9 @@ namespace Content.Server.Atmos.Components
{
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(args.Performer, args.Item))
return false;
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<GasTankComponent?>(args.Item, out var gasTankComponent)) return false;
// no change
if (gasTankComponent.IsConnected == args.ToggledOn) return false;

View File

@@ -181,7 +181,7 @@ namespace Content.Server.Atmos.EntitySystems
if (!Resolve(uid, ref flammable))
return;
if (!flammable.OnFire || !_actionBlockerSystem.CanInteract(flammable.Owner) || flammable.Resisting)
if (!flammable.OnFire || !_actionBlockerSystem.CanInteract(flammable.Owner, null) || flammable.Resisting)
return;
flammable.Resisting = true;

View File

@@ -18,10 +18,8 @@ using Robust.Shared.Player;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
{
[UsedImplicitly]
public class GasValveSystem : EntitySystem
public sealed class GasValveSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -51,11 +49,8 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
private void OnActivate(EntityUid uid, GasValveComponent component, ActivateInWorldEvent args)
{
if (args.User.InRangeUnobstructed(args.Target) && _actionBlockerSystem.CanInteract(args.User))
{
Toggle(uid, component);
SoundSystem.Play(Filter.Pvs(component.Owner), component.ValveSound.GetSound(), component.Owner, AudioHelpers.WithVariation(0.25f));
}
Toggle(uid, component);
SoundSystem.Play(Filter.Pvs(component.Owner), component.ValveSound.GetSound(), component.Owner, AudioHelpers.WithVariation(0.25f));
}
public void Set(EntityUid uid, GasValveComponent component, bool value)

View File

@@ -25,11 +25,10 @@ using Robust.Shared.Players;
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
{
[UsedImplicitly]
public class GasCanisterSystem : EntitySystem
public sealed class GasCanisterSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly AdminLogSystem _adminLogSystem = default!;
public override void Initialize()

View File

@@ -422,7 +422,7 @@ namespace Content.Server.Botany.Components
public bool DoHarvest(EntityUid user)
{
if (Seed == null || _entMan.Deleted(user) || !EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
if (Seed == null || _entMan.Deleted(user))
return false;
var botanySystem = EntitySystem.Get<BotanySystem>();
@@ -657,7 +657,7 @@ namespace Content.Server.Botany.Components
var user = eventArgs.User;
var usingItem = eventArgs.Using;
if ((!_entMan.EntityExists(usingItem) ? EntityLifeStage.Deleted : _entMan.GetComponent<MetaDataComponent>(usingItem).EntityLifeStage) >= EntityLifeStage.Deleted || !EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
if ((!_entMan.EntityExists(usingItem) ? EntityLifeStage.Deleted : _entMan.GetComponent<MetaDataComponent>(usingItem).EntityLifeStage) >= EntityLifeStage.Deleted)
return false;
var botanySystem = EntitySystem.Get<BotanySystem>();

View File

@@ -138,12 +138,6 @@ namespace Content.Server.Buckle.Components
return false;
}
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
{
popupSystem.PopupEntity(Loc.GetString("buckle-component-cannot-do-that-message"), user, Filter.Entities(user));
return false;
}
if (!_entMan.TryGetComponent(to, out strap))
{
return false;
@@ -295,13 +289,6 @@ namespace Content.Server.Buckle.Components
return false;
}
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
{
var popupSystem = EntitySystem.Get<SharedPopupSystem>();
popupSystem.PopupEntity(Loc.GetString("buckle-component-cannot-do-that-message"), user, Filter.Entities(user));
return false;
}
if (!user.InRangeUnobstructed(oldBuckledTo.Owner, Range, popup: true))
{
return false;

View File

@@ -162,11 +162,6 @@ namespace Content.Server.Chemistry.Components
if (playerEntity == default)
return false;
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
//Check if player can interact in their current state
if (!actionBlocker.CanInteract(playerEntity) || !actionBlocker.CanUse(playerEntity))
return false;
//Check if device is powered
if (needsPower && !Powered)
return false;

View File

@@ -207,11 +207,6 @@ namespace Content.Server.Chemistry.Components
if (playerEntity == null)
return false;
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
//Check if player can interact in their current state
if (!actionBlocker.CanInteract(playerEntity.Value) || !actionBlocker.CanUse(playerEntity.Value))
return false;
//Check if device is powered
if (needsPower && !Powered)
return false;

View File

@@ -6,6 +6,7 @@ using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Climbing;
using Content.Shared.DragDrop;
using Content.Shared.Interaction.Events;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Popups;
using Robust.Shared.GameObjects;
@@ -70,7 +71,7 @@ namespace Content.Server.Climbing.Components
/// <returns></returns>
private bool CanVault(EntityUid user, EntityUid target, out string reason)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user, target))
{
reason = Loc.GetString("comp-climbable-cant-interact");
return false;
@@ -110,7 +111,17 @@ namespace Content.Server.Climbing.Components
/// <returns></returns>
private bool CanVault(EntityUid user, EntityUid dragged, EntityUid target, out string reason)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user, dragged))
{
reason = Loc.GetString("comp-climbable-cant-interact");
return false;
}
// CanInteract() doesn't support checking a second "target" entity.
// Doing so manually:
var ev = new GettingInteractedWithAttemptEvent(user, target);
_entities.EventBus.RaiseLocalEvent(target, ev);
if (ev.Cancelled)
{
reason = Loc.GetString("comp-climbable-cant-interact");
return false;

View File

@@ -301,7 +301,7 @@ namespace Content.Server.Construction
var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name);
if (args.SenderSession.AttachedEntity is not {Valid: true} user ||
!Get<ActionBlockerSystem>().CanInteract(user)) return;
!Get<ActionBlockerSystem>().CanInteract(user, null)) return;
if (!EntityManager.TryGetComponent(user, out HandsComponent? hands)) return;
@@ -399,7 +399,7 @@ namespace Content.Server.Construction
_beingBuilt[args.SenderSession].Remove(ev.Ack);
}
if (!Get<ActionBlockerSystem>().CanInteract(user)
if (!Get<ActionBlockerSystem>().CanInteract(user, null)
|| !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.GetActiveHandItem == null
|| !user.InRangeUnobstructed(ev.Location, ignoreInsideBlocker:constructionPrototype.CanBuildInImpassable))
{

View File

@@ -149,7 +149,6 @@ namespace Content.Server.Cuffs.Components
if (_cuffing) return true;
if (eventArgs.Target is not {Valid: true} target ||
!EntitySystem.Get<ActionBlockerSystem>().CanUse(eventArgs.User) ||
!_entities.TryGetComponent<CuffableComponent?>(eventArgs.Target.Value, out var cuffed))
{
return false;

View File

@@ -83,7 +83,7 @@ namespace Content.Server.Cuffs
else
{
// Check if the user can interact.
if (!_actionBlockerSystem.CanInteract(args.User))
if (!_actionBlockerSystem.CanInteract(args.User, args.Target))
{
args.Cancel();
}

View File

@@ -81,7 +81,7 @@ namespace Content.Server.Disposal.Tube.Components
var msg = (UiActionMessage) obj.Message;
if (!PlayerCanUseDisposalTagger(obj.Session))
if (!Anchored)
return;
//Check for correct message and ignore maleformed strings
@@ -96,29 +96,6 @@ namespace Content.Server.Disposal.Tube.Components
}
}
/// <summary>
/// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
/// </summary>
/// <param name="IPlayerSession">The player session.</param>
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
private bool PlayerCanUseDisposalTagger(IPlayerSession session)
{
//Need player entity to check if they are still able to use the configuration interface
if (session.AttachedEntity is not {} attached)
return false;
if (!Anchored)
return false;
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
var groupController = IoCManager.Resolve<IConGroupController>();
//Check if player can interact in their current state
if (!groupController.CanAdminMenu(session) && (!actionBlocker.CanInteract(attached) || !actionBlocker.CanUse(attached)))
return false;
return true;
}
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>

View File

@@ -67,7 +67,7 @@ namespace Content.Server.Disposal.Tube.Components
{
var msg = (UiActionMessage) obj.Message;
if (!PlayerCanUseDisposalTagger(obj.Session))
if (!Anchored)
return;
//Check for correct message and ignore maleformed strings
@@ -78,28 +78,6 @@ namespace Content.Server.Disposal.Tube.Components
}
}
/// <summary>
/// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
/// </summary>
/// <param name="IPlayerSession">The player entity.</param>
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
private bool PlayerCanUseDisposalTagger(IPlayerSession session)
{
//Need player entity to check if they are still able to use the configuration interface
if (session.AttachedEntity is not {} attached)
return false;
if (!Anchored)
return false;
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
var groupController = IoCManager.Resolve<IConGroupController>();
//Check if player can interact in their current state
if (!groupController.CanAdminMenu(session) && (!actionBlocker.CanInteract(attached) || !actionBlocker.CanUse(attached)))
return false;
return true;
}
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>

View File

@@ -184,11 +184,6 @@ namespace Content.Server.Disposal.Unit.EntitySystems
return;
}
if (!_actionBlockerSystem.CanInteract(player) || !_actionBlockerSystem.CanUse(player))
{
return;
}
switch (args.Button)
{
case SharedDisposalUnitComponent.UiButton.Eject:
@@ -241,11 +236,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
}
args.Handled = true;
if (IsValidInteraction(args))
{
component.Owner.GetUIOrNull(SharedDisposalUnitComponent.DisposalUnitUiKey.Key)?.Open(actor.PlayerSession);
}
component.Owner.GetUIOrNull(SharedDisposalUnitComponent.DisposalUnitUiKey.Key)?.Open(actor.PlayerSession);
}
private void HandleAfterInteractUsing(EntityUid uid, DisposalUnitComponent component, AfterInteractUsingEvent args)
@@ -439,30 +430,6 @@ namespace Content.Server.Disposal.Unit.EntitySystems
return state == SharedDisposalUnitComponent.PressureState.Ready && component.RecentlyEjected.Count == 0;
}
private bool IsValidInteraction(ITargetedInteractEventArgs eventArgs)
{
if (!Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
{
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-cannot=interact"));
return false;
}
if (eventArgs.User.IsInContainer())
{
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-cannot-reach"));
return false;
}
// This popup message doesn't appear on clicks, even when code was seperate. Unsure why.
if (!EntityManager.HasComponent<HandsComponent>(eventArgs.User))
{
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-no-hands"));
return false;
}
return true;
}
public bool TryInsert(EntityUid unitId, EntityUid toInsertId, EntityUid userId, DisposalUnitComponent? unit = null)
{
if (!Resolve(unitId, ref unit))

View File

@@ -19,7 +19,6 @@ namespace Content.Server.Extinguisher;
public class FireExtinguisherSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -145,9 +144,6 @@ public class FireExtinguisherSystem : EntitySystem
if (!Resolve(uid, ref extinguisher))
return;
if (!_actionBlockerSystem.CanInteract(user) || !extinguisher.HasSafety)
return;
extinguisher.Safety = !extinguisher.Safety;
SoundSystem.Play(Filter.Pvs(uid), extinguisher.SafetySound.GetSound(), uid,
AudioHelpers.WithVariation(0.125f).WithVolume(-4f));

View File

@@ -100,10 +100,10 @@ namespace Content.Server.Interaction
return;
}
if (!_actionBlockerSystem.CanInteract(userEntity.Value))
if (Deleted(msg.Dropped) || Deleted(msg.Target))
return;
if (Deleted(msg.Dropped) || Deleted(msg.Target))
if (!_actionBlockerSystem.CanInteract(userEntity.Value, msg.Target))
return;
var interactionArgs = new DragDropEvent(userEntity.Value, msg.DropLocation, msg.Dropped, msg.Target);
@@ -232,13 +232,11 @@ namespace Content.Server.Interaction
/// <summary>
/// Uses an empty hand on an entity
/// Finds components with the InteractHand interface and calls their function
/// NOTE: Does not have an InRangeUnobstructed check
/// NOTE: Does not have any range or can-interact checks. These should all have been done before this function is called.
/// </summary>
public override void InteractHand(EntityUid user, EntityUid target, bool checkActionBlocker = true)
public override void InteractHand(EntityUid user, EntityUid target)
{
// TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction.
if (checkActionBlocker && !_actionBlockerSystem.CanInteract(user))
return;
// all interactions should only happen when in range / unobstructed, so no range check is needed
var message = new InteractHandEvent(user, target);
@@ -260,18 +258,26 @@ namespace Content.Server.Interaction
}
// Else we run Activate.
InteractionActivate(user, target);
InteractionActivate(user, target,
checkCanInteract: false,
checkUseDelay: true,
checkAccess: false);
}
/// <summary>
/// Will have two behaviors, either "uses" the used entity at range on the target entity if it is capable of accepting that action
/// Or it will use the used entity itself on the position clicked, regardless of what was there
/// </summary>
public override async Task<bool> InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool inRangeUnobstructed)
public override 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.
if (InteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed))
return true;
if (RangedInteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed))
return;
if (target != null)
{
@@ -279,10 +285,10 @@ namespace Content.Server.Interaction
RaiseLocalEvent(target.Value, rangedMsg);
if (rangedMsg.Handled)
return true;
return;
}
return await InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed);
InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed);
}
public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid? target = null)

View File

@@ -79,21 +79,8 @@ namespace Content.Server.Labels
args.Handled = true;
}
private bool CheckInteract(ICommonSession session)
{
if (session.AttachedEntity is not {Valid: true } uid
|| !Get<ActionBlockerSystem>().CanInteract(uid)
|| !Get<ActionBlockerSystem>().CanUse(uid))
return false;
return true;
}
private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args)
{
if (!CheckInteract(args.Session))
return;
handLabeler.AssignedLabel = args.Label.Trim().Substring(0, Math.Min(handLabeler.MaxLabelChars, args.Label.Length));
DirtyUI(uid, handLabeler);
}

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Content.Server.Clothing.Components;
using Content.Server.Light.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Actions.Behaviors.Item;
using Content.Shared.Examine;
using Content.Shared.Interaction;
@@ -50,10 +51,11 @@ namespace Content.Server.Light.Components
[UsedImplicitly]
[DataDefinition]
public class ToggleLightAction : IToggleItemAction
public sealed class ToggleLightAction : IToggleItemAction
{
public bool DoToggleAction(ToggleItemActionEventArgs args)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(args.Performer, args.Item)) return false;
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<HandheldLightComponent?>(args.Item, out var lightComponent)) return false;
if (lightComponent.Activated == args.ToggledOn) return false;
return EntitySystem.Get<HandheldLightSystem>().ToggleStatus(args.Performer, lightComponent);

View File

@@ -28,7 +28,6 @@ namespace Content.Server.Light.EntitySystems
[UsedImplicitly]
public sealed class HandheldLightSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
@@ -96,7 +95,6 @@ namespace Content.Server.Light.EntitySystems
/// <returns>True if the light's status was toggled, false otherwise.</returns>
public bool ToggleStatus(EntityUid user, HandheldLightComponent component)
{
if (!_blocker.CanUse(user)) return false;
return component.Activated ? TurnOff(component) : TurnOn(user, component);
}

View File

@@ -16,9 +16,8 @@ using Robust.Shared.Player;
namespace Content.Server.Light.EntitySystems
{
[UsedImplicitly]
public class LightReplacerSystem : EntitySystem
public sealed class LightReplacerSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
@@ -42,7 +41,6 @@ namespace Content.Server.Light.EntitySystems
return;
// standard interaction checks
if (!_blocker.CanUse(eventArgs.User)) return;
if (!eventArgs.CanReach) return;
// behaviour will depends on target type
@@ -64,9 +62,6 @@ namespace Content.Server.Light.EntitySystems
if (eventArgs.Handled)
return;
// standard interaction checks
if (!_blocker.CanInteract(eventArgs.User)) return;
var usedUid = eventArgs.Used;
// want to insert a new light bulb?

View File

@@ -75,7 +75,7 @@ namespace Content.Server.Medical
private void OnRelayMovement(EntityUid uid, MedicalScannerComponent component, RelayMovementEntityEvent args)
{
if (_blocker.CanInteract(args.Entity))
if (_blocker.CanInteract(args.Entity, null))
{
if (_gameTiming.CurTime <
component.LastInternalOpenAttempt + MedicalScannerComponent.InternalOpenAttemptDelay)

View File

@@ -24,7 +24,6 @@ namespace Content.Server.Medical.SuitSensors
{
public class SuitSensorSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
@@ -142,7 +141,7 @@ namespace Content.Server.Medical.SuitSensors
return;
// standard interaction checks
if (!args.CanAccess || !args.CanInteract || !_actionBlockerSystem.CanDrop(args.User))
if (!args.CanAccess || !args.CanInteract)
return;
args.Verbs.UnionWith(new[]

View File

@@ -117,12 +117,6 @@ namespace Content.Server.Nuke
if (args.Handled)
return;
// standard interactions check
if (!args.InRangeUnobstructed())
return;
if (!_actionBlocker.CanInteract(args.User) || !_actionBlocker.CanUse(args.User))
return;
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;

View File

@@ -40,7 +40,6 @@ namespace Content.Server.Nutrition.EntitySystems
[Dependency] private readonly StomachSystem _stomachSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly SpillableSystem _spillableSystem = default!;
public override void Initialize()
@@ -111,10 +110,6 @@ namespace Content.Server.Nutrition.EntitySystems
if (args.Handled || args.Target == null || !args.CanReach)
return;
// CanInteract already checked CanInteract
if (!_actionBlockerSystem.CanUse(args.User))
return;
args.Handled = TryDrink(args.User, args.Target.Value, component);
}
@@ -122,12 +117,6 @@ namespace Content.Server.Nutrition.EntitySystems
{
if (args.Handled) return;
if (!args.User.InRangeUnobstructed(uid, popup: true))
{
args.Handled = true;
return;
}
if (!component.Opened)
{
//Do the opening stuff like playing the sounds.
@@ -137,10 +126,6 @@ namespace Content.Server.Nutrition.EntitySystems
return;
}
// CanUse already checked; trying to keep it consistent if we interact with ourselves.
if (!_actionBlockerSystem.CanInteract(args.User))
return;
args.Handled = TryDrink(args.User, args.User, component);
}

View File

@@ -40,7 +40,6 @@ namespace Content.Server.Nutrition.EntitySystems
[Dependency] private readonly UtensilSystem _utensilSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public override void Initialize()
@@ -79,9 +78,6 @@ namespace Content.Server.Nutrition.EntitySystems
public bool TryFeed(EntityUid user, EntityUid target, FoodComponent food)
{
if (!_actionBlockerSystem.CanInteract(user) || !_actionBlockerSystem.CanUse(user))
return false;
// if currently being used to feed, cancel that action.
if (food.CancelToken != null)
{

View File

@@ -150,13 +150,6 @@ namespace Content.Server.ParticleAccelerator.Components
return;
}
if (obj.Session.AttachedEntity is not {Valid: true} attached ||
!EntitySystem.Get<ActionBlockerSystem>().CanInteract(attached))
{
return;
}
if (_wireInterfaceBlocked)
{
return;

View File

@@ -37,9 +37,6 @@ namespace Content.Server.Plants.Systems
if (args.Handled)
return;
// standard interaction checks
if (!_blocker.CanInteract(args.User)) return;
Rustle(uid, component);
args.Handled = _stashSystem.TryHideItem(uid, args.User, args.Used);
}
@@ -49,9 +46,6 @@ namespace Content.Server.Plants.Systems
if (args.Handled)
return;
// standard interaction checks
if (!_blocker.CanInteract(args.User)) return;
Rustle(uid, component);
var gotItem = _stashSystem.TryGetItem(uid, args.User);

View File

@@ -96,7 +96,7 @@ namespace Content.Server.Shuttles.EntitySystems
{
if (comp.Console == null) continue;
if (!_blocker.CanInteract((comp).Owner))
if (!_blocker.CanInteract(comp.Owner, comp.Console.Owner))
{
toRemove.Add(comp);
}
@@ -189,8 +189,7 @@ namespace Content.Server.Shuttles.EntitySystems
public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
{
if (!_blocker.CanInteract(entity) ||
!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
component.SubscribedPilots.Contains(pilotComponent))
{
return;

View File

@@ -88,9 +88,6 @@ namespace Content.Server.Strip
bool Check()
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
return false;
if (item == null)
{
user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything"));
@@ -153,9 +150,6 @@ namespace Content.Server.Strip
bool Check()
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
return false;
if (item == null)
{
user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything"));
@@ -219,9 +213,6 @@ namespace Content.Server.Strip
bool Check()
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
return false;
if (!invSystem.HasSlot(Owner, slot))
return false;
@@ -272,9 +263,6 @@ namespace Content.Server.Strip
bool Check()
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
return false;
if (!hands.HasHand(hand))
return false;

View File

@@ -73,9 +73,6 @@ namespace Content.Server.Stunnable
private void OnUseInHand(EntityUid uid, StunbatonComponent comp, UseInHandEvent args)
{
if (!Get<ActionBlockerSystem>().CanUse(args.User))
return;
if (comp.Activated)
{
TurnOff(comp);

View File

@@ -58,9 +58,7 @@ namespace Content.Server.Tabletop
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
// Check that the entity can interact with the game board.
if(_actionBlockerSystem.CanInteract(args.User))
OpenSessionFor(actor.PlayerSession, uid);
OpenSessionFor(actor.PlayerSession, uid);
}
private void OnGameShutdown(EntityUid uid, TabletopGameComponent component, ComponentShutdown args)

View File

@@ -102,9 +102,6 @@ namespace Content.Server.Tools
return false;
}
if (user != null && !_actionBlockerSystem.CanInteract(user.Value))
return false;
solution.RemoveReagent(welder.FuelReagent, welder.FuelLitCost);
welder.Lit = true;
@@ -140,9 +137,6 @@ namespace Content.Server.Tools
// Optional components.
Resolve(uid, ref item, ref light, ref sprite);
if (user != null && !_actionBlockerSystem.CanInteract(user.Value))
return false;
welder.Lit = false;
// TODO: Make all this use visualizers.

View File

@@ -23,7 +23,6 @@ namespace Content.Server.Tools
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
@@ -214,7 +213,7 @@ namespace Content.Server.Tools
if (!Resolve(tool, ref toolComponent))
return false;
if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded) || !_actionBlockerSystem.CanInteract(user))
if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded))
return false;
var beforeAttempt = new ToolUseAttemptEvent(fuel, user);

View File

@@ -89,10 +89,6 @@ namespace Content.Server.Traitor.Uplink
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
var actionBlocker = EntitySystem.Get<ActionBlockerSystem>();
if (!actionBlocker.CanInteract(uid) || !actionBlocker.CanUse(uid))
return;
ToggleUplinkUI(component, actor.PlayerSession);
args.Handled = true;
}

View File

@@ -18,7 +18,6 @@ namespace Content.Server.UserInterface
[UsedImplicitly]
internal sealed class ActivatableUISystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
public override void Initialize()
@@ -82,12 +81,6 @@ namespace Content.Server.UserInterface
if (aui.AdminOnly && !_adminManager.IsAdmin(actor.PlayerSession)) return false;
if (!HasComp<GhostComponent>(user) && !_actionBlockerSystem.CanInteract(user))
{
user.PopupMessageCursor(Loc.GetString("base-computer-ui-component-cannot-interact"));
return true;
}
var ui = aui.UserInterface;
if (ui == null) return false;

View File

@@ -44,9 +44,6 @@ namespace Content.Server.Weapon.Melee.EnergySword
{
if (args.Handled) return;
if (!_blockerSystem.CanUse(args.User))
return;
args.Handled = true;
if (comp.Activated)
@@ -114,7 +111,7 @@ namespace Content.Server.Weapon.Melee.EnergySword
{
if (args.Handled) return;
if (comp.Hacked || !_blockerSystem.CanInteract(args.User))
if (comp.Hacked)
return;
if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.ContainsAny("Pulsing")) return;

View File

@@ -40,7 +40,7 @@ public sealed partial class GunSystem
if (!TryComp(user, out CombatModeComponent? combat) ||
!combat.IsInCombatMode ||
!_blocker.CanInteract(user)) return;
!_blocker.CanInteract(user, gun.Owner)) return;
var fireAttempt = new GunFireAttemptEvent(user, gun);
EntityManager.EventBus.RaiseLocalEvent(gun.Owner, fireAttempt);

View File

@@ -16,7 +16,7 @@ namespace Content.Shared.ActionBlocker
/// Utility methods to check if a specific entity is allowed to perform an action.
/// </summary>
[UsedImplicitly]
public class ActionBlockerSystem : EntitySystem
public sealed class ActionBlockerSystem : EntitySystem
{
public bool CanMove(EntityUid uid)
{
@@ -26,26 +26,54 @@ namespace Content.Shared.ActionBlocker
return !ev.Cancelled;
}
public bool CanInteract(EntityUid uid)
/// <summary>
/// Raises an event directed at both the user and the target entity to check whether a user is capable of
/// interacting with this entity.
/// </summary>
/// <remarks>
/// If this is a generic interaction without a target (e.g., stop-drop-and-roll when burning), the target
/// may be null. Note that this is checked by <see cref="SharedInteractionSystem"/>. In the majority of
/// cases, systems that provide interactions will not need to check this themselves, though they may need to
/// check other blockers like <see cref="CanPickup(EntityUid)"/>
/// </remarks>
/// <returns></returns>
public bool CanInteract(EntityUid user, EntityUid? target)
{
var ev = new InteractionAttemptEvent(uid);
RaiseLocalEvent(uid, ev);
var ev = new InteractionAttemptEvent(user, target);
RaiseLocalEvent(user, ev);
if (ev.Cancelled)
return false;
if (target == null)
return true;
var targetEv = new GettingInteractedWithAttemptEvent(user, target);
RaiseLocalEvent(target.Value, targetEv);
return !targetEv.Cancelled;
}
/// <summary>
/// Can a user utilize the entity that they are currently holding in their hands.
/// </summary>>
/// <remarks>
/// This event is automatically checked by <see cref="SharedInteractionSystem"/> for any interactions that
/// involve using a held entity. In the majority of cases, systems that provide interactions will not need
/// to check this themselves.
/// </remarks>
public bool CanUseHeldEntity(EntityUid user)
{
var ev = new UseAttemptEvent(user);
RaiseLocalEvent(user, ev);
return !ev.Cancelled;
}
public bool CanUse(EntityUid uid)
public bool CanThrow(EntityUid user)
{
var ev = new UseAttemptEvent(uid);
RaiseLocalEvent(uid, ev);
return !ev.Cancelled;
}
public bool CanThrow(EntityUid uid)
{
var ev = new ThrowAttemptEvent(uid);
RaiseLocalEvent(uid, ev);
var ev = new ThrowAttemptEvent(user);
RaiseLocalEvent(user, ev);
return !ev.Cancelled;
}

View File

@@ -278,6 +278,7 @@ namespace Content.Shared.Containers.ItemSlots
/// <summary>
/// Tries to insert item into a specific slot from an entity's hand.
/// Does not check action blockers.
/// </summary>
/// <returns>False if failed to insert item</returns>
public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, SharedHandsComponent? hands = null)
@@ -293,7 +294,7 @@ namespace Content.Shared.Containers.ItemSlots
return false;
// hands.Drop(item) checks CanDrop action blocker
if (!_actionBlockerSystem.CanInteract(user) && hands.Drop(heldItem))
if (hands.Drop(heldItem))
return false;
Insert(uid, slot, heldItem, user);

View File

@@ -561,7 +561,7 @@ namespace Content.Shared.Hands.Components
/// <summary>
/// Attempts to interact with the item in a hand using the active held item.
/// </summary>
public async void InteractHandWithActiveHand(string handName)
public void InteractHandWithActiveHand(string handName)
{
if (!TryGetActiveHeldEntity(out var activeHeldEntity))
return;
@@ -572,7 +572,7 @@ namespace Content.Shared.Hands.Components
if (activeHeldEntity == heldEntity)
return;
await EntitySystem.Get<SharedInteractionSystem>()
EntitySystem.Get<SharedInteractionSystem>()
.InteractUsing(Owner, activeHeldEntity.Value, heldEntity.Value, EntityCoordinates.Invalid);
}
@@ -585,7 +585,7 @@ namespace Content.Shared.Hands.Components
if (altInteract)
sys.AltInteract(Owner, heldEntity.Value);
else
sys.TryUseInteraction(Owner, heldEntity.Value);
sys.UseInHandInteraction(Owner, heldEntity.Value);
}
public void ActivateHeldEntity(string handName)
@@ -594,7 +594,7 @@ namespace Content.Shared.Hands.Components
return;
EntitySystem.Get<SharedInteractionSystem>()
.TryInteractionActivate(Owner, heldEntity);
.InteractionActivate(Owner, heldEntity.Value);
}
/// <summary>

View File

@@ -12,7 +12,7 @@ public abstract class SharedHandVirtualItemSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<HandVirtualItemComponent, BeingEquippedAttemptEvent>(OnBeingEquippedAttempt);
SubscribeLocalEvent<HandVirtualItemComponent, BeforeInteractEvent>(HandleBeforeInteract);
SubscribeLocalEvent<HandVirtualItemComponent, BeforeRangedInteractEvent>(HandleBeforeInteract);
}
private void OnBeingEquippedAttempt(EntityUid uid, HandVirtualItemComponent component, BeingEquippedAttemptEvent args)
@@ -23,7 +23,7 @@ public abstract class SharedHandVirtualItemSystem : EntitySystem
private static void HandleBeforeInteract(
EntityUid uid,
HandVirtualItemComponent component,
BeforeInteractEvent args)
BeforeRangedInteractEvent args)
{
// No interactions with a virtual item, please.
args.Handled = true;

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Interaction
/// Raised directed on the used object when clicking on another object before an interaction is handled.
/// </summary>
[PublicAPI]
public class BeforeInteractEvent : HandledEntityEventArgs
public class BeforeRangedInteractEvent : HandledEntityEventArgs
{
/// <summary>
/// Entity that triggered the interaction.
@@ -35,7 +35,7 @@ namespace Content.Shared.Interaction
/// </summary>
public bool CanReach { get; }
public BeforeInteractEvent(
public BeforeRangedInteractEvent(
EntityUid user,
EntityUid used,
EntityUid? target,

View File

@@ -2,13 +2,34 @@
namespace Content.Shared.Interaction.Events
{
public class InteractionAttemptEvent : CancellableEntityEventArgs
/// <summary>
/// Event raised directed at a user to see if they can perform a generic interaction.
/// </summary>
public sealed class InteractionAttemptEvent : CancellableEntityEventArgs
{
public InteractionAttemptEvent(EntityUid uid)
public InteractionAttemptEvent(EntityUid uid, EntityUid? target)
{
Uid = uid;
Target = target;
}
public EntityUid Uid { get; }
public EntityUid? Target { get; }
}
/// <summary>
/// Event raised directed at the target entity of an interaction to see if the user is allowed to perform some
/// generic interaction.
/// </summary>
public sealed class GettingInteractedWithAttemptEvent : CancellableEntityEventArgs
{
public GettingInteractedWithAttemptEvent(EntityUid uid, EntityUid? target)
{
Uid = uid;
Target = target;
}
public EntityUid Uid { get; }
public EntityUid? Target { get; }
}
}

View File

@@ -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.

View File

@@ -24,7 +24,7 @@ namespace Content.Shared.Pulling
return false;
}
if (!_blocker.CanInteract(puller))
if (!_blocker.CanInteract(puller, pulled))
{
return false;
}

View File

@@ -34,7 +34,7 @@ namespace Content.Shared.Storage
bool IDraggable.Drop(DragDropEvent eventArgs)
{
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User))
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User, eventArgs.Target))
{
return false;
}

View File

@@ -15,7 +15,7 @@ namespace Content.Shared.Strip.Components
{
return by != Owner
&& IoCManager.Resolve<IEntityManager>().HasComponent<SharedHandsComponent>(@by)
&& EntitySystem.Get<ActionBlockerSystem>().CanInteract(@by);
&& EntitySystem.Get<ActionBlockerSystem>().CanInteract(@by, Owner);
}
bool IDraggable.CanDrop(CanDropEvent args)

View File

@@ -49,7 +49,7 @@ namespace Content.Shared.Tabletop
return false;
}
return playerEntity.InRangeUnobstructed(table.Value) && _actionBlockerSystem.CanInteract(playerEntity);
return playerEntity.InRangeUnobstructed(table.Value) && _actionBlockerSystem.CanInteract(playerEntity, table);
}
protected bool StunnedOrNoHands(EntityUid playerEntity)

View File

@@ -76,10 +76,10 @@ namespace Content.Shared.Verbs
// A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
// call ActionBlocker checks, just cache it for the verb request.
var canInteract = force || _actionBlockerSystem.CanInteract(user);
var canInteract = force || _actionBlockerSystem.CanInteract(user, target);
EntityUid? @using = null;
if (TryComp(user, out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUse(user)))
if (TryComp(user, out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUseHeldEntity(user)))
{
hands.TryGetActiveHeldEntity(out @using);

View File

@@ -118,7 +118,7 @@ namespace Content.Shared.Verbs
/// The entity currently being held by the active hand.
/// </summary>
/// <remarks>
/// This is only ever not null when <see cref="ActionBlockerSystem.CanUse(EntityUid)"/> is true and the user
/// This is only ever not null when <see cref="ActionBlockerSystem.CanUseHeldEntity(EntityUid)"/> is true and the user
/// has hands.
/// </remarks>
public readonly EntityUid? Using;

View File

@@ -1,4 +1,3 @@
buckle-component-cannot-do-that-message = You can't do that!
buckle-component-no-hands-message = You don't have hands!
buckle-component-already-buckled-message = You are already buckled in!
buckle-component-other-already-buckled-message = {$owner} is already buckled in!

View File

@@ -1,3 +1 @@
base-computer-ui-component-cannot-interact = You can't interact with a computer right now.
base-computer-ui-component-not-powered = The computer is not powered.
base-computer-ui-component-not-powered = The computer is not powered.

View File

@@ -8,8 +8,4 @@ ui-disposal-unit-label-status = Ready
ui-disposal-unit-button-flush = Flush
ui-disposal-unit-button-eject = Eject Contents
ui-disposal-unit-button-power = Power
ui-disposal-unit-is-valid-interaction-cannot=interact = You can't do that!
ui-disposal-unit-is-valid-interaction-cannot-reach = You can't reach there!
ui-disposal-unit-is-valid-interaction-no-hands = You have no hands.
ui-disposal-unit-button-power = Power