public void FakeClientActivateInWorld(EntityUid activated)
{
- var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated);
+ var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated, true);
RaiseLocalEvent(activated, activateMsg);
}
components:
- type: Buckle
- type: Hands
+ - type: ComplexInteraction
- type: InputMover
- type: Body
prototype: Human
components:
- type: Cuffable
- type: Hands
+ - type: ComplexInteraction
- type: Body
prototype: Human
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
+using Content.Shared.Interaction.Components;
using Content.Shared.Item;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
{
user = sEntities.SpawnEntity(null, coords);
sEntities.EnsureComponent<HandsComponent>(user);
+ sEntities.EnsureComponent<ComplexInteractionComponent>(user);
handSys.AddHand(user, "hand", HandLocation.Left);
target = sEntities.SpawnEntity(null, coords);
item = sEntities.SpawnEntity(null, coords);
{
user = sEntities.SpawnEntity(null, coords);
sEntities.EnsureComponent<HandsComponent>(user);
+ sEntities.EnsureComponent<ComplexInteractionComponent>(user);
handSys.AddHand(user, "hand", HandLocation.Left);
target = sEntities.SpawnEntity(null, new MapCoordinates(new Vector2(SharedInteractionSystem.InteractionRange - 0.1f, 0), mapId));
item = sEntities.SpawnEntity(null, coords);
{
user = sEntities.SpawnEntity(null, coords);
sEntities.EnsureComponent<HandsComponent>(user);
+ sEntities.EnsureComponent<ComplexInteractionComponent>(user);
handSys.AddHand(user, "hand", HandLocation.Left);
target = sEntities.SpawnEntity(null, coords);
item = sEntities.SpawnEntity(null, coords);
prototype: Aghost
- type: DoAfter
- type: Hands
+ - type: ComplexInteraction
- type: MindContainer
- type: Stripping
- type: Tag
id: HumanVendingDummy
components:
- type: Hands
+ - type: ComplexInteraction
- type: Body
prototype: Human
private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
if (component.ActionEntities is not {} actionEnts)
private void OnExtinguishActivateInWorld(EntityUid uid, ExtinguishOnInteractComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
if (!TryComp(uid, out FlammableComponent? flammable))
private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (TryComp<WiresPanelComponent>(uid, out var panel) && panel.Open)
{
args.Handled = false;
private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
private void OnActivate(EntityUid uid, GasValveComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
Toggle(uid, component);
_audio.PlayPvs(component.ValveSound, uid, AudioParams.Default.WithVariation(0.25f));
+ args.Handled = true;
}
public void Set(EntityUid uid, GasValveComponent component, bool value)
private void OnPumpActivate(EntityUid uid, GasVolumePumpComponent pump, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
private void OnFilterActivate(EntityUid uid, GasFilterComponent filter, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
private void OnMixerActivate(EntityUid uid, GasMixerComponent mixer, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
private void OnCanisterActivate(EntityUid uid, GasCanisterComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (!TryComp<ActorComponent>(args.User, out var actor))
return;
private void OnActivate(EntityUid uid, GasOutletInjectorComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
component.Enabled = !component.Enabled;
UpdateAppearance(uid, component);
+ args.Handled = true;
}
public void UpdateAppearance(EntityUid uid, GasOutletInjectorComponent component, AppearanceComponent? appearance = null)
SubscribeLocalEvent<CardboardBoxComponent, StorageAfterCloseEvent>(AfterStorageClosed);
SubscribeLocalEvent<CardboardBoxComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
SubscribeLocalEvent<CardboardBoxComponent, ActivateInWorldEvent>(OnInteracted);
- SubscribeLocalEvent<CardboardBoxComponent, InteractedNoHandEvent>(OnNoHandInteracted);
SubscribeLocalEvent<CardboardBoxComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<CardboardBoxComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
private void OnInteracted(EntityUid uid, CardboardBoxComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled)
+ return;
+
if (!TryComp<EntityStorageComponent>(uid, out var box))
return;
+ if (!args.Complex)
+ {
+ if (box.Open || !box.Contents.Contains(args.User))
+ return;
+ }
+
args.Handled = true;
_storage.ToggleOpen(args.User, uid, box);
}
}
- private void OnNoHandInteracted(EntityUid uid, CardboardBoxComponent component, InteractedNoHandEvent args)
- {
- //Free the mice please
- if (!TryComp<EntityStorageComponent>(uid, out var box) || box.Open || !box.Contents.Contains(args.User))
- return;
-
- _storage.OpenStorage(uid);
- }
-
private void OnGetAdditionalAccess(EntityUid uid, CardboardBoxComponent component, ref GetAdditionalAccessEvent args)
{
if (component.Mover == null)
private void OnActivated(EntityUid uid, SignalSwitchComponent comp, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
comp.State = !comp.State;
private void HandleActivate(EntityUid uid, MailingUnitComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
{
return;
private void OnActivate(EntityUid uid, SharedDisposalUnitComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (!TryComp(args.User, out ActorComponent? actor))
{
return;
private void OnActivate(EntityUid uid, AirlockComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (TryComp<WiresPanelComponent>(uid, out var panel) &&
panel.Open &&
TryComp<ActorComponent>(args.User, out var actor))
private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
Trigger(uid, args.User);
args.Handled = true;
}
base.Initialize();
SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
- SubscribeLocalEvent<AbsorbentComponent, InteractNoHandEvent>(OnInteractNoHand);
+ SubscribeLocalEvent<AbsorbentComponent, UserActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<AbsorbentComponent, SolutionContainerChangedEvent>(OnAbsorbentSolutionChange);
}
Dirty(uid, component);
}
- private void OnInteractNoHand(EntityUid uid, AbsorbentComponent component, InteractNoHandEvent args)
+ private void OnActivateInWorld(EntityUid uid, AbsorbentComponent component, UserActivateInWorldEvent args)
{
- if (args.Handled || args.Target == null)
+ if (args.Handled)
return;
- Mop(uid, args.Target.Value, uid, component);
+ Mop(uid, args.Target, uid, component);
args.Handled = true;
}
private void OnActivate(Entity<GatherableComponent> gatherable, ref ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
if (gatherable.Comp.ToolWhitelist?.IsValid(args.User, EntityManager) != true)
return;
Gather(gatherable, args.User);
+ args.Handled = true;
}
public void Gather(EntityUid gatheredUid, EntityUid? gatherer = null, GatherableComponent? component = null)
private void OnActivate(Entity<HandheldLightComponent> ent, ref ActivateInWorldEvent args)
{
- if (args.Handled || !ent.Comp.ToggleOnInteract)
+ if (args.Handled || !args.Complex || !ent.Comp.ToggleOnInteract)
return;
if (ToggleStatus(args.User, ent))
SubscribeLocalEvent<MechGrabberComponent, MechEquipmentRemovedEvent>(OnEquipmentRemoved);
SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
- SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
+ SubscribeLocalEvent<MechGrabberComponent, UserActivateInWorldEvent>(OnInteract);
SubscribeLocalEvent<MechGrabberComponent, GrabberDoAfterEvent>(OnMechGrab);
}
args.States.Add(GetNetEntity(uid), state);
}
- private void OnInteract(EntityUid uid, MechGrabberComponent component, InteractNoHandEvent args)
+ private void OnInteract(EntityUid uid, MechGrabberComponent component, UserActivateInWorldEvent args)
{
- if (args.Handled || args.Target is not {} target)
+ if (args.Handled)
return;
+ var target = args.Target;
if (args.Target == args.User || component.DoAfter != null)
return;
private void OnCigarActivatedEvent(Entity<CigarComponent> entity, ref ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
if (!EntityManager.TryGetComponent(entity, out SmokableComponent? smokable))
private void OnActivate(EntityUid uid, PinpointerComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
TogglePinpointer(uid, component);
if (!component.CanRetarget)
LocateTarget(uid, component);
+
+ args.Handled = true;
}
private void OnLocateTarget(ref FTLCompletedEvent ev)
private void OnActivate(Entity<GeigerComponent> geiger, ref ActivateInWorldEvent args)
{
- if (args.Handled || geiger.Comp.AttachedToSuit)
+ if (args.Handled || !args.Complex || geiger.Comp.AttachedToSuit)
return;
args.Handled = true;
private void OnActivate(EntityUid uid, RadioJammerComponent comp, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
var activated = !HasComp<ActiveRadioJammerComponent>(uid) &&
_powerCell.TryGetBatteryFromSlot(uid, out var battery) &&
battery.CurrentCharge > GetCurrentWattage(comp);
#region Toggling
private void OnActivateMicrophone(EntityUid uid, RadioMicrophoneComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (!component.ToggleOnInteract)
return;
private void OnActivateSpeaker(EntityUid uid, RadioSpeakerComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (!component.ToggleOnInteract)
return;
private void InitializeAbilities()
{
- SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
+ SubscribeLocalEvent<RevenantComponent, UserActivateInWorldEvent>(OnInteract);
SubscribeLocalEvent<RevenantComponent, SoulEvent>(OnSoulSearch);
SubscribeLocalEvent<RevenantComponent, HarvestEvent>(OnHarvest);
SubscribeLocalEvent<RevenantComponent, RevenantMalfunctionActionEvent>(OnMalfunctionAction);
}
- private void OnInteract(EntityUid uid, RevenantComponent component, InteractNoHandEvent args)
+ private void OnInteract(EntityUid uid, RevenantComponent component, UserActivateInWorldEvent args)
{
- if (args.Target == args.User || args.Target == null)
+ if (args.Handled)
+ return;
+
+ if (args.Target == args.User)
return;
- var target = args.Target.Value;
+ var target = args.Target;
if (HasComp<PoweredLightComponent>(target))
{
{
BeginHarvestDoAfter(uid, target, component, essence);
}
+
+ args.Handled = true;
}
private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
private void OnActivateThruster(EntityUid uid, ThrusterComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
component.Enabled ^= true;
if (!component.Enabled)
{
DisableThruster(uid, component);
+ args.Handled = true;
}
else if (CanEnable(uid, component))
{
EnableThruster(uid, component);
+ args.Handled = true;
}
}
private void OnTabletopActivate(EntityUid uid, TabletopGameComponent component, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
// Check that a player is attached to the entity.
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
return;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Humanoid;
+using Content.Shared.Interaction.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
RemComp<ReproductiveComponent>(target);
RemComp<ReproductivePartnerComponent>(target);
RemComp<LegsParalyzedComponent>(target);
+ RemComp<ComplexInteractionComponent>(target);
//funny voice
var accentType = "zombie";
/// 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)
+ public bool CanUseHeldEntity(EntityUid user, EntityUid used)
{
- var ev = new UseAttemptEvent(user);
+ var ev = new UseAttemptEvent(user, used);
RaiseLocalEvent(user, ev);
return !ev.Cancelled;
private void OnActivate(EntityUid uid, GraveComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
_popupSystem.PopupClient(Loc.GetString("grave-digging-requires-tool", ("grave", args.Target)), uid, args.User);
+ args.Handled = true;
}
private void OnGraveDigging(EntityUid uid, GraveComponent component, GraveDiggingDoAfterEvent args)
private void OnActivateInWorld(Entity<SolutionContainerMixerComponent> entity, ref ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
TryStartMix(entity, args.User);
+ args.Handled = true;
}
private void OnRemoveAttempt(Entity<SolutionContainerMixerComponent> ent, ref ContainerIsRemovingAttemptEvent args)
private void OnActivated(EntityUid uid, TwoWayLeverComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
component.State = component.State switch
#region Interactions
protected void OnActivate(EntityUid uid, DoorComponent door, ActivateInWorldEvent args)
{
- if (args.Handled || !door.ClickOpen)
+ if (args.Handled || !args.Complex || !door.ClickOpen)
return;
if (!TryToggleDoor(uid, door, args.User, predicted: true))
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Input;
+using Content.Shared.Interaction;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Localizations;
using Robust.Shared.Input.Binding;
SubscribeAllEvent<RequestMoveHandItemEvent>(HandleMoveItemFromHand);
SubscribeAllEvent<RequestHandAltInteractEvent>(HandleHandAltInteract);
+ SubscribeLocalEvent<HandsComponent, GetUsedEntityEvent>(OnGetUsedEntity);
SubscribeLocalEvent<HandsComponent, ExaminedEvent>(HandleExamined);
CommandBinds.Builder
return true;
}
+ private void OnGetUsedEntity(EntityUid uid, HandsComponent component, ref GetUsedEntityEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ // TODO: this pattern is super uncommon, but it might be worth changing GetUsedEntityEvent to be recursive.
+ if (TryComp<VirtualItemComponent>(component.ActiveHandEntity, out var virtualItem))
+ args.Used = virtualItem.BlockingEntity;
+ else
+ args.Used = component.ActiveHandEntity;
+ }
+
//TODO: Actually shows all items/clothing/etc.
private void HandleExamined(EntityUid examinedUid, HandsComponent handsComp, ExaminedEvent args)
{
/// </summary>
public EntityUid Target { get; }
+ /// <summary>
+ /// Whether or not <see cref="User"/> can perform complex interactions or only basic ones.
+ /// </summary>
+ public bool Complex;
+
/// <summary>
/// Set to true when the activation is logged by a specific logger.
/// </summary>
public bool WasLogged { get; set; }
- public ActivateInWorldEvent(EntityUid user, EntityUid target)
+ public ActivateInWorldEvent(EntityUid user, EntityUid target, bool complex)
+ {
+ User = user;
+ Target = target;
+ Complex = complex;
+ }
+}
+
+/// <summary>
+/// Event raised on the user when it activates something in the world
+/// </summary>
+[PublicAPI]
+public sealed class UserActivateInWorldEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
+{
+ /// <summary>
+ /// Entity that activated the target world entity.
+ /// </summary>
+ public EntityUid User { get; }
+
+ /// <summary>
+ /// Entity that was activated in the world.
+ /// </summary>
+ public EntityUid Target { get; }
+
+ /// <summary>
+ /// Whether or not <see cref="User"/> can perform complex interactions or only basic ones.
+ /// </summary>
+ public bool Complex;
+
+ public UserActivateInWorldEvent(EntityUid user, EntityUid target, bool complex)
{
User = user;
Target = target;
+ Complex = complex;
}
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Interaction.Components;
+
+/// <summary>
+/// This is used for identifying entities as being able to use complex interactions with the environment.
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedInteractionSystem))]
+public sealed partial class ComplexInteractionComponent : Component;
namespace Content.Shared.Interaction.Events
{
- public sealed class UseAttemptEvent : CancellableEntityEventArgs
+ public sealed class UseAttemptEvent(EntityUid uid, EntityUid used) : CancellableEntityEventArgs
{
- public UseAttemptEvent(EntityUid uid)
- {
- Uid = uid;
- }
+ public EntityUid Uid { get; } = uid;
- public EntityUid Uid { get; }
+ public EntityUid Used = used;
}
}
Target = target;
}
}
-
- /// <summary>
- /// Low-level interaction event used for entities without hands.
- /// </summary>
- /// <remarks>
- /// SHIT IS CURSED.
- /// </remarks>
- //TODO: KILLLLLLL
- public sealed class InteractNoHandEvent : HandledEntityEventArgs
- {
- /// <summary>
- /// Entity that triggered the interaction.
- /// </summary>
- public EntityUid User;
-
- /// <summary>
- /// Entity that was interacted on.
- /// </summary>
- public EntityUid? Target;
-
- public EntityCoordinates ClickLocation;
-
- public InteractNoHandEvent(EntityUid user, EntityUid? target, EntityCoordinates clickLocation)
- {
- User = user;
- Target = target;
- ClickLocation = clickLocation;
- }
- }
-
- /// <summary>
- /// Reverse of the InteractNoHandEvent - raised on what was interacted on, rather than the other way around.
- /// </summary>
- public sealed class InteractedNoHandEvent : HandledEntityEventArgs
- {
- /// <summary>
- /// Entity that was interacted on
- /// </summary>
- public EntityUid Target;
-
- /// <summary>
- /// Entity that triggered this interaction
- /// </summary>
- public EntityUid User;
-
- public EntityCoordinates ClickLocation;
-
- public InteractedNoHandEvent(EntityUid target, EntityUid user, EntityCoordinates clickLocation)
- {
- Target = target;
- User = user;
- ClickLocation = clickLocation;
- }
- }
}
private void OnActivateInWorld(EntityUid uid, InteractionPopupComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (!component.OnActivate)
return;
private EntityQuery<WallMountComponent> _wallMountQuery;
private EntityQuery<UseDelayComponent> _delayQuery;
private EntityQuery<ActivatableUIComponent> _uiQuery;
+ private EntityQuery<ComplexInteractionComponent> _complexInteractionQuery;
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
_wallMountQuery = GetEntityQuery<WallMountComponent>();
_delayQuery = GetEntityQuery<UseDelayComponent>();
_uiQuery = GetEntityQuery<ActivatableUIComponent>();
+ _complexInteractionQuery = GetEntityQuery<ComplexInteractionComponent>();
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
// TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways.
if (_actionBlockerSystem.CanInteract(user, target))
{
- UserInteraction(relay.RelayEntity.Value, coordinates, target, altInteract, checkCanInteract,
- checkAccess, checkCanUse);
+ UserInteraction(relay.RelayEntity.Value,
+ coordinates,
+ target,
+ altInteract,
+ checkCanInteract,
+ checkAccess,
+ checkCanUse);
return;
}
}
? !checkAccess || InRangeUnobstructed(user, coordinates)
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
- // Does the user have hands?
- if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null)
- {
- var ev = new InteractNoHandEvent(user, target, coordinates);
- RaiseLocalEvent(user, ev);
-
- if (target != null)
- {
- var interactedEv = new InteractedNoHandEvent(target.Value, user, coordinates);
- RaiseLocalEvent(target.Value, interactedEv);
- DoContactInteraction(user, target.Value, ev);
- }
- return;
- }
-
// empty-hand interactions
// combat mode hand interactions will always be true here -- since
// they check this earlier before returning in
- if (hands.ActiveHandEntity is not { } held)
+ if (!TryGetUsedEntity(user, out var used, checkCanUse))
{
if (inRangeUnobstructed && target != null)
InteractHand(user, target.Value);
return;
}
- // Can the user use the held entity?
- if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
- return;
-
- if (target == held)
+ if (target == used)
{
UseInHandInteraction(user, target.Value, checkCanUse: false, checkCanInteract: false);
return;
{
InteractUsing(
user,
- held,
+ used.Value,
target.Value,
coordinates,
checkCanInteract: false,
InteractUsingRanged(
user,
- held,
+ used.Value,
target,
coordinates,
inRangeUnobstructed);
public void InteractHand(EntityUid user, EntityUid target)
{
+ var complexInteractions = SupportsComplexInteractions(user);
+ if (!complexInteractions)
+ {
+ InteractionActivate(user,
+ target,
+ checkCanInteract: false,
+ checkUseDelay: true,
+ checkAccess: false,
+ complexInteractions: complexInteractions);
+ return;
+ }
+
// allow for special logic before main interaction
var ev = new BeforeInteractHandEvent(target);
RaiseLocalEvent(user, ev);
return;
// Else we run Activate.
- InteractionActivate(user, target,
+ InteractionActivate(user,
+ target,
checkCanInteract: false,
checkUseDelay: true,
- checkAccess: false);
+ checkAccess: false,
+ complexInteractions: complexInteractions);
}
public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target,
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
return;
- if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
+ if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user, used))
return;
if (RangedInteractDoBefore(user, used, target, clickLocation, true))
EntityUid used,
bool checkCanInteract = true,
bool checkUseDelay = true,
- bool checkAccess = true)
+ bool checkAccess = true,
+ bool? complexInteractions = null)
{
_delayQuery.TryComp(used, out var delayComponent);
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
if (checkAccess && !IsAccessible(user, used))
return false;
- // Does the user have hands?
- if (!_handsQuery.HasComp(user))
- return false;
-
- var activateMsg = new ActivateInWorldEvent(user, used);
+ complexInteractions ??= SupportsComplexInteractions(user);
+ var activateMsg = new ActivateInWorldEvent(user, used, complexInteractions.Value);
RaiseLocalEvent(used, activateMsg, true);
- if (!activateMsg.Handled)
+ var userEv = new UserActivateInWorldEvent(user, used, complexInteractions.Value);
+ RaiseLocalEvent(user, userEv, true);
+ if (!activateMsg.Handled && !userEv.Handled)
return false;
DoContactInteraction(user, used, activateMsg);
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
return false;
- if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user))
+ if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user, used))
return false;
var useMsg = new UseInHandEvent(user);
? BoundUserInterfaceRangeResult.Pass
: BoundUserInterfaceRangeResult.Fail;
}
+
+ /// <summary>
+ /// Gets the entity that is currently being "used" for the interaction.
+ /// In most cases, this refers to the entity in the character's active hand.
+ /// </summary>
+ /// <returns>If there is an entity being used.</returns>
+ public bool TryGetUsedEntity(EntityUid user, [NotNullWhen(true)] out EntityUid? used, bool checkCanUse = true)
+ {
+ var ev = new GetUsedEntityEvent();
+ RaiseLocalEvent(user, ref ev);
+
+ used = ev.Used;
+ if (!ev.Handled)
+ return false;
+
+ // Can the user use the held entity?
+ if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user, ev.Used!.Value))
+ {
+ used = null;
+ return false;
+ }
+
+ return ev.Handled;
+ }
+
+ /// <summary>
+ /// Checks if a given entity is able to do specific complex interactions.
+ /// This is used to gate manipulation to general humanoids. If a mouse shouldn't be able to do something, then it's complex.
+ /// </summary>
+ public bool SupportsComplexInteractions(EntityUid user)
+ {
+ return _complexInteractionQuery.HasComp(user);
+ }
}
/// <summary>
}
}
+ /// <summary>
+ /// Raised directed by-ref on an entity to determine what item will be used in interactions.
+ /// </summary>
+ [ByRefEvent]
+ public record struct GetUsedEntityEvent()
+ {
+ public EntityUid? Used = null;
+
+ public bool Handled => Used != null;
+ };
+
+ /// <summary>
+ /// Raised directed by-ref on an item and a user to determine if interactions can occur.
+ /// </summary>
+ /// <param name="Cancelled">Whether the hand interaction should be cancelled.</param>
+ [ByRefEvent]
+ public record struct AttemptUseInteractEvent(EntityUid User, EntityUid Used, bool Cancelled = false);
+
/// <summary>
/// Raised directed by-ref on an item to determine if hand interactions should go through.
/// Defaults to allowing hand interactions to go through. Cancel to force the item to be attacked instead.
private void OnActivated(EntityUid uid, LockComponent lockComp, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
// Only attempt an unlock by default on Activate
{
SubscribeLocalEvent<MechComponent, MechToggleEquipmentEvent>(OnToggleEquipmentAction);
SubscribeLocalEvent<MechComponent, MechEjectPilotEvent>(OnEjectPilotEvent);
- SubscribeLocalEvent<MechComponent, InteractNoHandEvent>(RelayInteractionEvent);
+ SubscribeLocalEvent<MechComponent, UserActivateInWorldEvent>(RelayInteractionEvent);
SubscribeLocalEvent<MechComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<MechComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<MechComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
TryEject(uid, component);
}
- private void RelayInteractionEvent(EntityUid uid, MechComponent component, InteractNoHandEvent args)
+ private void RelayInteractionEvent(EntityUid uid, MechComponent component, UserActivateInWorldEvent args)
{
var pilot = component.PilotSlot.ContainedEntity;
if (pilot == null)
if (component.RemovalTime == null)
return;
- if (args.Handled || !TryComp<PhysicsComponent>(uid, out var physics) || physics.BodyType != BodyType.Static)
+ if (args.Handled || !args.Complex || !TryComp<PhysicsComponent>(uid, out var physics) || physics.BodyType != BodyType.Static)
return;
args.Handled = true;
protected void OnInteract(EntityUid uid, SharedEntityStorageComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
args.Handled = true;
/// </summary>
private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInWorldEvent args)
{
- if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert))
+ if (args.Handled || !args.Complex || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert))
return;
// Toggle
private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args)
{
- if (args.Handled || args.Target == args.User)
+ if (args.Handled || !args.Complex || args.Target == args.User)
return;
if (TryOpenStrippingUi(args.User, (uid, component)))
private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
SetScannerEnabled(uid, !scanner.Enabled, scanner);
+ args.Handled = true;
}
private void SetScannerEnabled(EntityUid uid, bool enabled, TrayScannerComponent? scanner = null)
private void OnActivateInWorld(Entity<SwapTeleporterComponent> ent, ref ActivateInWorldEvent args)
{
+ if (args.Handled || !args.Complex)
+ return;
+
var (uid, comp) = ent;
var user = args.User;
if (comp.TeleportTime != null)
comp.NextTeleportUse = _timing.CurTime + comp.Cooldown;
comp.TeleportTime = _timing.CurTime + comp.TeleportDelay;
Dirty(uid, comp);
+ args.Handled = true;
}
public void DoTeleport(Entity<SwapTeleporterComponent, TransformComponent> ent)
private void OnActivateInWorld(EntityUid uid, ToiletComponent comp, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
args.Handled = true;
private void OnMultipleToolActivated(EntityUid uid, MultipleToolComponent multiple, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
args.Handled = CycleMultipleTool(uid, multiple, args.User);
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
if (component.VerbOnly)
// call ActionBlocker checks, just cache it for the verb request.
var canInteract = force || _actionBlockerSystem.CanInteract(user, target);
- EntityUid? @using = null;
- if (TryComp(user, out HandsComponent? hands) && (force || _actionBlockerSystem.CanUseHeldEntity(user)))
- {
- // if we don't actually have any hands, pass in a null value for the events.
- if (hands.Count == 0)
- {
- hands = null;
- }
- else
- {
- @using = hands.ActiveHandEntity;
-
- // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
- // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
- // their sprite.
-
- if (TryComp(@using, out VirtualItemComponent? pull))
- {
- @using = pull.BlockingEntity;
- }
- }
- }
+ _interactionSystem.TryGetUsedEntity(user, out var @using);
+ TryComp<HandsComponent>(user, out var hands);
// TODO: fix this garbage and use proper generics or reflection or something else, not this.
if (types.Contains(typeof(InteractionVerb)))
private void OnGunActivate(EntityUid uid, GrapplingGunComponent component, ActivateInWorldEvent args)
{
- if (!Timing.IsFirstTimePredicted || args.Handled)
+ if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex)
return;
if (Deleted(component.Projectile))
private void OnForceActivate(EntityUid uid, ForceGunComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
StopTether(uid, component);
}
private void OnTetherActivate(EntityUid uid, TetherGunComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
StopTether(uid, component);
}
private void OnInteractHandEvent(EntityUid uid, BatteryWeaponFireModesComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (component.FireModes.Count < 2)
return;
private void OnRechargeCycled(EntityUid uid, RechargeCycleAmmoComponent component, ActivateInWorldEvent args)
{
+ if (!args.Complex)
+ return;
+
if (!TryComp<BasicEntityAmmoProviderComponent>(uid, out var basic) || args.Handled)
return;
/// </summary>
private void OnChamberActivate(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ActivateInWorldEvent args)
{
- if (args.Handled)
+ if (args.Handled || !args.Complex)
return;
args.Handled = true;
- type: Hands
showInHands: false
disableExplosionRecursion: true
+ - type: ComplexInteraction
- type: IntrinsicRadioReceiver
- type: IntrinsicRadioTransmitter
channels:
state: "creampie_human"
visible: false
- type: Hands
+ - type: ComplexInteraction
- type: GenericVisualizer
visuals:
enum.CreamPiedVisuals.Creamed:
- type: NpcFactionMember
factions:
- SimpleHostile
- - type: Hands
- type: Sprite
drawdepth: Mobs
sprite: Structures/Machines/VendingMachines/cola.rsi
factions:
- Xeno
- type: Hands
+ - type: ComplexInteraction
- type: Sprite
drawdepth: Mobs
sprite: Mobs/Aliens/Xenos/burrower.rsi
canInteract: true
- type: GhostHearing
- type: Hands
+ - type: ComplexInteraction
- type: Puller
- type: CombatMode
- type: Physics
- type: Inventory
templateId: holoclown
- type: Hands
+ - type: ComplexInteraction
- type: Clumsy
clumsyDamage:
types:
- type: Identity
- type: IdExaminable
- type: Hands
+ - type: ComplexInteraction
- type: Internals
- type: Inventory
- type: InventorySlots
abstract: true
components:
- type: Hands
+ - type: ComplexInteraction
- type: Inventory
- type: InventorySlots
- type: ContainerContainer