]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Decouple interactions from hands, cleanup old events, add new fears (#28393)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Fri, 31 May 2024 20:26:19 +0000 (16:26 -0400)
committerGitHub <noreply@github.com>
Fri, 31 May 2024 20:26:19 +0000 (13:26 -0700)
* ok basic shit

* second part

* pretend it isn't real it can't hurt you.

* :eye: :eye:

* shadowcommander review

72 files changed:
Content.Client/Guidebook/GuidebookSystem.cs
Content.IntegrationTests/Tests/Buckle/BuckleTest.cs
Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs
Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.cs
Content.IntegrationTests/Tests/VendingMachineRestockTest.cs
Content.Server/Actions/ActionOnInteractSystem.cs
Content.Server/Atmos/EntitySystems/FlammableSystem.cs
Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs
Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs
Content.Server/Atmos/Piping/Binary/EntitySystems/GasValveSystem.cs
Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs
Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs
Content.Server/Atmos/Piping/Trinary/EntitySystems/GasMixerSystem.cs
Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs
Content.Server/Atmos/Piping/Unary/EntitySystems/GasOutletInjectorSystem.cs
Content.Server/CardboardBox/CardboardBoxSystem.cs
Content.Server/DeviceLinking/Systems/SignalSwitchSystem.cs
Content.Server/Disposal/Mailing/MailingUnitSystem.cs
Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs
Content.Server/Doors/Systems/AirlockSystem.cs
Content.Server/Explosion/EntitySystems/TriggerSystem.cs
Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
Content.Server/Gatherable/GatherableSystem.cs
Content.Server/Light/EntitySystems/HandheldLightSystem.cs
Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs
Content.Server/Nutrition/EntitySystems/SmokingSystem.Cigar.cs
Content.Server/Pinpointer/PinpointerSystem.cs
Content.Server/Radiation/Systems/GeigerSystem.cs
Content.Server/Radio/EntitySystems/JammerSystem.cs
Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs
Content.Server/Shuttles/Systems/ThrusterSystem.cs
Content.Server/Tabletop/TabletopSystem.cs
Content.Server/Zombies/ZombieSystem.Transform.cs
Content.Shared/ActionBlocker/ActionBlockerSystem.cs
Content.Shared/Burial/BurialSystem.cs
Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerMixerSystem.cs
Content.Shared/DeviceLinking/Systems/TwoWayLeverSystem.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs
Content.Shared/Interaction/ActivateInWorldEvent.cs
Content.Shared/Interaction/Components/ComplexInteractionComponent.cs [new file with mode: 0644]
Content.Shared/Interaction/Events/UseAttemptEvent.cs
Content.Shared/Interaction/InteractHand.cs
Content.Shared/Interaction/InteractionPopupSystem.cs
Content.Shared/Interaction/SharedInteractionSystem.cs
Content.Shared/Lock/LockSystem.cs
Content.Shared/Mech/EntitySystems/SharedMechSystem.cs
Content.Shared/Projectiles/SharedProjectileSystem.cs
Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs
Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
Content.Shared/Strip/SharedStrippableSystem.cs
Content.Shared/SubFloor/SharedTrayScannerSystem.cs
Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs
Content.Shared/Toilet/Systems/SharedToiletSystem.cs
Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs
Content.Shared/UserInterface/ActivatableUISystem.cs
Content.Shared/Verbs/SharedVerbSystem.cs
Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs
Content.Shared/Weapons/Misc/SharedTetherGunSystem.Force.cs
Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs
Content.Shared/Weapons/Ranged/Systems/BatteryWeaponFireModesSystem.cs
Content.Shared/Weapons/Ranged/Systems/RechargeCycleAmmoSystem.cs
Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.ChamberMagazine.cs
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml
Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
Resources/Prototypes/Entities/Mobs/Player/guardian.yml
Resources/Prototypes/Entities/Mobs/Species/base.yml

index 0aa2c85142e0410073308ac0cfe7d5b0ec9731fa..86dcf76942487f7c8f6e6a0a9dacd0c453a18f08 100644 (file)
@@ -148,7 +148,7 @@ public sealed class GuidebookSystem : EntitySystem
 
     public void FakeClientActivateInWorld(EntityUid activated)
     {
-        var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated);
+        var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated, true);
         RaiseLocalEvent(activated, activateMsg);
     }
 
index 7c700d9fb8a8ecc2ae03443a5e40a205b1eebe52..57ac63b12476b3f8b60f8a58ff083dbb8075363f 100644 (file)
@@ -29,6 +29,7 @@ namespace Content.IntegrationTests.Tests.Buckle
   components:
   - type: Buckle
   - type: Hands
+  - type: ComplexInteraction
   - type: InputMover
   - type: Body
     prototype: Human
index c6a8e618cc1a2f173fa05a35afeefaf7333248ff..0ac6b68a3ec2759ed508e0d75ab6e06d88c13ecc 100644 (file)
@@ -24,6 +24,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
   components:
   - type: Cuffable
   - type: Hands
+  - type: ComplexInteraction
   - type: Body
     prototype: Human
 
index 4415eddf376d25c7fbfafe4ec45f823a7ec02e6c..2b844d34f0caaf580eb0b472d37b861a49a81082 100644 (file)
@@ -4,6 +4,7 @@ using Content.Server.Interaction;
 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;
@@ -64,6 +65,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
             {
                 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);
@@ -205,6 +207,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
             {
                 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);
@@ -347,6 +350,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
             {
                 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);
index 42f64b344cdb6af9f3b5861fe3e490c5bd86c521..e5f794feaa04e6541bc14d367b4eb5cb34064b52 100644 (file)
@@ -137,6 +137,7 @@ public abstract partial class InteractionTest
     prototype: Aghost
   - type: DoAfter
   - type: Hands
+  - type: ComplexInteraction
   - type: MindContainer
   - type: Stripping
   - type: Tag
index 99481db70e71e78153fd8a5cc63deaae79fb706a..3cceaefbdc9eaef8f4584ff65eabcae4f23f203a 100644 (file)
@@ -27,6 +27,7 @@ namespace Content.IntegrationTests.Tests
   id: HumanVendingDummy
   components:
   - type: Hands
+  - type: ComplexInteraction
   - type: Body
     prototype: Human
 
index b6eec0ce0f69ad769e185f67fa44a6f31a3c231d..28685858592954d66fe82d970a482351f4722052 100644 (file)
@@ -39,7 +39,7 @@ public sealed class ActionOnInteractSystem : EntitySystem
 
     private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args)
     {
-        if (args.Handled)
+        if (args.Handled || !args.Complex)
             return;
 
         if (component.ActionEntities is not {} actionEnts)
index b6e26435a759ef22a2f5195b18adeffd7f6c30a1..dd0ac25d703b78933d074d6e0c2346fe3137454a 100644 (file)
@@ -155,7 +155,7 @@ namespace Content.Server.Atmos.EntitySystems
 
         private void OnExtinguishActivateInWorld(EntityUid uid, ExtinguishOnInteractComponent component, ActivateInWorldEvent args)
         {
-            if (args.Handled)
+            if (args.Handled || !args.Complex)
                 return;
 
             if (!TryComp(uid, out FlammableComponent? flammable))
index 881f54512a166e3407da53a0cac7c7b9538dcd86..f4650861dbcae4e3f3d6ee2a2f1e196ecfe21f00 100644 (file)
@@ -246,6 +246,9 @@ public sealed class AirAlarmSystem : EntitySystem
 
     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;
index 83b7b67ba46a53b185d8a684e5e38c2b5ed2b707..871c84e058866d9ab68c5ace83aa956ddb01428d 100644 (file)
@@ -103,6 +103,9 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
 
         private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, ActivateInWorldEvent args)
         {
+            if (args.Handled || !args.Complex)
+                return;
+
             if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
                 return;
 
index ed7567428e14f9505aaa82362c0186d1d7975a91..4aeba2f8fe2f8acaffbf39665916d7fab65ad1d1 100644 (file)
@@ -52,8 +52,12 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
 
         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)
index cbcd1f4fa3bd85fe36fe8b8028f0a6676fe22cd1..d9fbeb474e2f3252b13da9296b3430459c711b8e 100644 (file)
@@ -133,6 +133,9 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
 
         private void OnPumpActivate(EntityUid uid, GasVolumePumpComponent pump, ActivateInWorldEvent args)
         {
+            if (args.Handled || !args.Complex)
+                return;
+
             if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
                 return;
 
index 007d304e98e7c6c7dec06b45206fb998575a6895..752d1e9eb83511d5ca6d7fd43888734513841631 100644 (file)
@@ -99,6 +99,9 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
 
         private void OnFilterActivate(EntityUid uid, GasFilterComponent filter, ActivateInWorldEvent args)
         {
+            if (args.Handled || !args.Complex)
+                return;
+
             if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
                 return;
 
index 4ab8572843b345bbf84fba6430f5f7bdf4d21551..178caeaa4a92044a24f8c8e0a590f8b869f7fc5f 100644 (file)
@@ -139,6 +139,9 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
 
         private void OnMixerActivate(EntityUid uid, GasMixerComponent mixer, ActivateInWorldEvent args)
         {
+            if (args.Handled || !args.Complex)
+                return;
+
             if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
                 return;
 
index e279db09aaf848fa0f6dd860416a4f32487ebd43..584928def72a5d02309108b3aad434096d8ab42f 100644 (file)
@@ -201,6 +201,9 @@ public sealed class GasCanisterSystem : EntitySystem
 
     private void OnCanisterActivate(EntityUid uid, GasCanisterComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         if (!TryComp<ActorComponent>(args.User, out var actor))
             return;
 
index 834a1dfb0b7526203d2e480b9820d24eaf60653a..6203918517087b84bed3cbd543c206ab0092aefb 100644 (file)
@@ -33,8 +33,12 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
 
         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)
index b9c9427d5c8c92346b3f34ef8d297838ad54e04c..836dc485d9225a9d26af383270eb6549268883cd 100644 (file)
@@ -36,7 +36,6 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
         SubscribeLocalEvent<CardboardBoxComponent, StorageAfterCloseEvent>(AfterStorageClosed);
         SubscribeLocalEvent<CardboardBoxComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
         SubscribeLocalEvent<CardboardBoxComponent, ActivateInWorldEvent>(OnInteracted);
-        SubscribeLocalEvent<CardboardBoxComponent, InteractedNoHandEvent>(OnNoHandInteracted);
         SubscribeLocalEvent<CardboardBoxComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
         SubscribeLocalEvent<CardboardBoxComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
 
@@ -45,9 +44,18 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
 
     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);
 
@@ -58,15 +66,6 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
         }
     }
 
-    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)
index f6469d68b932d845fafd025709a8b7b2d2e938c9..67fad29d934e5ea89313dc460d6cbfe066c67b17 100644 (file)
@@ -26,7 +26,7 @@ public sealed class SignalSwitchSystem : EntitySystem
 
     private void OnActivated(EntityUid uid, SignalSwitchComponent comp, ActivateInWorldEvent args)
     {
-        if (args.Handled)
+        if (args.Handled || !args.Complex)
             return;
 
         comp.State = !comp.State;
index 8e9c9e4ba73a4551e31422c43b5f98e2cedc1bb3..e1fbdbf0894d53184f7a7f6ffdfc0f36426f5874 100644 (file)
@@ -152,6 +152,9 @@ public sealed class MailingUnitSystem : EntitySystem
 
     private void HandleActivate(EntityUid uid, MailingUnitComponent component, ActivateInWorldEvent args)
     {
+        if (args.Handled || !args.Complex)
+            return;
+
         if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
         {
             return;
index 1f3b9703d4cce359d8ef0dcd7c9f2f99bad9ce54..e63c03bd5e7a45b18f7e9d41e6f1170ad06b8435 100644 (file)
@@ -263,6 +263,9 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
 
     private void OnActivate(EntityUid uid, SharedDisposalUnitComponent component, ActivateInWorldEvent args)
     {
+        if (args.Handled || !args.Complex)
+            return;
+
         if (!TryComp(args.User, out ActorComponent? actor))
         {
             return;
index 71f9347e9ed36899eeb8af13df621ba0e68240f7..fd5d3a9ceba3c654a1cb22484aebf6cbf0c1ce3f 100644 (file)
@@ -67,6 +67,9 @@ public sealed class AirlockSystem : SharedAirlockSystem
 
     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))
index 7c6b5df7f143264f0b28e97eb92a6515e35b9002..e03b8aff54460dd744e1172ed5c3d4cfffac9795 100644 (file)
@@ -218,6 +218,9 @@ namespace Content.Server.Explosion.EntitySystems
 
         private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args)
         {
+            if (args.Handled || !args.Complex)
+                return;
+
             Trigger(uid, args.User);
             args.Handled = true;
         }
index d88f46968abb77109b751b84c8a1821b2a24f294..a3717fd94ce8dbf1a8e834548799a5ae3fbaf25f 100644 (file)
@@ -34,7 +34,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
         base.Initialize();
         SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
         SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
-        SubscribeLocalEvent<AbsorbentComponent, InteractNoHandEvent>(OnInteractNoHand);
+        SubscribeLocalEvent<AbsorbentComponent, UserActivateInWorldEvent>(OnActivateInWorld);
         SubscribeLocalEvent<AbsorbentComponent, SolutionContainerChangedEvent>(OnAbsorbentSolutionChange);
     }
 
@@ -84,12 +84,12 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
         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;
     }
 
index 44e60cb102a1e08552c9908be9cc3f8479282aa9..e24b0da593949efa4f2b913a66865de0f4577b8c 100644 (file)
@@ -38,10 +38,14 @@ public sealed partial class GatherableSystem : EntitySystem
 
     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)
index 813f8c407b7125d6650f467b1080ce388c04144c..a5a41bcc1052efe43db3572d700b57f4534f4e45 100644 (file)
@@ -126,7 +126,7 @@ namespace Content.Server.Light.EntitySystems
 
         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))
index 4b2ba8ffbdccde70a6b713485506cbdc0db79419..c5c44056b96cd8b91aa17fc4a95ecfcb3caae48c 100644 (file)
@@ -40,7 +40,7 @@ public sealed class MechGrabberSystem : EntitySystem
         SubscribeLocalEvent<MechGrabberComponent, MechEquipmentRemovedEvent>(OnEquipmentRemoved);
         SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
 
-        SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
+        SubscribeLocalEvent<MechGrabberComponent, UserActivateInWorldEvent>(OnInteract);
         SubscribeLocalEvent<MechGrabberComponent, GrabberDoAfterEvent>(OnMechGrab);
     }
 
@@ -123,10 +123,11 @@ public sealed class MechGrabberSystem : EntitySystem
         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;
index 4e672444d18181a0abc04766baf3f18a224b1e7b..510b9552a3df2d207dd5b56cc51e734b7456a8a7 100644 (file)
@@ -18,7 +18,7 @@ namespace Content.Server.Nutrition.EntitySystems
 
         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))
index be9a715d5d578926f1d9ebbf5e443155e5fd834b..eebf9cbbfde826198a33e068c660ea52d772fa6b 100644 (file)
@@ -45,10 +45,15 @@ public sealed class PinpointerSystem : SharedPinpointerSystem
 
     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)
index f889336a0681e04fdb538775c5b74374af1082a4..a0bc5dd7394cdb4e5e93774f939a9d9d64252fd6 100644 (file)
@@ -35,7 +35,7 @@ public sealed class GeigerSystem : SharedGeigerSystem
 
     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;
 
index 4f58cb21e1d7ca727411c8fa5967efb27f904acf..223d0e47c0215ecc24a4b78870cc0f9316327d31 100644 (file)
@@ -67,6 +67,9 @@ public sealed class JammerSystem : SharedJammerSystem
 
     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);
index 56c5d8e548ce0bb373aa48bc08c85aa0d3f16256..8484fb2336384f17b3824ee49a2e61fef8167835 100644 (file)
@@ -81,6 +81,9 @@ public sealed class RadioDeviceSystem : EntitySystem
     #region Toggling
     private void OnActivateMicrophone(EntityUid uid, RadioMicrophoneComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         if (!component.ToggleOnInteract)
             return;
 
@@ -90,6 +93,9 @@ public sealed class RadioDeviceSystem : EntitySystem
 
     private void OnActivateSpeaker(EntityUid uid, RadioSpeakerComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         if (!component.ToggleOnInteract)
             return;
 
index 4106630d526ad551ea0c25bc50ebee56e2ebb730..fd7d15ea5dceb61febd7f15848d065927a2fa76a 100644 (file)
@@ -43,7 +43,7 @@ public sealed partial class RevenantSystem
 
     private void InitializeAbilities()
     {
-        SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
+        SubscribeLocalEvent<RevenantComponent, UserActivateInWorldEvent>(OnInteract);
         SubscribeLocalEvent<RevenantComponent, SoulEvent>(OnSoulSearch);
         SubscribeLocalEvent<RevenantComponent, HarvestEvent>(OnHarvest);
 
@@ -53,11 +53,14 @@ public sealed partial class RevenantSystem
         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))
         {
@@ -78,6 +81,8 @@ public sealed partial class RevenantSystem
         {
             BeginHarvestDoAfter(uid, target, component, essence);
         }
+
+        args.Handled = true;
     }
 
     private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
index 74c42ccbc53503300766f68ee8ea2d355cd2aa2c..e82235e44f5bac56bdb783b1927fab177e23efc8 100644 (file)
@@ -128,15 +128,20 @@ public sealed class ThrusterSystem : EntitySystem
 
     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;
         }
     }
 
index 4376ec4bc61d86e8698b4e768caabc8aaf71faff..caa319a0b71829dadb02ae258b12e3538eb0d5da 100644 (file)
@@ -141,6 +141,9 @@ namespace Content.Server.Tabletop
 
         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;
index a2c13ed71c1fc05c74756fed32b0974f6697bcce..0a745d5fc7da4af9a196713e5bfb0f18b3c6bbd7 100644 (file)
@@ -21,6 +21,7 @@ using Content.Shared.Damage;
 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;
@@ -106,6 +107,7 @@ namespace Content.Server.Zombies
             RemComp<ReproductiveComponent>(target);
             RemComp<ReproductivePartnerComponent>(target);
             RemComp<LegsParalyzedComponent>(target);
+            RemComp<ComplexInteractionComponent>(target);
 
             //funny voice
             var accentType = "zombie";
index 47b3997806d32fd5cb0bccc9028792b3a25a2111..d2883b5ef5be410d942cb7fcaf7dfa5b79c91934 100644 (file)
@@ -96,9 +96,9 @@ namespace Content.Shared.ActionBlocker
         ///     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;
index 326272cc7decd1ecddd6a908cddc80a81c9094ec..45a89f3be9704387d7cc34b2e034eb49dde115c5 100644 (file)
@@ -83,10 +83,11 @@ public sealed class BurialSystem : EntitySystem
 
     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)
index 87957066125eff356f67eec288bd097b14823c8c..c8e8e89ce53bbb1afd32de2957493108301233c6 100644 (file)
@@ -31,7 +31,11 @@ public abstract class SharedSolutionContainerMixerSystem : EntitySystem
 
     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)
index c8783b05fc706d3a5d265eb465473f3ef85d5abb..7e665dc19065271d7f052db2595e0a57b228164c 100644 (file)
@@ -28,7 +28,7 @@ namespace Content.Shared.DeviceLinking.Systems
 
         private void OnActivated(EntityUid uid, TwoWayLeverComponent component, ActivateInWorldEvent args)
         {
-            if (args.Handled)
+            if (args.Handled || !args.Complex)
                 return;
 
             component.State = component.State switch
index 20456c14777be0befd206d36c4e8c76069da4d32..ab2df72568e5346fdcb6b42a1eb765960f743dbf 100644 (file)
@@ -215,7 +215,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
     #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))
index 6d4d332479fdac3332f0ad0ab639027365e4bf6a..ae22efcd6a53bb0618929a8c436bff809a4a1dd0 100644 (file)
@@ -3,6 +3,7 @@ using Content.Shared.Examine;
 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;
@@ -23,6 +24,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
         SubscribeAllEvent<RequestMoveHandItemEvent>(HandleMoveItemFromHand);
         SubscribeAllEvent<RequestHandAltInteractEvent>(HandleHandAltInteract);
 
+        SubscribeLocalEvent<HandsComponent, GetUsedEntityEvent>(OnGetUsedEntity);
         SubscribeLocalEvent<HandsComponent, ExaminedEvent>(HandleExamined);
 
         CommandBinds.Builder
@@ -181,6 +183,18 @@ public abstract partial class SharedHandsSystem : EntitySystem
         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)
     {
index 9dbd636c48fdf2770959ca743be8cacfde0b479a..f7a1b7a799d5dec7a4245d120b9625627bdd8151 100644 (file)
@@ -18,14 +18,49 @@ public sealed class ActivateInWorldEvent : HandledEntityEventArgs, ITargetedInte
     /// </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;
     }
 }
diff --git a/Content.Shared/Interaction/Components/ComplexInteractionComponent.cs b/Content.Shared/Interaction/Components/ComplexInteractionComponent.cs
new file mode 100644 (file)
index 0000000..ae7d65d
--- /dev/null
@@ -0,0 +1,9 @@
+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;
index 3db185ed1729ec27bc4ea881df3cc52f3a8bb70c..c28f2b651774f762ed0cc1679559d635369abfa6 100644 (file)
@@ -1,12 +1,9 @@
 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;
     }
 }
index 63ea3b6f30db23c31b1e506f8c8936d9cfe1c574..1d2df4c28b299c2ea4275a55ef5b62ea1136cf85 100644 (file)
@@ -51,58 +51,4 @@ namespace Content.Shared.Interaction
             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;
-        }
-    }
 }
index 030bc0ae6b8782400dd03b25df57ea15bbe38467..d956ff3d95399a5c0f2335c51eb3647050454cd5 100644 (file)
@@ -32,6 +32,9 @@ public sealed class InteractionPopupSystem : EntitySystem
 
     private void OnActivateInWorld(EntityUid uid, InteractionPopupComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         if (!component.OnActivate)
             return;
 
index bcde27ceba3fec9bd6a1bc5c56338f533eb50505..1e4c49211f566f35d04da554b0e7a9560f880238 100644 (file)
@@ -76,6 +76,7 @@ namespace Content.Shared.Interaction
         private EntityQuery<WallMountComponent> _wallMountQuery;
         private EntityQuery<UseDelayComponent> _delayQuery;
         private EntityQuery<ActivatableUIComponent> _uiQuery;
+        private EntityQuery<ComplexInteractionComponent> _complexInteractionQuery;
 
         private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
 
@@ -98,6 +99,7 @@ namespace Content.Shared.Interaction
             _wallMountQuery = GetEntityQuery<WallMountComponent>();
             _delayQuery = GetEntityQuery<UseDelayComponent>();
             _uiQuery = GetEntityQuery<ActivatableUIComponent>();
+            _complexInteractionQuery = GetEntityQuery<ComplexInteractionComponent>();
 
             SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
             SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
@@ -360,8 +362,13 @@ namespace Content.Shared.Interaction
                 // 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;
                 }
             }
@@ -398,25 +405,10 @@ namespace Content.Shared.Interaction
                 ? !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);
@@ -424,11 +416,7 @@ namespace Content.Shared.Interaction
                 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;
@@ -438,7 +426,7 @@ namespace Content.Shared.Interaction
             {
                 InteractUsing(
                     user,
-                    held,
+                    used.Value,
                     target.Value,
                     coordinates,
                     checkCanInteract: false,
@@ -449,7 +437,7 @@ namespace Content.Shared.Interaction
 
             InteractUsingRanged(
                 user,
-                held,
+                used.Value,
                 target,
                 coordinates,
                 inRangeUnobstructed);
@@ -457,6 +445,18 @@ namespace Content.Shared.Interaction
 
         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);
@@ -475,10 +475,12 @@ namespace Content.Shared.Interaction
                 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,
@@ -921,7 +923,7 @@ namespace Content.Shared.Interaction
             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))
@@ -1001,7 +1003,8 @@ namespace Content.Shared.Interaction
             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)))
@@ -1018,13 +1021,12 @@ namespace Content.Shared.Interaction
             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);
@@ -1059,7 +1061,7 @@ namespace Content.Shared.Interaction
             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);
@@ -1259,6 +1261,39 @@ namespace Content.Shared.Interaction
                     ? 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>
@@ -1284,6 +1319,24 @@ namespace Content.Shared.Interaction
         }
     }
 
+    /// <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.
index a25dcea8a78ed207ba0d2d160f57f16675228802..bf4af1cb9a4e6d36b4ea8fc9cbd2aafc0e73a872 100644 (file)
@@ -62,7 +62,7 @@ public sealed class LockSystem : EntitySystem
 
     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
index 97b5bfeba6f5367f612c80f489707821d315db1d..73b7c0847fc133b3a5229b636e6fe75d723bbb1e 100644 (file)
@@ -43,7 +43,7 @@ public abstract class SharedMechSystem : EntitySystem
     {
         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);
@@ -71,7 +71,7 @@ public abstract class SharedMechSystem : EntitySystem
         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)
index 372dc8a75d19f324631812136b55428b016f67fb..b718c3a690081e0365b32d4a4a1b84e49870720e 100644 (file)
@@ -47,7 +47,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem
         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;
index 636c603834809769e519d8fd5c45c194e9a3c02d..0576e46df4a2b3776508ca6c210a55886f0c0173 100644 (file)
@@ -89,7 +89,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
 
     protected void OnInteract(EntityUid uid, SharedEntityStorageComponent component, ActivateInWorldEvent args)
     {
-        if (args.Handled)
+        if (args.Handled || !args.Complex)
             return;
 
         args.Handled = true;
index 18a8f20afc7e6ad31aa6310bbff8a4118f74051c..ee087901f3ba278caa1f6953c425fc2b30daa9cb 100644 (file)
@@ -364,7 +364,7 @@ public abstract class SharedStorageSystem : EntitySystem
     /// </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
index 075cf81a4cb9b23c01d9cdc11c5a38f81fcdc9d4..38e2f9fd7a52b1ccc576d31d21259870bd6bef75 100644 (file)
@@ -21,7 +21,7 @@ public abstract class SharedStrippableSystem : EntitySystem
 
     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)))
index 6e8393036d47ff62a0cdc1266f440be05da262e3..8903747e430f2d78cf2b924bf8ad0156214f6051 100644 (file)
@@ -26,7 +26,11 @@ public abstract class SharedTrayScannerSystem : EntitySystem
 
     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)
index 62c0b0f44e416c0dea2675d5ef64d0695c89c087..bc73baa61adfa7949425b46e30ece0c81d5efcd9 100644 (file)
@@ -101,6 +101,9 @@ public sealed class SwapTeleporterSystem : EntitySystem
 
     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)
@@ -130,6 +133,7 @@ public sealed class SwapTeleporterSystem : EntitySystem
         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)
index 87df69e88dabf1402c4ce1b84bea6a1b78c0ac90..f11af335527500b3ccf4b971403f81b2d32ca9a0 100644 (file)
@@ -79,7 +79,7 @@ namespace Content.Shared.Toilet.Systems
 
         private void OnActivateInWorld(EntityUid uid, ToiletComponent comp, ActivateInWorldEvent args)
         {
-            if (args.Handled)
+            if (args.Handled || !args.Complex)
                 return;
 
             args.Handled = true;
index 9114c62adeed9b60da66eabd603e92f78756fe79..d69f01d762f62cb345759a079452bef950a15f0f 100644 (file)
@@ -28,7 +28,7 @@ public abstract partial class SharedToolSystem
 
     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);
index c1822c4ee3352230d1568ca6b4d9c9d531939fe8..1bb11f337f65533855b88642b6e285f5d6be382a 100644 (file)
@@ -133,7 +133,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
 
     private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
     {
-        if (args.Handled)
+        if (args.Handled || !args.Complex)
             return;
 
         if (component.VerbOnly)
index e78fe98f4c3ce8081e5b2ec008f86320bc6688a6..319f927c7b3d415f636b3b16106b28ac5e4cb1bf 100644 (file)
@@ -78,28 +78,8 @@ namespace Content.Shared.Verbs
             // 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)))
index 41e1895da2c963951f011f90778849b3c5f5f5ed..6feffffbe3181ae44f0ad3d850ba6c3d8d3ef351 100644 (file)
@@ -116,7 +116,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
 
     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))
index eec115b02dfbdb8d5d433d24afa8f5b968fc3aee..932ef704607b97e3ea9f413a42c9630ffc0a75f3 100644 (file)
@@ -14,6 +14,9 @@ public abstract partial class SharedTetherGunSystem
 
     private void OnForceActivate(EntityUid uid, ForceGunComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         StopTether(uid, component);
     }
 
index ad2249bfddf6fd67af4ef5a95eb08db4bd68e5a0..3d7f9df458ef98a3ebdf1679127f7737aa54c4a3 100644 (file)
@@ -152,6 +152,9 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
 
     private void OnTetherActivate(EntityUid uid, TetherGunComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         StopTether(uid, component);
     }
 
index 68fb2f27c98e1a3c954efefa865b1e7cfae3c1e3..8e44576d28345807c756f086fa19ca7af583d248 100644 (file)
@@ -75,6 +75,9 @@ public sealed class BatteryWeaponFireModesSystem : EntitySystem
 
     private void OnInteractHandEvent(EntityUid uid, BatteryWeaponFireModesComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         if (component.FireModes.Count < 2)
             return;
 
index a014f8e5c744a80b066d6788a34e61cde00b9d47..ee5ca2174f36cba4146f9cf82462386a0b2d0e1f 100644 (file)
@@ -18,6 +18,9 @@ public sealed class RechargeCycleAmmoSystem : EntitySystem
 
     private void OnRechargeCycled(EntityUid uid, RechargeCycleAmmoComponent component, ActivateInWorldEvent args)
     {
+        if (!args.Complex)
+            return;
+
         if (!TryComp<BasicEntityAmmoProviderComponent>(uid, out var basic) || args.Handled)
             return;
 
index c421c92a9f7512dd1eec93bb335bb0a26ece139b..adae26a223a1429fbb6658fab073f46bb3f3d147 100644 (file)
@@ -51,7 +51,7 @@ public abstract partial class SharedGunSystem
     /// </summary>
     private void OnChamberActivate(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ActivateInWorldEvent args)
     {
-        if (args.Handled)
+        if (args.Handled || !args.Complex)
             return;
 
         args.Handled = true;
index 71f5596ccfd0c40f761048125dc59ad8d79c7866..8bf55e48bd3e5142f8b407833e8d1f9385372cab 100644 (file)
@@ -84,6 +84,7 @@
   - type: Hands
     showInHands: false
     disableExplosionRecursion: true
+  - type: ComplexInteraction
   - type: IntrinsicRadioReceiver
   - type: IntrinsicRadioTransmitter
     channels:
index a79d96065a6dc67fec6ab2f8ce4bf5ba540cd65b..b5f69e6f56b61e1c0de8dfb8c78ba5e655328e79 100644 (file)
       state: "creampie_human"
       visible: false
   - type: Hands
+  - type: ComplexInteraction
   - type: GenericVisualizer
     visuals:
       enum.CreamPiedVisuals.Creamed:
index 657ac466f846609cf1241b2e24b10f0495182ef3..285e75bca0a84a45d78da3b9776af5477c47b2e3 100644 (file)
@@ -12,7 +12,6 @@
   - type: NpcFactionMember
     factions:
     - SimpleHostile
-  - type: Hands
   - type: Sprite
     drawdepth: Mobs
     sprite: Structures/Machines/VendingMachines/cola.rsi
index de3a282eeb6e4fb6474dc42382e4b6738fa46165..a3d4eafacbbca729b34b12a7478c851a5f439beb 100644 (file)
@@ -40,6 +40,7 @@
     factions:
     - Xeno
   - type: Hands
+  - type: ComplexInteraction
   - type: Sprite
     drawdepth: Mobs
     sprite: Mobs/Aliens/Xenos/burrower.rsi
index b294729e07a46297a053291e2803f4cd4ea12f06..0fa85fc84f212a35bcbfc0a89ea5769358b5efc0 100644 (file)
@@ -18,6 +18,7 @@
     canInteract: true
   - type: GhostHearing
   - type: Hands
+  - type: ComplexInteraction
   - type: Puller
   - type: CombatMode
   - type: Physics
index c7b4464350e8d4211fb51b1dd2c2d8ddcf513a0d..80ee2c55b88aad3f7350d58272f29c6441557671 100644 (file)
     - type: Inventory
       templateId: holoclown
     - type: Hands
+    - type: ComplexInteraction
     - type: Clumsy
       clumsyDamage:
         types:
index 01b938ffa863d25583fe3150e57332f0fdc945d9..a49f8ff34c351abb1ce1318edf38f28d9d8e40fd 100644 (file)
   - 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