From bfd95c493b24774fa23f3935376687ce04d0ea4b Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 17 Mar 2022 20:13:31 +1300 Subject: [PATCH] hands ECS (#7081) --- .../EscapeMenu/UI/Tabs/KeyRebindTab.xaml.cs | 4 +- Content.Client/Hands/Systems/HandsSystem.cs | 92 +- Content.Client/Input/ContentContexts.cs | 4 +- .../Inventory/ClientInventorySystem.cs | 10 +- .../Weapons/Ranged/RangedWeaponSystem.cs | 2 +- .../Tests/Buckle/BuckleTest.cs | 13 +- .../Components/ActionBlocking/HandCuffTest.cs | 2 +- .../Click/InteractionSystemTests.cs | 41 +- .../Tests/PDA/PDAExtensionsTests.cs | 133 --- .../Combat/Melee/SwingMeleeWeaponOperator.cs | 4 +- .../Operators/Inventory/DropEntityOperator.cs | 10 +- .../Inventory/DropHandItemsOperator.cs | 10 +- .../Inventory/EquipEntityOperator.cs | 18 +- .../Inventory/PickupEntityOperator.cs | 35 +- .../Inventory/UseItemInInventoryOperator.cs | 22 +- .../Nutrition/UseDrinkInInventoryOperator.cs | 35 +- .../Nutrition/UseFoodInInventoryOperator.cs | 30 +- .../Considerations/Hands/FreeHandCon.cs | 21 +- .../States/Hands/AnyFreeHandState.cs | 16 +- .../AI/WorldState/States/Hands/FreeHands.cs | 17 +- .../WorldState/States/Hands/HandItemsState.cs | 24 +- .../States/Inventory/EquippedEntityState.cs | 2 +- .../States/Inventory/InventoryState.cs | 17 +- .../AME/Components/AMEControllerComponent.cs | 13 +- Content.Server/Access/Systems/IdCardSystem.cs | 4 +- .../Atmos/Components/GasAnalyzerComponent.cs | 4 +- .../Unary/EntitySystems/GasCanisterSystem.cs | 15 +- .../Botany/Components/PlantHolderComponent.cs | 2 +- .../Chat/Commands/SuicideCommand.cs | 4 +- .../Components/ChemMasterComponent.cs | 44 +- .../CommunicationsConsoleComponent.cs | 10 +- .../ConstructionSystem.Initial.cs | 23 +- .../Cuffs/Components/CuffableComponent.cs | 39 +- Content.Server/Cuffs/CuffableSystem.cs | 2 +- .../Components/DisposalTaggerComponent.cs | 1 + .../Disposal/Tube/DisposalTubeSystem.cs | 6 +- .../Unit/EntitySystems/DisposalUnitSystem.cs | 14 +- Content.Server/DoAfter/DoAfter.cs | 12 +- Content.Server/Drone/DroneSystem.cs | 21 +- .../DisassembleOnAltVerbSystem.cs | 11 +- .../GameTicking/GameTicker.Spawning.cs | 17 +- Content.Server/Guardian/GuardianSystem.cs | 5 +- .../Hands/Components/HandsComponent.cs | 108 +-- .../Hands/Systems/HandVirtualItemSystem.cs | 42 +- Content.Server/Hands/Systems/HandsSystem.cs | 100 +- .../Interaction/InteractionSystem.cs | 2 +- .../Jobs/GiveItemOnHolidaySpecial.cs | 15 +- .../Kitchen/Components/MicrowaveComponent.cs | 2 +- .../EntitySystems/ReagentGrinderSystem.cs | 10 +- .../Light/EntitySystems/PoweredLightSystem.cs | 12 +- .../System/SignalLinkerSystem.cs | 4 +- .../Nutrition/EntitySystems/FoodSystem.cs | 18 +- .../EntitySystems/SliceableFoodSystem.cs | 10 +- Content.Server/PDA/PDAExtensions.cs | 82 -- .../PneumaticCannon/PneumaticCannonSystem.cs | 13 +- Content.Server/Sandbox/SandboxSystem.cs | 4 +- Content.Server/Stack/StackSystem.cs | 7 +- .../Standing/StandingStateSystem.cs | 20 +- .../Components/ServerStorageComponent.cs | 27 +- .../EntitySystems/SecretStashSystem.cs | 13 +- .../EntitySystems/SpawnItemsOnUseSystem.cs | 11 +- Content.Server/Strip/StrippableComponent.cs | 90 +- Content.Server/Strip/StrippableSystem.cs | 10 +- Content.Server/Traitor/Uplink/UplinkSystem.cs | 21 +- Content.Server/Verbs/VerbSystem.cs | 2 +- .../Weapon/Ranged/GunSystem.AmmoBox.cs | 11 +- .../Weapon/Ranged/GunSystem.Guns.cs | 2 +- .../Weapon/Ranged/GunSystem.Magazine.cs | 11 +- .../Weapon/Ranged/GunSystem.RangedMagazine.cs | 12 +- .../Weapon/Ranged/GunSystem.SpeedLoader.cs | 7 +- Content.Server/Weapon/Ranged/GunSystem.cs | 4 +- Content.Server/Wieldable/WieldableSystem.cs | 12 +- Content.Server/WireHacking/WiresComponent.cs | 2 +- .../Effects/Systems/SpawnArtifactSystem.cs | 11 +- .../Access/Systems/AccessReaderSystem.cs | 18 +- .../ActionBlocker/ActionBlockerSystem.cs | 14 +- .../Containers/ItemSlot/ItemSlotsSystem.cs | 72 +- .../Hands/Components/HandHelpers.cs | 34 + .../Hands/Components/SharedHandsComponent.cs | 874 ++---------------- .../EntitySystems/SharedHandsSystem.AI.cs | 39 + .../EntitySystems/SharedHandsSystem.Drop.cs | 159 ++++ .../SharedHandsSystem.Interactions.cs | 172 ++++ .../EntitySystems/SharedHandsSystem.Pickup.cs | 161 ++++ .../Hands/EntitySystems/SharedHandsSystem.cs | 209 +++++ Content.Shared/Hands/HandEvents.cs | 81 +- Content.Shared/Hands/SharedHandsSystem.cs | 196 ---- Content.Shared/Input/ContentKeyFunctions.cs | 4 +- .../Interaction/SharedInteractionSystem.cs | 10 +- .../Inventory/InventorySystem.Equip.cs | 12 +- Content.Shared/Item/PickupAttemptEvent.cs | 43 +- Content.Shared/Item/SharedItemSystem.cs | 18 +- .../Placeable/PlaceableSurfaceSystem.cs | 15 +- Content.Shared/Verbs/SharedVerbSystem.cs | 2 +- RobustToolbox | 2 +- 94 files changed, 1454 insertions(+), 2185 deletions(-) delete mode 100644 Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs delete mode 100644 Content.Server/PDA/PDAExtensions.cs create mode 100644 Content.Shared/Hands/Components/HandHelpers.cs create mode 100644 Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs create mode 100644 Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs create mode 100644 Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs create mode 100644 Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs create mode 100644 Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs delete mode 100644 Content.Shared/Hands/SharedHandsSystem.cs diff --git a/Content.Client/EscapeMenu/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/EscapeMenu/UI/Tabs/KeyRebindTab.xaml.cs index 073ddb6549..ed8d0eb895 100644 --- a/Content.Client/EscapeMenu/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/EscapeMenu/UI/Tabs/KeyRebindTab.xaml.cs @@ -108,8 +108,8 @@ namespace Content.Client.EscapeMenu.UI.Tabs AddHeader("ui-options-header-interaction-basic"); AddButton(EngineKeyFunctions.Use); AddButton(ContentKeyFunctions.WideAttack); - AddButton(ContentKeyFunctions.ActivateItemInHand); - AddButton(ContentKeyFunctions.AltActivateItemInHand); + AddButton(ContentKeyFunctions.UseItemInHand); + AddButton(ContentKeyFunctions.AltUseItemInHand); AddButton(ContentKeyFunctions.ActivateItemInWorld); AddButton(ContentKeyFunctions.AltActivateItemInWorld); AddButton(ContentKeyFunctions.Drop); diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index 97d7f77adf..1880f838bb 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -4,17 +4,14 @@ using Content.Client.Animations; using Content.Client.HUD; using Content.Shared.Hands; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Timing; namespace Content.Client.Hands @@ -25,11 +22,15 @@ namespace Content.Client.Hands [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(HandleContainerModified); + SubscribeLocalEvent(HandleContainerModified); + SubscribeLocalEvent(HandlePlayerAttached); SubscribeLocalEvent(HandlePlayerDetached); SubscribeLocalEvent(HandleCompRemove); @@ -45,46 +46,29 @@ namespace Content.Client.Hands if (args.Current is not HandsComponentState state) return; - // Do we have a NEW hand? var handsModified = component.Hands.Count != state.Hands.Count; - if (!handsModified) + var manager = EnsureComp(uid); + foreach (var hand in state.Hands) { - for (var i = 0; i < state.Hands.Count; i++) + if (component.Hands.TryAdd(hand.Name, hand)) { - if (component.Hands[i].Name != state.Hands[i].Name || - component.Hands[i].Location != state.Hands[i].Location) - { - handsModified = true; - break; - } + hand.Container = _containerSystem.EnsureContainer(uid, hand.Name, manager); + handsModified = true; } } if (handsModified) { - // we have new hands, get the new containers. - component.Hands = state.Hands; - UpdateHandContainers(uid, component); + foreach (var name in component.Hands.Keys) + { + if (!state.HandNames.Contains(name)) + component.Hands.Remove(name); + } + + component.SortedHands = new(state.HandNames); } TrySetActiveHand(uid, state.ActiveHand, component); - } - - /// - /// Used to update the hand-containers when hands have been added or removed. Also updates the GUI - /// - public void UpdateHandContainers(EntityUid uid, HandsComponent? hands = null, ContainerManagerComponent? containerMan = null) - { - if (!Resolve(uid, ref hands, ref containerMan)) - return; - - foreach (var hand in hands.Hands) - { - if (hand.Container == null) - { - hand.Container = hands.Owner.EnsureContainer(hand.Name); - } - } if (uid == _playerManager.LocalPlayer?.ControlledEntity) UpdateGui(); @@ -117,9 +101,7 @@ namespace Content.Client.Hands public EntityUid? GetActiveHandEntity() { - return TryGetPlayerHands(out var hands) && hands.TryGetActiveHeldEntity(out var entity) - ? entity - : null; + return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null; } /// @@ -137,56 +119,57 @@ namespace Content.Client.Hands /// public void UIHandClick(HandsComponent hands, string handName) { - if (!hands.TryGetHand(handName, out var pressedHand)) + if (!hands.Hands.TryGetValue(handName, out var pressedHand)) return; - if (!hands.TryGetActiveHand(out var activeHand)) + if (hands.ActiveHand == null) return; var pressedEntity = pressedHand.HeldEntity; - var activeEntity = activeHand.HeldEntity; + var activeEntity = hands.ActiveHand.HeldEntity; - if (pressedHand == activeHand && activeEntity != null) + if (pressedHand == hands.ActiveHand && activeEntity != null) { // use item in hand // it will always be attack_self() in my heart. - RaiseNetworkEvent(new UseInHandMsg()); + EntityManager.RaisePredictiveEvent(new RequestUseInHandEvent()); return; } - if (pressedHand != activeHand && pressedEntity == null) + if (pressedHand != hands.ActiveHand && pressedEntity == null) { // change active hand EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName)); return; } - if (pressedHand != activeHand && pressedEntity != null && activeEntity != null) + if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null) { // use active item on held item - RaiseNetworkEvent(new ClientInteractUsingInHandMsg(pressedHand.Name)); + EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name)); return; } - if (pressedHand != activeHand && pressedEntity != default && activeEntity == default) + if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null) { - // use active item on held item - RaiseNetworkEvent(new MoveItemFromHandMsg(pressedHand.Name)); + // move the item to the active hand + EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name)); } } /// - /// Called when a user clicks on an item in their hands GUI. + /// Called when a user clicks on the little "activation" icon in the hands GUI. This is currently only used + /// by storage (backpacks, etc). /// public void UIHandActivate(string handName) { - RaiseNetworkEvent(new ActivateInHandMsg(handName)); + EntityManager.RaisePredictiveEvent(new RequestActivateInHandEvent(handName)); } #region visuals - protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args) + private void HandleContainerModified(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args) { - if (handComp.TryGetHand(args.Container.ID, out var hand)) + if (handComp.Hands.TryGetValue(args.Container.ID, out var hand)) { UpdateHandVisuals(uid, args.Entity, hand); } @@ -267,25 +250,24 @@ namespace Content.Client.Hands private void OnVisualsChanged(EntityUid uid, HandsComponent component, VisualsChangedEvent args) { // update hands visuals if this item is in a hand (rather then inventory or other container). - if (component.TryGetHand(args.ContainerId, out var hand)) + if (component.Hands.TryGetValue(args.ContainerId, out var hand)) { UpdateHandVisuals(uid, args.Item, hand, component); } } #endregion - #region Gui public void UpdateGui(HandsComponent? hands = null) { if (hands == null && !TryGetPlayerHands(out hands) || hands.Gui == null) return; - var states = hands.Hands + var states = hands.Hands.Values .Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity)) .ToArray(); - hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand)); + hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand?.Name)); } public override bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null) diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index c5d5d5b849..1ee20b43ff 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -32,8 +32,8 @@ namespace Content.Client.Input var human = contexts.GetContext("human"); human.AddFunction(ContentKeyFunctions.SwapHands); human.AddFunction(ContentKeyFunctions.Drop); - human.AddFunction(ContentKeyFunctions.ActivateItemInHand); - human.AddFunction(ContentKeyFunctions.AltActivateItemInHand); + human.AddFunction(ContentKeyFunctions.UseItemInHand); + human.AddFunction(ContentKeyFunctions.AltUseItemInHand); human.AddFunction(ContentKeyFunctions.OpenCharacterMenu); human.AddFunction(ContentKeyFunctions.ActivateItemInWorld); human.AddFunction(ContentKeyFunctions.ThrowItemInHand); diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs index 2ce5fadbef..ce9aae6ca4 100644 --- a/Content.Client/Inventory/ClientInventorySystem.cs +++ b/Content.Client/Inventory/ClientInventorySystem.cs @@ -196,15 +196,15 @@ namespace Content.Client.Inventory if (!Resolve(uid, ref hands, false)) return; - if (!hands.TryGetActiveHeldEntity(out var heldEntity)) + if (hands.ActiveHandEntity is not EntityUid heldEntity) return; if(!TryGetSlotContainer(uid, slot, out var containerSlot, out var slotDef, inventoryComponent)) return; - _itemSlotManager.HoverInSlot(button, heldEntity.Value, - CanEquip(uid, heldEntity.Value, slot, out _, slotDef, inventoryComponent) && - containerSlot.CanInsert(heldEntity.Value, EntityManager)); + _itemSlotManager.HoverInSlot(button, heldEntity, + CanEquip(uid, heldEntity, slot, out _, slotDef, inventoryComponent) && + containerSlot.CanInsert(heldEntity, EntityManager)); } private void HandleSlotButtonPressed(EntityUid uid, string slot, ItemSlotButton button, @@ -217,7 +217,7 @@ namespace Content.Client.Inventory return; // only raise event if either itemUid is not null, or the user is holding something - if (itemUid != null || TryComp(uid, out SharedHandsComponent? hands) && hands.TryGetActiveHeldEntity(out _)) + if (itemUid != null || TryComp(uid, out SharedHandsComponent? hands) && hands.ActiveHandEntity != null) EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot)); } diff --git a/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs b/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs index 7e0c27fa48..1484aa7872 100644 --- a/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs +++ b/Content.Client/Weapons/Ranged/RangedWeaponSystem.cs @@ -59,7 +59,7 @@ namespace Content.Client.Weapons.Ranged return; } - if (!hands.TryGetActiveHeldEntity(out var held) || !EntityManager.TryGetComponent(held, out ClientRangedWeaponComponent? weapon)) + if (hands.ActiveHandEntity is not EntityUid held || !EntityManager.TryGetComponent(held, out ClientRangedWeaponComponent? weapon)) { _blocked = true; return; diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 55a4ef2715..731269000a 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -5,6 +5,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Body.Components; using Content.Shared.Body.Part; using Content.Shared.Buckle.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; using Content.Shared.Standing; using NUnit.Framework; @@ -251,9 +252,7 @@ namespace Content.IntegrationTests.Tests.Buckle { var akms = entityManager.SpawnEntity(ItemDummyId, coordinates); - // Equip items - Assert.True(entityManager.TryGetComponent(akms, out SharedItemComponent item)); - Assert.True(hands.PutInHand(item)); + Assert.True(EntitySystem.Get().TryPickupAnyHand(human, akms)); } }); @@ -265,9 +264,9 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(buckle.Buckled); // With items in all hands - foreach (var slot in hands.HandNames) + foreach (var hand in hands.Hands.Values) { - Assert.NotNull(hands.GetItem(slot)); + Assert.NotNull(hands.ActiveHandEntity); } var legs = body.GetPartsOfType(BodyPartType.Leg); @@ -287,9 +286,9 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.True(buckle.Buckled); // Now with no item in any hand - foreach (var slot in hands.HandNames) + foreach (var hand in hands.Hands.Values) { - Assert.Null(hands.GetItem(slot)); + Assert.Null(hands.ActiveHandEntity); } buckle.TryUnbuckle(human, true); diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs index fe196ef9a6..ee60615c71 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/ActionBlocking/HandCuffTest.cs @@ -81,7 +81,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking AddHand(cuffed.Owner); Assert.That(cuffed.CuffedHandCount, Is.EqualTo(2)); - Assert.That(hands.HandNames.Count(), Is.EqualTo(4)); + Assert.That(hands.SortedHands.Count(), Is.EqualTo(4)); // Test to give a player with 4 hands 2 sets of cuffs cuffed.TryAddNewCuffs(human, secondCuffs); diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index d88820d4d1..8a682264c4 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -4,6 +4,7 @@ using Content.Client.Items.Components; using Content.Server.Hands.Components; using Content.Server.Interaction; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Weapons.Melee; @@ -54,6 +55,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sEntities = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + var sysMan = server.ResolveDependency(); + var handSys = sysMan.GetEntitySystem(); var mapId = MapId.Nullspace; var coords = MapCoordinates.Nullspace; @@ -71,7 +74,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click server.Assert(() => { user = sEntities.SpawnEntity(null, coords); - user.EnsureComponent().AddHand("hand", HandLocation.Left); + user.EnsureComponent(); + handSys.AddHand(user, "hand", HandLocation.Left); target = sEntities.SpawnEntity(null, coords); item = sEntities.SpawnEntity(null, coords); item.EnsureComponent(); @@ -98,8 +102,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click Assert.That(interactUsing, Is.False); Assert.That(interactHand); - Assert.That(sEntities.TryGetComponent(user, out var hands)); - Assert.That(hands.PutInHand(sEntities.GetComponent(item))); + Assert.That(handSys.TryPickup(user, item)); interactionSystem.UserInteraction(user, sEntities.GetComponent(target).Coordinates, target); Assert.That(interactUsing); @@ -124,6 +127,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sEntities = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + var sysMan = server.ResolveDependency(); + var handSys = sysMan.GetEntitySystem(); var mapId = MapId.Nullspace; var coords = MapCoordinates.Nullspace; @@ -142,7 +147,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click server.Assert(() => { user = sEntities.SpawnEntity(null, coords); - user.EnsureComponent().AddHand("hand", HandLocation.Left); + user.EnsureComponent(); + handSys.AddHand(user, "hand", HandLocation.Left); target = sEntities.SpawnEntity(null, new MapCoordinates((1.9f, 0), mapId)); item = sEntities.SpawnEntity(null, coords); item.EnsureComponent(); @@ -170,8 +176,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click Assert.That(interactUsing, Is.False); Assert.That(interactHand, Is.False); - Assert.That(sEntities.TryGetComponent(user, out var hands)); - Assert.That(hands.PutInHand(sEntities.GetComponent(item))); + Assert.That(handSys.TryPickup(user, item)); interactionSystem.UserInteraction(user, sEntities.GetComponent(target).Coordinates, target); Assert.That(interactUsing, Is.False); @@ -195,6 +200,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sEntities = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + var sysMan = server.ResolveDependency(); + var handSys = sysMan.GetEntitySystem(); var mapId = MapId.Nullspace; var coords = MapCoordinates.Nullspace; @@ -212,7 +219,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click server.Assert(() => { user = sEntities.SpawnEntity(null, coords); - user.EnsureComponent().AddHand("hand", HandLocation.Left); + user.EnsureComponent(); + handSys.AddHand(user, "hand", HandLocation.Left); target = sEntities.SpawnEntity(null, new MapCoordinates((InteractionSystem.InteractionRange - 0.1f, 0), mapId)); item = sEntities.SpawnEntity(null, coords); item.EnsureComponent(); @@ -239,8 +247,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click Assert.That(interactUsing, Is.False); Assert.That(interactHand); - Assert.That(sEntities.TryGetComponent(user, out var hands)); - Assert.That(hands.PutInHand(sEntities.GetComponent(item))); + Assert.That(handSys.TryPickup(user, item)); interactionSystem.UserInteraction(user, sEntities.GetComponent(target).Coordinates, target); Assert.That(interactUsing); @@ -265,6 +272,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sEntities = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + var sysMan = server.ResolveDependency(); + var handSys = sysMan.GetEntitySystem(); var mapId = MapId.Nullspace; var coords = MapCoordinates.Nullspace; @@ -282,7 +291,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click server.Assert(() => { user = sEntities.SpawnEntity(null, coords); - user.EnsureComponent().AddHand("hand", HandLocation.Left); + user.EnsureComponent(); + handSys.AddHand(user, "hand", HandLocation.Left); target = sEntities.SpawnEntity(null, new MapCoordinates((SharedInteractionSystem.InteractionRange + 0.01f, 0), mapId)); item = sEntities.SpawnEntity(null, coords); item.EnsureComponent(); @@ -309,8 +319,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click Assert.That(interactUsing, Is.False); Assert.That(interactHand, Is.False); - Assert.That(sEntities.TryGetComponent(user, out var hands)); - Assert.That(hands.PutInHand(sEntities.GetComponent(item))); + Assert.That(handSys.TryPickup(user, item)); interactionSystem.UserInteraction(user, sEntities.GetComponent(target).Coordinates, target); Assert.That(interactUsing, Is.False); @@ -335,6 +344,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click var sEntities = server.ResolveDependency(); var mapManager = server.ResolveDependency(); + var sysMan = server.ResolveDependency(); + var handSys = sysMan.GetEntitySystem(); var mapId = MapId.Nullspace; var coords = MapCoordinates.Nullspace; @@ -354,7 +365,8 @@ namespace Content.IntegrationTests.Tests.Interaction.Click server.Assert(() => { user = sEntities.SpawnEntity(null, coords); - user.EnsureComponent().AddHand("hand", HandLocation.Left); + user.EnsureComponent(); + handSys.AddHand(user, "hand", HandLocation.Left); target = sEntities.SpawnEntity(null, coords); item = sEntities.SpawnEntity(null, coords); item.EnsureComponent(); @@ -394,8 +406,7 @@ namespace Content.IntegrationTests.Tests.Interaction.Click Assert.That(interactUsing, Is.False); Assert.That(interactHand); - Assert.That(sEntities.TryGetComponent(user, out var hands)); - Assert.That(hands.PutInHand(sEntities.GetComponent(item))); + Assert.That(handSys.TryPickup(user, item)); interactionSystem.UserInteraction(user, sEntities.GetComponent(target).Coordinates, target); Assert.That(interactUsing, Is.False); diff --git a/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs b/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs deleted file mode 100644 index 6aa051b59b..0000000000 --- a/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Content.Server.Hands.Components; -using Content.Server.PDA; -using Content.Shared.Access.Components; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.Inventory; -using Content.Shared.Item; -using Content.Shared.PDA; -using NUnit.Framework; -using Robust.Server.Player; -using Robust.Shared.GameObjects; - -namespace Content.IntegrationTests.Tests.PDA -{ - public sealed class PDAExtensionsTests : ContentIntegrationTest - { - private const string IdCardDummy = "DummyIdCard"; - private const string PdaDummy = "DummyPda"; - - private static readonly string Prototypes = $@" -- type: entity - id: {IdCardDummy} - name: {IdCardDummy} - components: - - type: IdCard - - type: Item - -- type: entity - id: {PdaDummy} - name: {PdaDummy} - components: - - type: PDA - idSlot: - name: ID Card - whitelist: - components: - - IdCard - - type: Item"; - - [Test] - public async Task PlayerGetIdComponent() - { - var clientOptions = new ClientIntegrationOptions - { - ExtraPrototypes = Prototypes - }; - - var serverOptions = new ServerIntegrationOptions - { - ExtraPrototypes = Prototypes - }; - - var (client, server) = await StartConnectedServerClientPair(clientOptions, serverOptions); - - await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()); - - var sPlayerManager = server.ResolveDependency(); - var sEntityManager = server.ResolveDependency(); - - var invSystem = server.ResolveDependency().GetEntitySystem(); - - await server.WaitAssertion(() => - { - var player = sPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault(); - - Assert.That(player != default); - - // The player spawns with an ID on by default - Assert.NotNull(player.GetHeldId()); - Assert.True(player.TryGetHeldId(out var id)); - Assert.NotNull(id); - - // Put PDA in hand - var dummyPda = sEntityManager.SpawnEntity(PdaDummy, sEntityManager.GetComponent(player).MapPosition); - var pdaItemComponent = sEntityManager.GetComponent(dummyPda); - sEntityManager.GetComponent(player).PutInHand(pdaItemComponent); - - var pdaComponent = sEntityManager.GetComponent(dummyPda); - var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, sEntityManager.GetComponent(player).MapPosition); - - var itemSlots = sEntityManager.GetComponent(dummyPda); - sEntityManager.EntitySysManager.GetEntitySystem() - .TryInsert(dummyPda, pdaComponent.IdSlot, pdaIdCard, null); - var pdaContainedId = pdaComponent.ContainedID; - - // The PDA in the hand should be found first - Assert.NotNull(player.GetHeldId()); - Assert.True(player.TryGetHeldId(out id)); - - Assert.NotNull(id); - Assert.That(id, Is.EqualTo(pdaContainedId)); - - // Put ID card in hand - var idDummy = sEntityManager.SpawnEntity(IdCardDummy, sEntityManager.GetComponent(player).MapPosition); - var idItemComponent = sEntityManager.GetComponent(idDummy); - sEntityManager.GetComponent(player).PutInHand(idItemComponent); - - var idCardComponent = sEntityManager.GetComponent(idDummy); - - // The ID in the hand should be found first - Assert.NotNull(player.GetHeldId()); - Assert.True(player.TryGetHeldId(out id)); - Assert.NotNull(id); - Assert.That(id, Is.EqualTo(idCardComponent)); - - // Remove all IDs and PDAs - Assert.That(invSystem.TryGetSlots(player, out var slots)); - - foreach (var slot in slots) - { - if(!invSystem.TryGetSlotEntity(player, slot.Name, out var item)) - continue; - - if (sEntityManager.HasComponent(item)) - { - invSystem.TryUnequip(player, slot.Name, force: true); - } - } - - var hands = sEntityManager.GetComponent(player); - - hands.Drop(dummyPda, false); - hands.Drop(idDummy, false); - - // No ID - Assert.Null(player.GetHeldId()); - Assert.False(player.TryGetHeldId(out id)); - Assert.Null(id); - }); - } - } -} diff --git a/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs b/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs index e359c7d1b0..c4b3ae0de2 100644 --- a/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs +++ b/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs @@ -66,12 +66,12 @@ namespace Content.Server.AI.Operators.Combat.Melee return Outcome.Success; } - if (!_entMan.TryGetComponent(_owner, out HandsComponent? hands) || hands.GetActiveHandItem == null) + if (!_entMan.TryGetComponent(_owner, out HandsComponent? hands) || hands.ActiveHandEntity == null) { return Outcome.Failed; } - var meleeWeapon = hands.GetActiveHandItem.Owner; + var meleeWeapon = hands.ActiveHandEntity; _entMan.TryGetComponent(meleeWeapon, out MeleeWeaponComponent? meleeWeaponComponent); if ((_entMan.GetComponent(_target).Coordinates.Position - _entMan.GetComponent(_owner).Coordinates.Position).Length > diff --git a/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs b/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs index 285e20e77d..8511fc3d69 100644 --- a/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs @@ -1,6 +1,5 @@ using Content.Server.Hands.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; +using Content.Shared.Hands.EntitySystems; namespace Content.Server.AI.Operators.Inventory { @@ -21,12 +20,7 @@ namespace Content.Server.AI.Operators.Inventory /// public override Outcome Execute(float frameTime) { - if (!IoCManager.Resolve().TryGetComponent(_owner, out HandsComponent? handsComponent)) - { - return Outcome.Failed; - } - - return handsComponent.Drop(_entity) ? Outcome.Success : Outcome.Failed; + return IoCManager.Resolve().GetEntitySystem().TryDrop(_owner, _entity) ? Outcome.Success : Outcome.Failed; } } } diff --git a/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs b/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs index e1001712fc..18a62b4453 100644 --- a/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs +++ b/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs @@ -1,6 +1,5 @@ using Content.Server.Hands.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; +using Content.Shared.Hands.EntitySystems; namespace Content.Server.AI.Operators.Inventory { @@ -20,9 +19,12 @@ namespace Content.Server.AI.Operators.Inventory return Outcome.Failed; } - foreach (var item in handsComponent.GetAllHeldItems()) + var sys = IoCManager.Resolve().GetEntitySystem(); + + foreach (var hand in handsComponent.Hands.Values) { - handsComponent.Drop(item.Owner); + if (!hand.IsEmpty) + sys.TryDrop(_owner, hand, handsComp: handsComponent); } return Outcome.Success; diff --git a/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs b/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs index e2f3ae82fa..0c66fdab9e 100644 --- a/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs @@ -1,4 +1,5 @@ using Content.Server.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -16,19 +17,12 @@ namespace Content.Server.AI.Operators.Inventory public override Outcome Execute(float frameTime) { - if (!IoCManager.Resolve().TryGetComponent(_owner, out HandsComponent? handsComponent)) - { - return Outcome.Failed; - } + var sys = IoCManager.Resolve().GetEntitySystem(); + // TODO: If in clothing then click on it - foreach (var hand in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(hand)?.Owner == _entity) - { - handsComponent.ActiveHand = hand; - return Outcome.Success; - } - } + + if (sys.TrySelect(_owner, _entity)) + return Outcome.Success; // TODO: Get free hand count; if no hands free then fail right here diff --git a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs index f5c7c101f6..22454d929d 100644 --- a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs @@ -1,5 +1,6 @@ using Content.Server.Hands.Components; using Content.Server.Interaction; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Item; @@ -24,42 +25,22 @@ namespace Content.Server.AI.Operators.Inventory public override Outcome Execute(float frameTime) { var entMan = IoCManager.Resolve(); + var sysMan = IoCManager.Resolve(); + var interactionSystem = sysMan.GetEntitySystem(); + var handsSys = sysMan.GetEntitySystem(); if (entMan.Deleted(_target) || !entMan.HasComponent(_target) || _target.IsInContainer() - || !EntitySystem.Get().InRangeUnobstructed(_owner, _target, popup: true)) + || !interactionSystem.InRangeUnobstructed(_owner, _target, popup: true)) { return Outcome.Failed; } - if (!entMan.TryGetComponent(_owner, out HandsComponent? handsComponent)) - { + // select empty hand + if (!handsSys.TrySelectEmptyHand(_owner)) return Outcome.Failed; - } - - var emptyHands = false; - - foreach (var hand in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(hand) == null) - { - if (handsComponent.ActiveHand != hand) - { - handsComponent.ActiveHand = hand; - } - - emptyHands = true; - break; - } - } - - if (!emptyHands) - { - return Outcome.Failed; - } - - var interactionSystem = EntitySystem.Get(); + interactionSystem.InteractHand(_owner, _target); return Outcome.Success; } diff --git a/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs index 2088fbe31e..0ec171ce09 100644 --- a/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs @@ -1,4 +1,5 @@ using Content.Server.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -22,27 +23,18 @@ namespace Content.Server.AI.Operators.Inventory public override Outcome Execute(float frameTime) { var entMan = IoCManager.Resolve(); + var sysMan = IoCManager.Resolve(); + var sys = sysMan.GetEntitySystem(); // TODO: Also have this check storage a la backpack etc. - if (!entMan.TryGetComponent(_owner, out HandsComponent? handsComponent)) + if (!entMan.TryGetComponent(_owner, out HandsComponent? handsComponent) + || !sys.TrySelect(_owner, _target, handsComponent) + || !sys.TryUseItemInHand(_owner, false, handsComponent)) { return Outcome.Failed; } - if (!entMan.TryGetComponent(_target, out SharedItemComponent? itemComponent)) - { - return Outcome.Failed; - } - - foreach (var slot in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(slot) != itemComponent) continue; - handsComponent.ActiveHand = slot; - handsComponent.ActivateItem(); - return Outcome.Success; - } - - return Outcome.Failed; + return Outcome.Success; } } } diff --git a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs index 0dbde8f5bc..179098abad 100644 --- a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs @@ -1,10 +1,8 @@ using Content.Server.Hands.Components; using Content.Server.Nutrition.Components; using Content.Server.Nutrition.EntitySystems; -using Content.Shared.Item; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Nutrition.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Random; namespace Content.Server.AI.Operators.Nutrition @@ -30,35 +28,23 @@ namespace Content.Server.AI.Operators.Nutrition } var entities = IoCManager.Resolve(); + var sysMan = IoCManager.Resolve(); + var handsSys = sysMan.GetEntitySystem(); // TODO: Also have this check storage a la backpack etc. if (entities.Deleted(_target) || - !entities.TryGetComponent(_owner, out HandsComponent? handsComponent) || - !entities.TryGetComponent(_target, out SharedItemComponent? itemComponent)) + !entities.TryGetComponent(_owner, out HandsComponent? handsComponent)) { return Outcome.Failed; } - DrinkComponent? drinkComponent = null; - - foreach (var slot in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(slot) != itemComponent) continue; - handsComponent.ActiveHand = slot; - if (!entities.TryGetComponent(_target, out drinkComponent)) - { - return Outcome.Failed; - } - - // This should also implicitly open it. - handsComponent.ActivateItem(); - _interactionCooldown = IoCManager.Resolve().NextFloat() + 0.5f; - } - - if (drinkComponent == null) - { + if (!handsSys.TrySelect(_owner, out var drinkComponent, handsComponent)) return Outcome.Failed; - } + + if (!handsSys.TryUseItemInHand(_owner, false, handsComponent)) + return Outcome.Failed; + + _interactionCooldown = IoCManager.Resolve().NextFloat() + 0.5f; if (drinkComponent.Deleted || EntitySystem.Get().IsEmpty(drinkComponent.Owner, drinkComponent) || entities.TryGetComponent(_owner, out ThirstComponent? thirstComponent) && @@ -67,6 +53,7 @@ namespace Content.Server.AI.Operators.Nutrition return Outcome.Success; } + /// uuhhh do afters for drinks might mess this up? return Outcome.Continuing; } } diff --git a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs index 989749c4f9..981ee8044e 100644 --- a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs +++ b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs @@ -1,5 +1,6 @@ using Content.Server.Hands.Components; using Content.Server.Nutrition.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; using Content.Shared.Nutrition.Components; using Robust.Shared.GameObjects; @@ -29,35 +30,21 @@ namespace Content.Server.AI.Operators.Nutrition } var entities = IoCManager.Resolve(); + var sysMan = IoCManager.Resolve(); + var handsSys = sysMan.GetEntitySystem(); // TODO: Also have this check storage a la backpack etc. if (entities.Deleted(_target) || - !entities.TryGetComponent(_owner, out HandsComponent? handsComponent) || - !entities.TryGetComponent(_target, out SharedItemComponent? itemComponent)) + !entities.TryGetComponent(_owner, out HandsComponent? handsComponent)) { return Outcome.Failed; } - FoodComponent? foodComponent = null; - - foreach (var slot in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(slot) != itemComponent) continue; - handsComponent.ActiveHand = slot; - if (!entities.TryGetComponent(_target, out foodComponent)) - { - return Outcome.Failed; - } - - // This should also implicitly open it. - handsComponent.ActivateItem(); - _interactionCooldown = IoCManager.Resolve().NextFloat() + 0.5f; - } - - if (foodComponent == null) - { + if (!handsSys.TrySelect(_owner, out var foodComponent, handsComponent)) + return Outcome.Failed; + + if (!handsSys.TryUseItemInHand(_owner, false, handsComponent)) return Outcome.Failed; - } if ((!entities.EntityExists(_target) ? EntityLifeStage.Deleted : entities.GetComponent(_target).EntityLifeStage) >= EntityLifeStage.Deleted || foodComponent.UsesRemaining == 0 || @@ -67,6 +54,7 @@ namespace Content.Server.AI.Operators.Nutrition return Outcome.Success; } + /// do afters for food might mess this up? return Outcome.Continuing; } } diff --git a/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs b/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs index 7eeccc21b3..507f00f455 100644 --- a/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs +++ b/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs @@ -1,8 +1,7 @@ using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; -using Content.Server.Hands.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; +using Content.Shared.Hands.Components; +using System.Linq; namespace Content.Server.AI.Utility.Considerations.Hands { @@ -12,24 +11,12 @@ namespace Content.Server.AI.Utility.Considerations.Hands { var owner = context.GetState().GetValue(); - if (!owner.IsValid() || !IoCManager.Resolve().TryGetComponent(owner, out HandsComponent? handsComponent)) + if (!owner.IsValid() || !IoCManager.Resolve().TryGetComponent(owner, out SharedHandsComponent? handsComponent)) { return 0.0f; } - var handCount = 0; - var freeCount = 0; - - foreach (var hand in handsComponent.ActivePriorityEnumerable()) - { - handCount++; - if (handsComponent.GetItem(hand) == null) - { - freeCount += 1; - } - } - - return (float) freeCount / handCount; + return (float) handsComponent.CountFreeHands() / handsComponent.Count; } } } diff --git a/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs b/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs index e3ed0f259b..7ecc1f298a 100644 --- a/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs +++ b/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs @@ -1,4 +1,5 @@ using Content.Server.Hands.Components; +using Content.Shared.Hands.EntitySystems; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -11,20 +12,7 @@ namespace Content.Server.AI.WorldState.States.Hands public override string Name => "AnyFreeHand"; public override bool GetValue() { - if (!IoCManager.Resolve().TryGetComponent(Owner, out HandsComponent? handsComponent)) - { - return false; - } - - foreach (var hand in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(hand) == null) - { - return true; - } - } - - return false; + return IoCManager.Resolve().GetEntitySystem().TryGetEmptyHand(Owner, out _); } } } diff --git a/Content.Server/AI/WorldState/States/Hands/FreeHands.cs b/Content.Server/AI/WorldState/States/Hands/FreeHands.cs index 83bada9c2c..ab6d0f8727 100644 --- a/Content.Server/AI/WorldState/States/Hands/FreeHands.cs +++ b/Content.Server/AI/WorldState/States/Hands/FreeHands.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; +using System.Linq; using Content.Server.Hands.Components; +using Content.Shared.Hands.Components; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Server.AI.WorldState.States.Hands { @@ -17,18 +16,10 @@ namespace Content.Server.AI.WorldState.States.Hands if (!IoCManager.Resolve().TryGetComponent(Owner, out HandsComponent? handsComponent)) { - return result; + return new List(); } - foreach (var hand in handsComponent.ActivePriorityEnumerable()) - { - if (handsComponent.GetItem(hand) == null) - { - result.Add(hand); - } - } - - return result; + return handsComponent.GetFreeHandNames().ToList(); } } } diff --git a/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs b/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs index 87cd3fa726..7337017017 100644 --- a/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs +++ b/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using Content.Server.Hands.Components; +using System.Linq; +using Content.Shared.Hands.EntitySystems; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Server.AI.WorldState.States.Hands { @@ -12,23 +10,7 @@ namespace Content.Server.AI.WorldState.States.Hands public override string Name => "HandItems"; public override List GetValue() { - var result = new List(); - if (!IoCManager.Resolve().TryGetComponent(Owner, out HandsComponent? handsComponent)) - { - return result; - } - - foreach (var hand in handsComponent.ActivePriorityEnumerable()) - { - var item = handsComponent.GetItem(hand); - - if (item != null) - { - result.Add(item.Owner); - } - } - - return result; + return IoCManager.Resolve().GetEntitySystem().EnumerateHeld(Owner).ToList(); } } } diff --git a/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs b/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs index 4256c0b008..c2560f8bd1 100644 --- a/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs +++ b/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs @@ -15,7 +15,7 @@ namespace Content.Server.AI.WorldState.States.Inventory public override EntityUid? GetValue() { - return IoCManager.Resolve().GetComponentOrNull(Owner)?.GetActiveHandItem?.Owner; + return IoCManager.Resolve().GetComponentOrNull(Owner)?.ActiveHandEntity; } } } diff --git a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs index 0294dfd93a..01793155bc 100644 --- a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs +++ b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs @@ -1,8 +1,5 @@ -using System.Collections.Generic; -using Content.Server.Hands.Components; +using Content.Shared.Hands.EntitySystems; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Server.AI.WorldState.States.Inventory { @@ -13,17 +10,7 @@ namespace Content.Server.AI.WorldState.States.Inventory public override IEnumerable GetValue() { - var entMan = IoCManager.Resolve(); - if (entMan.TryGetComponent(Owner, out HandsComponent? handsComponent)) - { - foreach (var item in handsComponent.GetAllHeldItems()) - { - if (entMan.Deleted(item.Owner)) - continue; - - yield return item.Owner; - } - } + return IoCManager.Resolve().GetEntitySystem().EnumerateHeld(Owner); } } } diff --git a/Content.Server/AME/Components/AMEControllerComponent.cs b/Content.Server/AME/Components/AMEControllerComponent.cs index 3674b2468c..515755cde4 100644 --- a/Content.Server/AME/Components/AMEControllerComponent.cs +++ b/Content.Server/AME/Components/AMEControllerComponent.cs @@ -7,6 +7,7 @@ using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.ActionBlocker; using Content.Shared.AME; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Popups; @@ -23,6 +24,7 @@ namespace Content.Server.AME.Components public sealed class AMEControllerComponent : SharedAMEControllerComponent, IInteractUsing { [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(AMEControllerUiKey.Key); private bool _injecting; @@ -210,10 +212,7 @@ namespace Content.Server.AME.Components _jarSlot.Remove(jar); UpdateUserInterface(); - if (!_entities.TryGetComponent(user, out var hands) || !_entities.TryGetComponent(jar, out var item)) - return; - if (hands.CanPutInHand(item)) - hands.PutInHand(item); + _sysMan.GetEntitySystem().PickupOrDrop(user, jar); } private void ToggleInjection() @@ -306,13 +305,13 @@ namespace Content.Server.AME.Components return true; } - if (hands.GetActiveHandItem == null) + if (hands.ActiveHandEntity == null) { Owner.PopupMessage(args.User, Loc.GetString("ame-controller-component-interact-using-nothing-in-hands-text")); return false; } - var activeHandEntity = hands.GetActiveHandItem.Owner; + var activeHandEntity = hands.ActiveHandEntity; if (_entities.HasComponent(activeHandEntity)) { if (HasJar) @@ -322,7 +321,7 @@ namespace Content.Server.AME.Components else { - _jarSlot.Insert(activeHandEntity); + _jarSlot.Insert(activeHandEntity.Value); Owner.PopupMessage(args.User, Loc.GetString("ame-controller-component-interact-using-success")); UpdateUserInterface(); } diff --git a/Content.Server/Access/Systems/IdCardSystem.cs b/Content.Server/Access/Systems/IdCardSystem.cs index 605f4ce63a..245a93f4d7 100644 --- a/Content.Server/Access/Systems/IdCardSystem.cs +++ b/Content.Server/Access/Systems/IdCardSystem.cs @@ -140,8 +140,8 @@ namespace Content.Server.Access.Systems { // check held item? if (EntityManager.TryGetComponent(uid, out SharedHandsComponent? hands) && - hands.TryGetActiveHeldEntity(out var heldItem) && - TryGetIdCard(heldItem.Value, out idCard)) + hands.ActiveHandEntity is EntityUid heldItem && + TryGetIdCard(heldItem, out idCard)) { return true; } diff --git a/Content.Server/Atmos/Components/GasAnalyzerComponent.cs b/Content.Server/Atmos/Components/GasAnalyzerComponent.cs index 53c23450dc..0c14f51cbf 100644 --- a/Content.Server/Atmos/Components/GasAnalyzerComponent.cs +++ b/Content.Server/Atmos/Components/GasAnalyzerComponent.cs @@ -161,7 +161,7 @@ namespace Content.Server.Atmos.Components if (!_entities.TryGetComponent(playerEntity, out HandsComponent? handsComponent)) return; - if (handsComponent?.GetActiveHandItem?.Owner is not {Valid: true} activeHandEntity || + if (handsComponent?.ActiveHandEntity is not {Valid: true} activeHandEntity || !_entities.TryGetComponent(activeHandEntity, out GasAnalyzerComponent? gasAnalyzer)) { return; @@ -228,7 +228,7 @@ namespace Content.Server.Atmos.Components return; } - if (handsComponent.GetActiveHandItem?.Owner is not {Valid: true} activeHandEntity || + if (handsComponent.ActiveHandEntity is not {Valid: true} activeHandEntity || !_entities.TryGetComponent(activeHandEntity, out GasAnalyzerComponent? gasAnalyzer)) { serverMsg.Session.AttachedEntity.Value.PopupMessage(Loc.GetString("gas-analyzer-component-need-gas-analyzer-in-hand-message")); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index c65265fdfb..25d52af257 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -1,4 +1,3 @@ -using System; using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; @@ -8,19 +7,14 @@ using Content.Server.Hands.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; -using Content.Shared.ActionBlocker; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping.Binary.Components; using Content.Shared.Database; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Players; namespace Content.Server.Atmos.Piping.Unary.EntitySystems { @@ -30,6 +24,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly AdminLogSystem _adminLogSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -248,11 +243,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!EntityManager.TryGetComponent(args.Used, out GasTankComponent? _)) return; - // Check the user has hands. - if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands)) - return; - - if (!hands.Drop(args.Used, container)) + if (!_handsSystem.TryDropIntoContainer(args.User, args.Used, container)) return; _adminLogSystem.Add(LogType.CanisterTankInserted, LogImpact.Medium, $"Player {ToPrettyString(args.User):player} inserted tank {ToPrettyString(container.ContainedEntities[0]):tank} into {ToPrettyString(canister):canister}"); diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs index 82535e3d53..6c5bf394a4 100644 --- a/Content.Server/Botany/Components/PlantHolderComponent.cs +++ b/Content.Server/Botany/Components/PlantHolderComponent.cs @@ -432,7 +432,7 @@ namespace Content.Server.Botany.Components { if (_entMan.TryGetComponent(user, out HandsComponent? hands)) { - if (!botanySystem.CanHarvest(Seed, hands.GetActiveHandItem?.Owner)) + if (!botanySystem.CanHarvest(Seed, hands.ActiveHandEntity)) return false; } else if (!botanySystem.CanHarvest(Seed)) diff --git a/Content.Server/Chat/Commands/SuicideCommand.cs b/Content.Server/Chat/Commands/SuicideCommand.cs index 1f1564f379..8c686128aa 100644 --- a/Content.Server/Chat/Commands/SuicideCommand.cs +++ b/Content.Server/Chat/Commands/SuicideCommand.cs @@ -89,9 +89,9 @@ namespace Content.Server.Chat.Commands // Held item suicide if (_entities.TryGetComponent(owner, out HandsComponent handsComponent) - && handsComponent.GetActiveHandItem is {} itemComponent) + && handsComponent.ActiveHandEntity is EntityUid item) { - var suicide = _entities.GetComponents(itemComponent.Owner).FirstOrDefault(); + var suicide = _entities.GetComponents(item).FirstOrDefault(); if (suicide != null) { diff --git a/Content.Server/Chemistry/Components/ChemMasterComponent.cs b/Content.Server/Chemistry/Components/ChemMasterComponent.cs index 823cdc453e..2ed01990e5 100644 --- a/Content.Server/Chemistry/Components/ChemMasterComponent.cs +++ b/Content.Server/Chemistry/Components/ChemMasterComponent.cs @@ -1,14 +1,12 @@ using Content.Server.Chemistry.EntitySystems; -using Content.Server.Hands.Components; using Content.Server.Labels.Components; using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.Chemistry.Components; using Content.Shared.Containers.ItemSlots; using Content.Shared.FixedPoint; -using Content.Shared.Item; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Popups; -using Content.Shared.Random.Helpers; using Content.Shared.Sound; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -28,6 +26,7 @@ namespace Content.Server.Chemistry.Components public sealed class ChemMasterComponent : SharedChemMasterComponent { [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; [ViewVariables] private uint _pillType = 1; @@ -276,6 +275,9 @@ namespace Content.Server.Chemistry.Components return; } + var handSys = _sysMan.GetEntitySystem(); + var solSys = _sysMan.GetEntitySystem(); + if (action == UiAction.CreateBottles) { var individualVolume = BufferSolution.TotalVolume / FixedPoint2.New(bottleAmount); @@ -298,25 +300,12 @@ namespace Content.Server.Chemistry.Components labelComponent.CurrentLabel = label; var bufferSolution = BufferSolution.SplitSolution(actualVolume); - var bottleSolution = EntitySystem.Get().EnsureSolution(bottle, "drink"); + var bottleSolution = solSys.EnsureSolution(bottle, "drink"); - EntitySystem.Get().TryAddSolution(bottle, bottleSolution, bufferSolution); + solSys.TryAddSolution(bottle, bottleSolution, bufferSolution); //Try to give them the bottle - if (_entities.TryGetComponent(user, out var hands) && - _entities.TryGetComponent(bottle, out var item)) - { - if (hands.CanPutInHand(item)) - { - hands.PutInHand(item); - continue; - } - } - - //Put it on the floor - _entities.GetComponent(bottle).Coordinates = _entities.GetComponent(user).Coordinates; - //Give it an offset - bottle.RandomOffset(0.2f); + handSys.PickupOrDrop(user, bottle); } } else //Pills @@ -342,7 +331,7 @@ namespace Content.Server.Chemistry.Components var bufferSolution = BufferSolution.SplitSolution(actualVolume); var pillSolution = EntitySystem.Get().EnsureSolution(pill, "food"); - EntitySystem.Get().TryAddSolution(pill, pillSolution, bufferSolution); + solSys.TryAddSolution(pill, pillSolution, bufferSolution); //Change pill Sprite component state if (!_entities.TryGetComponent(pill, out SpriteComponent? sprite)) @@ -352,20 +341,7 @@ namespace Content.Server.Chemistry.Components sprite?.LayerSetState(0, "pill" + _pillType); //Try to give them the bottle - if (_entities.TryGetComponent(user, out var hands) && - _entities.TryGetComponent(pill, out var item)) - { - if (hands.CanPutInHand(item)) - { - hands.PutInHand(item); - continue; - } - } - - //Put it on the floor - _entities.GetComponent(pill).Coordinates = _entities.GetComponent(user).Coordinates; - //Give it an offset - pill.RandomOffset(0.2f); + handSys.PickupOrDrop(user, pill); } } diff --git a/Content.Server/Communications/CommunicationsConsoleComponent.cs b/Content.Server/Communications/CommunicationsConsoleComponent.cs index 578da0dca4..c7ff75c107 100644 --- a/Content.Server/Communications/CommunicationsConsoleComponent.cs +++ b/Content.Server/Communications/CommunicationsConsoleComponent.cs @@ -1,17 +1,13 @@ -using System; using System.Globalization; using System.Threading; +using Content.Server.Access.Systems; using Content.Server.Chat.Managers; -using Content.Server.PDA; using Content.Server.Power.Components; using Content.Server.RoundEnd; using Content.Server.UserInterface; using Content.Shared.Communications; using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Timing; -using Robust.Shared.ViewVariables; using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Communications @@ -23,6 +19,7 @@ namespace Content.Server.Communications [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IEntityManager _entities = default!; [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; private bool Powered => !_entities.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered; @@ -101,9 +98,10 @@ namespace Content.Server.Communications UpdateBoundInterface(); var message = msg.Message.Length <= 256 ? msg.Message.Trim() : $"{msg.Message.Trim().Substring(0, 256)}..."; + var sys = _sysMan.GetEntitySystem(); var author = "Unknown"; - if (obj.Session.AttachedEntity is {Valid: true} mob && mob.TryGetHeldId(out var id)) + if (obj.Session.AttachedEntity is {Valid: true} mob && sys.TryFindIdCard(mob, out var id)) { author = $"{id.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(id.JobTitle ?? string.Empty)})".Trim(); } diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 1c3d63f5c8..3795c575fa 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -10,6 +10,7 @@ using Content.Shared.Construction; using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Steps; using Content.Shared.Coordinates; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Popups; @@ -25,6 +26,7 @@ namespace Content.Server.Construction [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; // --- WARNING! LEGACY CODE AHEAD! --- // This entire file contains the legacy code for initial construction. @@ -43,20 +45,17 @@ namespace Content.Server.Construction // LEGACY CODE. See warning at the top of the file! private IEnumerable EnumerateNearby(EntityUid user) { - if (EntityManager.TryGetComponent(user, out HandsComponent? hands)) + foreach (var item in _handsSystem.EnumerateHeld(user)) { - foreach (var itemComponent in hands?.GetAllHeldItems()!) + if (TryComp(item, out ServerStorageComponent? storage)) { - if (EntityManager.TryGetComponent(itemComponent.Owner, out ServerStorageComponent? storage)) + foreach (var storedEntity in storage.StoredEntities!) { - foreach (var storedEntity in storage.StoredEntities!) - { - yield return storedEntity; - } + yield return storedEntity; } - - yield return itemComponent.Owner; } + + yield return item; } if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator)) @@ -334,7 +333,7 @@ namespace Content.Server.Construction } if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is {Valid: true} item) - hands.PutInHandOrDrop(item); + _handsSystem.PickupOrDrop(user, item); } // LEGACY CODE. See warning at the top of the file! @@ -401,7 +400,7 @@ namespace Content.Server.Construction } if (!_actionBlocker.CanInteract(user, null) - || !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.GetActiveHandItem == null) + || !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.ActiveHandEntity == null) { Cleanup(); return; @@ -426,7 +425,7 @@ namespace Content.Server.Construction var valid = false; - if (hands.GetActiveHandItem?.Owner is not {Valid: true} holding) + if (hands.ActiveHandEntity is not {Valid: true} holding) { Cleanup(); return; diff --git a/Content.Server/Cuffs/Components/CuffableComponent.cs b/Content.Server/Cuffs/Components/CuffableComponent.cs index 0f51607b77..1f815706a1 100644 --- a/Content.Server/Cuffs/Components/CuffableComponent.cs +++ b/Content.Server/Cuffs/Components/CuffableComponent.cs @@ -5,6 +5,7 @@ using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Shared.Alert; using Content.Shared.Cuffs.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; @@ -26,6 +27,7 @@ namespace Content.Server.Cuffs.Components public sealed class CuffableComponent : SharedCuffableComponent { [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; /// /// How many of this entity's hands are currently cuffed. @@ -103,15 +105,13 @@ namespace Content.Server.Cuffs.Components return true; } + var sys = _sysMan.GetEntitySystem(); + // Success! - if (_entMan.TryGetComponent(user, out HandsComponent? handsComponent) && handsComponent.IsHolding(handcuff)) - { - // Good lord handscomponent is scuffed, I hope some smug person will fix it someday - handsComponent.Drop(handcuff); - } + sys.TryDrop(user, handcuff); Container.Insert(handcuff); - CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? ownerHands) && ownerHands.HandNames.Count() > CuffedHandCount; + CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? ownerHands) && ownerHands.Hands.Count() > CuffedHandCount; OnCuffedStateChanged?.Invoke(); UpdateAlert(); @@ -133,23 +133,22 @@ namespace Content.Server.Cuffs.Components { if (!_entMan.TryGetComponent(Owner, out HandsComponent? handsComponent)) return; - var itemCount = handsComponent.GetAllHeldItems().Count(); - var freeHandCount = handsComponent.HandNames.Count() - CuffedHandCount; + var sys = _sysMan.GetEntitySystem(); - if (freeHandCount < itemCount) + var freeHandCount = handsComponent.Hands.Count() - CuffedHandCount; + + foreach (var hand in handsComponent.Hands.Values) { - foreach (var item in handsComponent.GetAllHeldItems()) + if (hand.IsEmpty) + continue; + + if (freeHandCount > 0) { - if (freeHandCount < itemCount) - { - freeHandCount++; - handsComponent.Drop(item.Owner, false); - } - else - { - break; - } + freeHandCount--; + continue; } + + sys.TryDrop(Owner, hand, checkActionBlocker: false, handsComp: handsComponent); } } @@ -267,7 +266,7 @@ namespace Content.Server.Cuffs.Components } } - CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? handsComponent) && handsComponent.HandNames.Count() > CuffedHandCount; + CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? handsComponent) && handsComponent.SortedHands.Count() > CuffedHandCount; OnCuffedStateChanged?.Invoke(); UpdateAlert(); Dirty(); diff --git a/Content.Server/Cuffs/CuffableSystem.cs b/Content.Server/Cuffs/CuffableSystem.cs index 6a732927a8..cb36ecfde8 100644 --- a/Content.Server/Cuffs/CuffableSystem.cs +++ b/Content.Server/Cuffs/CuffableSystem.cs @@ -2,7 +2,7 @@ using Content.Server.Cuffs.Components; using Content.Server.Hands.Components; using Content.Shared.ActionBlocker; using Content.Shared.Cuffs; -using Content.Shared.Hands.Components; +using Content.Shared.Hands; using Content.Shared.MobState.Components; using Content.Shared.Popups; using Content.Shared.Verbs; diff --git a/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs b/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs index c4a039ae21..e1823c74a9 100644 --- a/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs +++ b/Content.Server/Disposal/Tube/Components/DisposalTaggerComponent.cs @@ -84,6 +84,7 @@ namespace Content.Server.Disposal.Tube.Components { SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); } + protected override void OnRemove() { base.OnRemove(); diff --git a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs index 5d7d0e290d..ae48f8b98b 100644 --- a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs +++ b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Disposal.Tube.Components; +using Content.Server.Disposal.Tube.Components; using Content.Server.UserInterface; using Content.Server.Hands.Components; using Content.Shared.Movement; @@ -81,7 +81,7 @@ namespace Content.Server.Disposal.Tube return; } - var activeHandEntity = hands.GetActiveHandItem?.Owner; + var activeHandEntity = hands.ActiveHandEntity; if (activeHandEntity != null) { args.Cancel(); @@ -96,7 +96,7 @@ namespace Content.Server.Disposal.Tube return; } - var activeHandEntity = hands.GetActiveHandItem?.Owner; + var activeHandEntity = hands.ActiveHandEntity; if (activeHandEntity != null) { args.Cancel(); diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index f089f6f5be..189abe76de 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -16,21 +16,16 @@ using Content.Shared.Atmos; using Content.Shared.Disposal; using Content.Shared.Disposal.Components; using Content.Shared.DragDrop; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Movement; -using Content.Shared.Popups; using Content.Shared.Throwing; using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Player; using Robust.Shared.Random; @@ -43,6 +38,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; private readonly List _activeDisposals = new(); @@ -140,7 +136,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems Category = VerbCategory.Insert, Act = () => { - args.Hands.Drop(args.Using.Value, component.Container); + _handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands); AfterInsert(component, args.Using.Value); } }; @@ -248,8 +244,8 @@ namespace Content.Server.Disposal.Unit.EntitySystems { return; } - - if (!CanInsert(component, args.Used) || !hands.Drop(args.Used, component.Container)) + + if (!CanInsert(component, args.Used) || !_handsSystem.TryDropIntoContainer(args.User, args.Used, component.Container)) { return; } diff --git a/Content.Server/DoAfter/DoAfter.cs b/Content.Server/DoAfter/DoAfter.cs index 1f073b50e5..0f77e9a101 100644 --- a/Content.Server/DoAfter/DoAfter.cs +++ b/Content.Server/DoAfter/DoAfter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Content.Server.Hands.Components; using Content.Shared.Item; @@ -30,7 +30,7 @@ namespace Content.Server.DoAfter // NeedHand private readonly string? _activeHand; - private readonly SharedItemComponent? _activeItem; + private readonly EntityUid? _activeItem; public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager) { @@ -52,8 +52,8 @@ namespace Content.Server.DoAfter // (or if there is no item there we need to keep it free). if (eventArgs.NeedHand && entityManager.TryGetComponent(eventArgs.User, out HandsComponent? handsComponent)) { - _activeHand = handsComponent.ActiveHand; - _activeItem = handsComponent.GetActiveHandItem; + _activeHand = handsComponent.ActiveHand?.Name; + _activeItem = handsComponent.ActiveHandEntity; } Tcs = new TaskCompletionSource(); @@ -152,13 +152,13 @@ namespace Content.Server.DoAfter } else { - var currentActiveHand = handsComponent.ActiveHand; + var currentActiveHand = handsComponent.ActiveHand?.Name; if (_activeHand != currentActiveHand) { return true; } - var currentItem = handsComponent.GetActiveHandItem; + var currentItem = handsComponent.ActiveHandEntity; if (_activeItem != currentItem) { return true; diff --git a/Content.Server/Drone/DroneSystem.cs b/Content.Server/Drone/DroneSystem.cs index 4857696fbf..85668b845a 100644 --- a/Content.Server/Drone/DroneSystem.cs +++ b/Content.Server/Drone/DroneSystem.cs @@ -19,6 +19,7 @@ using Content.Server.Ghost.Roles.Components; using Content.Server.Hands.Components; using Content.Server.UserInterface; using Robust.Shared.Player; +using Content.Shared.Hands.EntitySystems; using Robust.Shared.Timing; namespace Content.Server.Drone @@ -28,6 +29,7 @@ namespace Content.Server.Drone [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; @@ -118,13 +120,18 @@ namespace Content.Server.Drone if (TryComp(uid, out var hands) && hands.Count >= drone.Tools.Count) { - foreach (var entry in drone.Tools) - { - var item = EntityManager.SpawnEntity(entry.PrototypeId, spawnCoord); - AddComp(item); - hands.PutInHand(item); - drone.ToolUids.Add(item); - } + foreach (var entry in drone.Tools) + { + var item = EntityManager.SpawnEntity(entry.PrototypeId, spawnCoord); + AddComp(item); + if (!_handsSystem.TryPickupAnyHand(uid, item, checkActionBlocker: false)) + { + QueueDel(item); + Logger.Error($"Drone ({ToPrettyString(uid)}) failed to pick up innate item ({ToPrettyString(item)})"); + continue; + } + drone.ToolUids.Add(item); + } } if (TryComp(uid, out var actions) && TryComp(uid, out var flashlight)) diff --git a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs index be31e0bee2..aae86f3942 100644 --- a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs +++ b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs @@ -1,8 +1,7 @@ using Content.Server.DoAfter; using Content.Server.Engineering.Components; using Content.Server.Hands.Components; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; using Content.Shared.Verbs; using JetBrains.Annotations; @@ -11,6 +10,8 @@ namespace Content.Server.Engineering.EntitySystems [UsedImplicitly] public sealed class DisassembleOnAltVerbSystem : EntitySystem { + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + public override void Initialize() { base.Initialize(); @@ -63,11 +64,7 @@ namespace Content.Server.Engineering.EntitySystems var entity = EntityManager.SpawnEntity(component.Prototype, transformComp.Coordinates); - if (TryComp(user, out var hands) - && TryComp(entity, out var item)) - { - hands.PutInHandOrDrop(item); - } + _handsSystem.TryPickup(user, entity); EntityManager.DeleteEntity(component.Owner); diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 6e39f5bc2e..4d0c26872d 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -13,6 +13,7 @@ using Content.Shared.Access.Components; using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.Ghost; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; using Content.Shared.PDA; using Content.Shared.Preferences; @@ -33,6 +34,7 @@ namespace Content.Server.GameTicking [Dependency] private readonly IdCardSystem _cardSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; /// /// Can't yet be removed because every test ever seems to depend on it. I'll make removing this a different PR. @@ -319,14 +321,15 @@ namespace Content.Server.GameTicking } } - if (EntityManager.TryGetComponent(entity, out HandsComponent? handsComponent)) + if (!TryComp(entity, out HandsComponent? handsComponent)) + return; + + var inhand = startingGear.Inhand; + var coords = EntityManager.GetComponent(entity).Coordinates; + foreach (var (hand, prototype) in inhand) { - var inhand = startingGear.Inhand; - foreach (var (hand, prototype) in inhand) - { - var inhandEntity = EntityManager.SpawnEntity(prototype, EntityManager.GetComponent(entity).Coordinates); - handsComponent.TryPickupEntity(hand, inhandEntity, checkActionBlocker: false); - } + var inhandEntity = EntityManager.SpawnEntity(prototype, coords); + _handsSystem.TryPickup(entity, inhandEntity, hand, checkActionBlocker: false, handsComp: handsComponent); } } diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs index c766e3c967..9647fa961a 100644 --- a/Content.Server/Guardian/GuardianSystem.cs +++ b/Content.Server/Guardian/GuardianSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Actions; using Content.Shared.Audio; using Content.Shared.Damage; using Content.Shared.Examine; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.MobState; @@ -25,6 +26,7 @@ namespace Content.Server.Guardian [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly DamageableSystem _damageSystem = default!; [Dependency] private readonly SharedActionsSystem _actionSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -180,8 +182,7 @@ namespace Content.Server.Guardian if (comp.Deleted || comp.Used || - !TryComp(ev.User, out var hands) || - !hands.IsHolding(comp.Owner) || + !_handsSystem.IsHolding(ev.User, comp.Owner, out _) || HasComp(ev.Target)) { comp.Injecting = false; diff --git a/Content.Server/Hands/Components/HandsComponent.cs b/Content.Server/Hands/Components/HandsComponent.cs index d89ef55e46..8ad2ed31cc 100644 --- a/Content.Server/Hands/Components/HandsComponent.cs +++ b/Content.Server/Hands/Components/HandsComponent.cs @@ -1,19 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Content.Server.Act; -using Content.Server.Popups; -using Content.Server.Pulling; -using Content.Shared.Audio; using Content.Shared.Body.Part; using Content.Shared.Hands.Components; -using Content.Shared.Item; -using Content.Shared.Popups; -using Content.Shared.Pulling.Components; -using Content.Shared.Sound; -using Robust.Shared.Audio; -using Robust.Shared.Player; +using Content.Shared.Hands.EntitySystems; namespace Content.Server.Hands.Components { @@ -24,9 +11,6 @@ namespace Content.Server.Hands.Components #pragma warning restore 618 { [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - [Dependency] private readonly IEntityManager _entities = default!; - - #region Pull/Disarm void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs args) { @@ -43,7 +27,7 @@ namespace Content.Server.Hands.Components _ => throw new ArgumentOutOfRangeException() }; - AddHand(args.Slot, location); + _entitySystemManager.GetEntitySystem().AddHand(Owner, args.Slot, location); } void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args) @@ -51,94 +35,8 @@ namespace Content.Server.Hands.Components if (args.Part.PartType != BodyPartType.Hand) return; - RemoveHand(args.Slot); + _entitySystemManager.GetEntitySystem().RemoveHand(Owner, args.Slot); } - - public bool BreakPulls() - { - // What is this API?? - // I just wanted to do actions not deal with this shit... - if (!_entities.TryGetComponent(Owner, out SharedPullerComponent? puller) - || puller.Pulling is not {Valid: true} pulling || !_entities.TryGetComponent(puller.Pulling.Value, out SharedPullableComponent? pullable)) - return false; - - return _entitySystemManager.GetEntitySystem().TryStopPull(pullable); - } - - #endregion - - #region Old public methods - - public IEnumerable HandNames => Hands.Select(h => h.Name); - - public int Count => Hands.Count; - - /// - /// Returns a list of all hand names, with the active hand being first. - /// - public IEnumerable ActivePriorityEnumerable() - { - if (ActiveHand != null) - yield return ActiveHand; - - foreach (var hand in Hands) - { - if (hand.Name == ActiveHand) - continue; - - yield return hand.Name; - } - } - - /// - /// Tries to get the ItemComponent on the entity held by a hand. - /// - public SharedItemComponent? GetItem(string handName) - { - if (!TryGetHeldEntity(handName, out var heldEntity)) - return null; - - _entities.TryGetComponent(heldEntity, out SharedItemComponent? item); - return item; - } - - /// - /// Tries to get the ItemComponent on the entity held by a hand. - /// - public bool TryGetItem(string handName, [NotNullWhen(true)] out SharedItemComponent? item) - { - item = null; - - if (!TryGetHeldEntity(handName, out var heldEntity)) - return false; - - return _entities.TryGetComponent(heldEntity, out item); - } - - /// - /// Tries to get the ItemComponent off the entity in the active hand. - /// - public SharedItemComponent? GetActiveHandItem - { - get - { - if (!TryGetActiveHeldEntity(out var heldEntity)) - return null; - - _entities.TryGetComponent(heldEntity, out SharedItemComponent? item); - return item; - } - } - - public IEnumerable GetAllHeldItems() - { - foreach (var entity in GetAllHeldEntities()) - { - if (_entities.TryGetComponent(entity, out SharedItemComponent? item)) - yield return item; - } - } - #endregion } } diff --git a/Content.Server/Hands/Systems/HandVirtualItemSystem.cs b/Content.Server/Hands/Systems/HandVirtualItemSystem.cs index 3de0a99d5a..6d804a5d9a 100644 --- a/Content.Server/Hands/Systems/HandVirtualItemSystem.cs +++ b/Content.Server/Hands/Systems/HandVirtualItemSystem.cs @@ -1,9 +1,8 @@ using Content.Server.Hands.Components; using Content.Shared.Hands; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; namespace Content.Server.Hands.Systems { @@ -14,24 +13,15 @@ namespace Content.Server.Hands.Systems public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user) { - if (EntityManager.TryGetComponent(user, out var hands)) - { - foreach (var handName in hands.ActivePriorityEnumerable()) - { - var hand = hands.GetHand(handName); - if (hand.HeldEntity != null) - continue; + if (!_handsSystem.TryGetEmptyHand(user, out var hand)) + return false; - var pos = EntityManager.GetComponent(hands.Owner).Coordinates; - var virtualItem = EntityManager.SpawnEntity("HandVirtualItem", pos); - var virtualItemComp = EntityManager.GetComponent(virtualItem); - virtualItemComp.BlockingEntity = blockingEnt; - _handsSystem.PutEntityIntoHand(user, hand, virtualItem, hands); - return true; - } - } - - return false; + var pos = EntityManager.GetComponent(user).Coordinates; + var virtualItem = EntityManager.SpawnEntity("HandVirtualItem", pos); + var virtualItemComp = EntityManager.GetComponent(virtualItem); + virtualItemComp.BlockingEntity = blockingEnt; + _handsSystem.DoPickup(user, hand, virtualItem); + return true; } /// @@ -40,20 +30,12 @@ namespace Content.Server.Hands.Systems /// public void DeleteInHandsMatching(EntityUid user, EntityUid matching) { - if (!EntityManager.TryGetComponent(user, out var hands)) - return; - - foreach (var handName in hands.ActivePriorityEnumerable()) + foreach (var hand in _handsSystem.EnumerateHands(user)) { - var hand = hands.GetHand(handName); - - if (!(hand.HeldEntity is { } heldEntity)) - continue; - - if (EntityManager.TryGetComponent(heldEntity, out var virt) - && virt.BlockingEntity == matching) + if (TryComp(hand.HeldEntity, out HandVirtualItemComponent? virt) && virt.BlockingEntity == matching) { Delete(virt, user); + return; } } } diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index cc892205d9..31f8c67483 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -21,7 +21,6 @@ using Content.Shared.Popups; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Player; -using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Input.Binding; @@ -29,6 +28,9 @@ using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Players; using Robust.Shared.Utility; +using Content.Shared.Pulling.Components; +using Content.Server.Pulling; +using Content.Shared.Hands.EntitySystems; namespace Content.Server.Hands.Systems { @@ -43,27 +45,24 @@ namespace Content.Server.Hands.Systems [Dependency] private readonly StrippableSystem _strippableSystem = default!; [Dependency] private readonly SharedHandVirtualItemSystem _virtualSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly PullingSystem _pullingSystem = default!; + public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(HandleExamined); - SubscribeNetworkEvent(HandleActivateInHand); - SubscribeNetworkEvent(HandleInteractUsingInHand); - SubscribeNetworkEvent(HandleUseInHand); - SubscribeNetworkEvent(HandleMoveItemFromHand); SubscribeLocalEvent(OnDisarmed, before: new[] { typeof(StunSystem) }); SubscribeLocalEvent(HandlePullAttempt); SubscribeLocalEvent(HandlePullStarted); SubscribeLocalEvent(HandlePullStopped); + SubscribeLocalEvent(HandleEntityRemoved); + SubscribeLocalEvent(GetComponentState); CommandBinds.Builder - .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(s => HandleActivateItem(s))) - .Bind(ContentKeyFunctions.AltActivateItemInHand, InputCmdHandler.FromDelegate(s => HandleActivateItem(s, true))) .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) @@ -79,15 +78,19 @@ namespace Content.Server.Hands.Systems private void GetComponentState(EntityUid uid, HandsComponent hands, ref ComponentGetState args) { - args.State = new HandsComponentState(hands.Hands, hands.ActiveHand); + args.State = new HandsComponentState(hands); } private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent args) { - if (args.Handled || component.BreakPulls()) + if (args.Handled) return; - if (component.ActiveHand == null || !component.Drop(component.ActiveHand, false)) + // Break any pulls + if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is EntityUid pulled && TryComp(pulled, out SharedPullableComponent? pullable)) + _pullingSystem.TryStopPull(pullable); + + if (_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false)) return; var targetName = Name(args.Target); @@ -103,9 +106,9 @@ namespace Content.Server.Hands.Systems } #region EntityInsertRemove - public override void RemoveHeldEntityFromHand(EntityUid uid, Hand hand, SharedHandsComponent? hands = null) + public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, SharedHandsComponent? hands = null) { - base.RemoveHeldEntityFromHand(uid, hand, hands); + base.DoDrop(uid, hand,doDropInteraction, hands); // update gui of anyone stripping this entity. _strippableSystem.SendUpdate(uid); @@ -114,9 +117,9 @@ namespace Content.Server.Hands.Systems sprite.RenderOrder = EntityManager.CurrentTick.Value; } - public override void PutEntityIntoHand(EntityUid uid, Hand hand, EntityUid entity, SharedHandsComponent? hands = null) + public override void DoPickup(EntityUid uid, Hand hand, EntityUid entity, SharedHandsComponent? hands = null) { - base.PutEntityIntoHand(uid, hand, entity, hands); + base.DoPickup(uid, hand, entity, hands); // update gui of anyone stripping this entity. _strippableSystem.SendUpdate(uid); @@ -124,6 +127,7 @@ namespace Content.Server.Hands.Systems _logSystem.Add(LogType.Pickup, LogImpact.Low, $"{uid} picked up {entity}"); } + public override void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, EntityUid? exclude) { @@ -138,12 +142,10 @@ namespace Content.Server.Hands.Systems RaiseNetworkEvent(new PickupAnimationEvent(item, initialPosition, finalPosition), filter); } - protected override void HandleContainerRemoved(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) + private void HandleEntityRemoved(EntityUid uid, SharedHandsComponent component, EntRemovedFromContainerMessage args) { if (!Deleted(args.Entity) && TryComp(args.Entity, out HandVirtualItemComponent? @virtual)) _virtualSystem.Delete(@virtual, uid); - - base.HandleContainerRemoved(uid, component, args); } #endregion @@ -154,11 +156,10 @@ namespace Content.Server.Hands.Systems return; // Cancel pull if all hands full. - if (component.Hands.All(hand => !hand.IsEmpty)) + if (!component.IsAnyHandFree()) args.Cancelled = true; } - private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) { if (args.Puller.Owner != uid) @@ -177,7 +178,7 @@ namespace Content.Server.Hands.Systems // Try find hand that is doing this pull. // and clear it. - foreach (var hand in component.Hands) + foreach (var hand in component.Hands.Values) { if (hand.HeldEntity == null || !TryComp(hand.HeldEntity, out HandVirtualItemComponent? virtualItem) @@ -191,34 +192,6 @@ namespace Content.Server.Hands.Systems #endregion #region interactions - private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args) - { - if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) - hands.TryMoveHeldEntityToActiveHand(msg.HandName); - } - private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args) - { - if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) - hands.ActivateItem(); - } - private void HandleInteractUsingInHand(ClientInteractUsingInHandMsg msg, EntitySessionEventArgs args) - { - if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) - hands.InteractHandWithActiveHand(msg.HandName); - } - - private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args) - { - if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) - hands.ActivateHeldEntity(msg.HandName); - } - - private void HandleActivateItem(ICommonSession? session, bool altInteract = false) - { - if (TryComp(session?.AttachedEntity, out SharedHandsComponent? hands)) - hands.ActivateItem(altInteract); - } - private bool HandleThrowItem(ICommonSession? session, EntityCoordinates coords, EntityUid uid) { if (session is not IPlayerSession playerSession) @@ -228,20 +201,20 @@ namespace Content.Server.Hands.Systems !Exists(player) || player.IsInContainer() || !TryComp(player, out SharedHandsComponent? hands) || - !hands.TryGetActiveHeldEntity(out var throwEnt) || + hands.ActiveHandEntity is not EntityUid throwEnt || !_actionBlockerSystem.CanThrow(player)) return false; - if (EntityManager.TryGetComponent(throwEnt.Value, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually) + if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually) { - var splitStack = _stackSystem.Split(throwEnt.Value, 1, EntityManager.GetComponent(player).Coordinates, stack); + var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent(player).Coordinates, stack); if (splitStack is not {Valid: true}) return false; throwEnt = splitStack.Value; } - else if (!hands.Drop(throwEnt.Value)) + else if (!TryDrop(player, throwEnt, handsComp: hands)) return false; var direction = coords.ToMapPos(EntityManager) - Transform(player).WorldPosition; @@ -251,7 +224,7 @@ namespace Content.Server.Hands.Systems direction = direction.Normalized * Math.Min(direction.Length, hands.ThrowRange); var throwStrength = hands.ThrowForceMultiplier; - throwEnt.Value.TryThrow(direction, throwStrength, player); + throwEnt.TryThrow(direction, throwStrength, player); return true; } @@ -287,7 +260,7 @@ namespace Content.Server.Hands.Systems return; } - if (hands.ActiveHandIsHoldingEntity()) + if (hands.ActiveHand?.HeldEntity != null) { storageComponent.PlayerInsertHeldEntity(plyEnt); } @@ -302,24 +275,11 @@ namespace Content.Server.Hands.Systems var lastStoredEntity = Enumerable.Last(storageComponent.StoredEntities); if (storageComponent.Remove(lastStoredEntity)) { - if (!hands.TryPickupEntityToActiveHand(lastStoredEntity, animateUser: true)) - Transform(lastStoredEntity).Coordinates = Transform(plyEnt).Coordinates; + PickupOrDrop(plyEnt, lastStoredEntity, animateUser: true, handsComp: hands); } } } } #endregion - - //TODO: Actually shows all items/clothing/etc. - private void HandleExamined(EntityUid uid, HandsComponent component, ExaminedEvent args) - { - foreach (var inhand in component.GetAllHeldItems()) - { - if (HasComp(inhand.Owner)) - continue; - - args.PushText(Loc.GetString("comp-hands-examine", ("user", component.Owner), ("item", inhand.Owner))); - } - } } } diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index f30ba3061b..7a87b0ea26 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -218,7 +218,7 @@ namespace Content.Server.Interaction // Verify user has a hand, and find what object they are currently holding in their active hand if (TryComp(user, out HandsComponent? hands)) { - var item = hands.GetActiveHandItem?.Owner; + var item = hands.ActiveHandEntity; if (item != null && !Deleted(item.Value)) { diff --git a/Content.Server/Jobs/GiveItemOnHolidaySpecial.cs b/Content.Server/Jobs/GiveItemOnHolidaySpecial.cs index 76c250ce0b..0b5a65bca9 100644 --- a/Content.Server/Jobs/GiveItemOnHolidaySpecial.cs +++ b/Content.Server/Jobs/GiveItemOnHolidaySpecial.cs @@ -1,12 +1,8 @@ -using Content.Server.Hands.Components; using Content.Server.Holiday; -using Content.Shared.Item; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Roles; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Jobs @@ -26,17 +22,16 @@ namespace Content.Server.Jobs if (string.IsNullOrEmpty(Holiday) || string.IsNullOrEmpty(Prototype)) return; - if (!EntitySystem.Get().IsCurrentlyHoliday(Holiday)) + var sysMan = IoCManager.Resolve(); + + if (!sysMan.GetEntitySystem().IsCurrentlyHoliday(Holiday)) return; var entMan = IoCManager.Resolve(); var entity = entMan.SpawnEntity(Prototype, entMan.GetComponent(mob).Coordinates); - if (!entMan.TryGetComponent(entity, out SharedItemComponent? item) || !entMan.TryGetComponent(mob, out HandsComponent? hands)) - return; - - hands.PutInHand(item, false); + sysMan.GetEntitySystem().PickupOrDrop(mob, entity); } } } diff --git a/Content.Server/Kitchen/Components/MicrowaveComponent.cs b/Content.Server/Kitchen/Components/MicrowaveComponent.cs index 784e0acecb..715b8a7a2c 100644 --- a/Content.Server/Kitchen/Components/MicrowaveComponent.cs +++ b/Content.Server/Kitchen/Components/MicrowaveComponent.cs @@ -216,7 +216,7 @@ namespace Content.Server.Kitchen.Components return false; } - if (_entities.GetComponent(eventArgs.User).GetActiveHandItem?.Owner is not {Valid: true} itemEntity) + if (_entities.GetComponent(eventArgs.User).ActiveHandEntity is not {Valid: true} itemEntity) { eventArgs.User.PopupMessage(Loc.GetString("microwave-component-interact-using-no-active-hand")); return false; diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 7b9b7900d4..66e79fcc7d 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Content.Server.Chemistry.EntitySystems; using Content.Server.Hands.Components; @@ -7,6 +7,7 @@ using Content.Server.Kitchen.Events; using Content.Server.Power.Components; using Content.Server.Stack; using Content.Server.UserInterface; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Kitchen.Components; @@ -16,10 +17,6 @@ using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -29,6 +26,7 @@ namespace Content.Server.Kitchen.EntitySystems internal sealed class ReagentGrinderSystem : EntitySystem { [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; private Queue _uiUpdateQueue = new(); @@ -267,7 +265,7 @@ namespace Content.Server.Kitchen.EntitySystems !EntityManager.TryGetComponent(beaker, out var item)) return; - hands.PutInHandOrDrop(item); + _handsSystem.PickupOrDrop(user.Value, beaker, handsComp: hands, item: item); component.HeldBeaker = null; EnqueueUiUpdate(component); diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index ef93e43537..b8e4f8b571 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -11,16 +11,13 @@ using Content.Shared.Audio; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Light; using Content.Shared.Popups; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -37,6 +34,7 @@ namespace Content.Server.Light.EntitySystems [Dependency] private readonly LightBulbSystem _bulbSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly AdminLogSystem _logSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); @@ -171,11 +169,7 @@ namespace Content.Server.Light.EntitySystems return null; // try to place bulb in hands - if (userUid != null) - { - if (EntityManager.TryGetComponent(userUid.Value, out SharedHandsComponent? hands)) - hands.PutInHand(bulb); - } + _handsSystem.PickupOrDrop(userUid, bulb); UpdateLight(uid, light); return bulb; diff --git a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs index 0bdc60c085..9ccf8712d3 100644 --- a/Content.Server/MachineLinking/System/SignalLinkerSystem.cs +++ b/Content.Server/MachineLinking/System/SignalLinkerSystem.cs @@ -123,7 +123,7 @@ namespace Content.Server.MachineLinking.System case SignalPortSelected portSelected: if (msg.Session.AttachedEntity == default || !EntityManager.TryGetComponent(msg.Session.AttachedEntity, out HandsComponent? hands) || - !hands.TryGetActiveHeldEntity(out var heldEntity) || + hands.ActiveHandEntity is not EntityUid heldEntity || !EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent) || !signalLinkerComponent.Port.HasValue || !signalLinkerComponent.Port.Value.transmitter.Outputs.ContainsPort(signalLinkerComponent.Port @@ -165,7 +165,7 @@ namespace Content.Server.MachineLinking.System case SignalPortSelected portSelected: if (msg.Session.AttachedEntity == default || !EntityManager.TryGetComponent(msg.Session.AttachedEntity, out HandsComponent? hands) || - !hands.TryGetActiveHeldEntity(out var heldEntity) || + hands.ActiveHandEntity is not EntityUid heldEntity || !EntityManager.TryGetComponent(heldEntity, out SignalLinkerComponent? signalLinkerComponent)) return; LinkerSaveInteraction(attached, signalLinkerComponent, component, diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index fc1323497a..81b9edd13b 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -7,7 +7,6 @@ using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Server.Nutrition.Components; using Content.Server.Popups; -using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Body.Components; using Content.Shared.Chemistry.Reagent; @@ -20,7 +19,7 @@ using Robust.Shared.Audio; using Robust.Shared.Player; using Robust.Shared.Utility; using Content.Shared.Inventory; -using Content.Shared.Item; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Events; namespace Content.Server.Nutrition.EntitySystems @@ -39,6 +38,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly SharedAdminLogSystem _logSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -223,18 +223,12 @@ namespace Content.Server.Nutrition.EntitySystems var finisher = EntityManager.SpawnEntity(component.TrashPrototype, position); // If the user is holding the item - if (user != null && - EntityManager.TryGetComponent(user.Value, out HandsComponent? handsComponent) && - handsComponent.IsHolding(component.Owner)) + if (user != null && _handsSystem.IsHolding(user.Value, component.Owner, out var hand)) { EntityManager.DeleteEntity((component).Owner); // Put the trash in the user's hand - if (EntityManager.TryGetComponent(finisher, out SharedItemComponent? item) && - handsComponent.CanPutInHand(item)) - { - handsComponent.PutInHand(item); - } + _handsSystem.TryPickup(user.Value, finisher, hand); return; } @@ -329,10 +323,10 @@ namespace Content.Server.Nutrition.EntitySystems var usedTypes = UtensilType.None; - foreach (var item in hands.GetAllHeldItems()) + foreach (var item in _handsSystem.EnumerateHeld(user, hands)) { // Is utensil? - if (!EntityManager.TryGetComponent(item.Owner, out UtensilComponent? utensil)) + if (!EntityManager.TryGetComponent(item, out UtensilComponent? utensil)) continue; if ((utensil.Types & component.Utensil) != 0 && // Acceptable type? diff --git a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs index d791831ef8..e2a290938d 100644 --- a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs @@ -5,13 +5,11 @@ using Content.Server.Nutrition.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Examine; using Content.Shared.FixedPoint; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Player; namespace Content.Server.Nutrition.EntitySystems @@ -19,6 +17,8 @@ namespace Content.Server.Nutrition.EntitySystems internal sealed class SliceableFoodSystem : EntitySystem { [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; public override void Initialize() { @@ -67,9 +67,9 @@ namespace Content.Server.Nutrition.EntitySystems if (EntityManager.TryGetComponent(user, out HandsComponent? handsComponent)) { - if (ContainerHelpers.IsInContainer(component.Owner)) + if (_containerSystem.IsEntityInContainer(component.Owner)) { - handsComponent.PutInHandOrDrop(EntityManager.GetComponent(sliceUid)); + _handsSystem.PickupOrDrop(user, sliceUid, handsComp: handsComponent); } } diff --git a/Content.Server/PDA/PDAExtensions.cs b/Content.Server/PDA/PDAExtensions.cs deleted file mode 100644 index dd37b9c14d..0000000000 --- a/Content.Server/PDA/PDAExtensions.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Server.Hands.Components; -using Content.Shared.Access.Components; -using Content.Shared.Inventory; -using Content.Shared.PDA; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.PDA -{ - public static class PdaExtensions - { - /// - /// Gets the id that a player is holding in their hands or inventory. - /// Order: Hands > ID slot > PDA in ID slot - /// - /// The player to check in. - /// The id card component. - public static IdCardComponent? GetHeldId(this EntityUid player) - { - IdCardComponent? foundPDAId = null; - - var entMan = IoCManager.Resolve(); - - if (entMan.TryGetComponent(player, out HandsComponent? hands)) - { - foreach (var item in hands.GetAllHeldItems()) - { - if (entMan.TryGetComponent(item.Owner, out PDAComponent? pda) && - pda.ContainedID != null) - { - foundPDAId = pda.ContainedID; - } - - if (entMan.TryGetComponent(item.Owner, out IdCardComponent? card)) - { - return card; - } - } - } - - if (foundPDAId != null) return foundPDAId; - - var invSystem = EntitySystem.Get(); - - if (invSystem.TryGetContainerSlotEnumerator(player, out var enumerator)) - { - while (enumerator.MoveNext(out var containerSlot)) - { - if(!containerSlot.ContainedEntity.HasValue) continue; - - if (entMan.TryGetComponent(containerSlot.ContainedEntity.Value, out PDAComponent? pda) && - pda.ContainedID != null) - { - foundPDAId = pda.ContainedID; - } - - if (entMan.TryGetComponent(containerSlot.ContainedEntity.Value, out IdCardComponent? card)) - { - return card; - } - } - } - - if (foundPDAId != null) return foundPDAId; - - return null; - } - - /// - /// Gets the id that a player is holding in their hands or inventory. - /// Order: Hands > ID slot > PDA in ID slot - /// - /// The player to check in. - /// The id card component. - /// true if found, false otherwise. - public static bool TryGetHeldId(this EntityUid player, [NotNullWhen(true)] out IdCardComponent? id) - { - return (id = player.GetHeldId()) != null; - } - } -} diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index 67cff1a7a5..2b07a6f106 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -11,6 +11,7 @@ using Content.Server.Throwing; using Content.Server.Tools.Components; using Content.Shared.Camera; using Content.Shared.CombatMode; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.PneumaticCannon; @@ -19,11 +20,7 @@ using Content.Shared.StatusEffect; using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Player; using Robust.Shared.Random; @@ -35,6 +32,7 @@ namespace Content.Server.PneumaticCannon [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly AtmosphereSystem _atmos = default!; [Dependency] private readonly CameraRecoilSystem _cameraRecoil = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; private HashSet _currentlyFiring = new(); @@ -320,11 +318,8 @@ namespace Content.Server.PneumaticCannon if (component.GasTankSlot.Remove(contained)) { - if (EntityManager.TryGetComponent(user, out var hands)) - { - hands.PutInHand(contained); - } - + _handsSystem.TryPickupAnyHand(user, contained); + user.PopupMessage(Loc.GetString("pneumatic-cannon-component-gas-tank-remove", ("tank", contained), ("cannon", component.Owner))); UpdateAppearance(component); diff --git a/Content.Server/Sandbox/SandboxSystem.cs b/Content.Server/Sandbox/SandboxSystem.cs index 1b4e7b1a8b..4a88298ff2 100644 --- a/Content.Server/Sandbox/SandboxSystem.cs +++ b/Content.Server/Sandbox/SandboxSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; using Content.Shared.Item; using Content.Shared.PDA; @@ -32,6 +33,7 @@ namespace Content.Server.Sandbox [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; [Dependency] private readonly GameTicker _ticker = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; private bool _isSandboxEnabled; @@ -154,7 +156,7 @@ namespace Content.Server.Sandbox var card = CreateFreshId(); if (!_inventory.TryEquip(attached, card, "id", true, true)) { - hands.PutInHandOrDrop(Comp(card)); + _handsSystem.PickupOrDrop(attached, card, handsComp: hands); } } diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index 9495ebd50e..ec0906f36a 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -1,6 +1,7 @@ using System; using Content.Server.Hands.Components; using Content.Server.Popups; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Stacks; @@ -25,6 +26,7 @@ namespace Content.Server.Stack { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 }; @@ -180,10 +182,7 @@ namespace Content.Server.Stack if (Split(uid, amount, userTransform.Coordinates, stack) is not {} split) return; - if (TryComp(userUid, out var hands) && TryComp(split, out var item)) - { - hands.PutInHandOrDrop(item); - } + _handsSystem.PickupOrDrop(userUid, split); _popupSystem.PopupCursor(Loc.GetString("comp-stack-split"), Filter.Entities(userUid)); } diff --git a/Content.Server/Standing/StandingStateSystem.cs b/Content.Server/Standing/StandingStateSystem.cs index e8e8a54fcb..c0b3bc4754 100644 --- a/Content.Server/Standing/StandingStateSystem.cs +++ b/Content.Server/Standing/StandingStateSystem.cs @@ -1,8 +1,6 @@ -using Content.Server.Hands.Components; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Standing; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; using Robust.Shared.Random; namespace Content.Server.Standing; @@ -10,22 +8,26 @@ namespace Content.Server.Standing; public sealed class StandingStateSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; private void FallOver(EntityUid uid, StandingStateComponent component, DropHandItemsEvent args) { var direction = EntityManager.TryGetComponent(uid, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero; var dropAngle = _random.NextFloat(0.8f, 1.2f); - if (!EntityManager.TryGetComponent(uid, out HandsComponent? hands)) + if (!TryComp(uid, out SharedHandsComponent? handsComp)) return; - foreach (var heldItem in hands.GetAllHeldItems()) + var worldRotation = EntityManager.GetComponent(uid).WorldRotation.ToVec(); + foreach (var hand in handsComp.Hands.Values) { - if (!hands.Drop(heldItem.Owner, false)) + if (hand.HeldEntity is not EntityUid held) continue; - var worldRotation = EntityManager.GetComponent(uid).WorldRotation.ToVec(); - Throwing.ThrowHelper.TryThrow(heldItem.Owner, + if (!_handsSystem.TryDrop(uid, hand, null, checkActionBlocker: false, handsComp: handsComp)) + continue; + + Throwing.ThrowHelper.TryThrow(held, _random.NextAngle().RotateVec(direction / dropAngle + worldRotation / 50), 0.5f * dropAngle * _random.NextFloat(-0.9f, 1.1f), uid, 0); diff --git a/Content.Server/Storage/Components/ServerStorageComponent.cs b/Content.Server/Storage/Components/ServerStorageComponent.cs index 9d22e21338..a569ecd42b 100644 --- a/Content.Server/Storage/Components/ServerStorageComponent.cs +++ b/Content.Server/Storage/Components/ServerStorageComponent.cs @@ -8,6 +8,7 @@ using Content.Server.Hands.Components; using Content.Server.Interaction; using Content.Shared.Acts; using Content.Shared.Coordinates; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Item; @@ -46,6 +47,7 @@ namespace Content.Server.Storage.Components public sealed class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IActivate, IStorageComponent, IDestroyAct, IExAct, IAfterInteract { [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; private const string LoggerName = "Storage"; @@ -243,22 +245,24 @@ namespace Content.Server.Storage.Components EnsureInitialCalculated(); if (!_entityManager.TryGetComponent(player, out HandsComponent? hands) || - hands.GetActiveHandItem == null) + hands.ActiveHandEntity == null) { return false; } - var toInsert = hands.GetActiveHandItem; + var toInsert = hands.ActiveHandEntity; - if (!hands.Drop(toInsert.Owner)) + var handSys = _sysMan.GetEntitySystem(); + + if (!handSys.TryDrop(player, toInsert.Value, handsComp: hands)) { Owner.PopupMessage(player, "Can't insert."); return false; } - if (!Insert(toInsert.Owner)) + if (!Insert(toInsert.Value)) { - hands.PutInHand(toInsert); + handSys.PickupOrDrop(player, toInsert.Value, handsComp: hands); Owner.PopupMessage(player, "Can't insert."); return false; } @@ -475,18 +479,7 @@ namespace Content.Server.Storage.Components break; } - if (!_entityManager.TryGetComponent(remove.EntityUid, out SharedItemComponent? item) || !_entityManager.TryGetComponent(player, out HandsComponent? hands)) - { - break; - } - - if (!hands.CanPutInHand(item)) - { - break; - } - - hands.PutInHand(item); - + _sysMan.GetEntitySystem().TryPickupAnyHand(player, remove.EntityUid); break; } case InsertEntityMessage _: diff --git a/Content.Server/Storage/EntitySystems/SecretStashSystem.cs b/Content.Server/Storage/EntitySystems/SecretStashSystem.cs index c5f715a1ea..012366d80e 100644 --- a/Content.Server/Storage/EntitySystems/SecretStashSystem.cs +++ b/Content.Server/Storage/EntitySystems/SecretStashSystem.cs @@ -1,8 +1,8 @@ -using Content.Server.Clothing.Components; using Content.Server.Popups; using Content.Server.Storage.Components; using Content.Shared.Acts; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Item; using Robust.Shared.Containers; using Robust.Shared.Player; @@ -12,6 +12,7 @@ namespace Content.Server.Storage.EntitySystems public sealed class SecretStashSystem : EntitySystem { [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -83,7 +84,7 @@ namespace Content.Server.Storage.EntitySystems } // try to move item from hands to stash container - if (!hands.Drop(itemToHideUid, container)) + if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container)) { return false; } @@ -115,13 +116,7 @@ namespace Content.Server.Storage.EntitySystems return false; } - // get item inside container - var itemUid = container.ContainedEntity; - if (!EntityManager.TryGetComponent(itemUid, out SharedItemComponent? item)) - { - return false; - } - hands.PutInHandOrDrop(item); + _handsSystem.PickupOrDrop(userUid, container.ContainedEntity.Value, handsComp: hands); // show success message var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something", diff --git a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs index a9402c4455..1b58b67b79 100644 --- a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs +++ b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs @@ -1,10 +1,7 @@ -using System.Collections.Generic; using Content.Server.Storage.Components; -using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Events; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Player; using Robust.Shared.Random; @@ -13,6 +10,7 @@ namespace Content.Server.Storage.EntitySystems public sealed class SpawnItemsOnUseSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -57,10 +55,9 @@ namespace Content.Server.Storage.EntitySystems EntityManager.DeleteEntity(uid); } - if (entityToPlaceInHands != null - && EntityManager.TryGetComponent(args.User, out var hands)) + if (entityToPlaceInHands != null) { - hands.TryPutInAnyHand(entityToPlaceInHands.Value); + _handsSystem.PickupOrDrop(args.User, entityToPlaceInHands.Value); } } } diff --git a/Content.Server/Strip/StrippableComponent.cs b/Content.Server/Strip/StrippableComponent.cs index 8b112e303d..92a8e3b51e 100644 --- a/Content.Server/Strip/StrippableComponent.cs +++ b/Content.Server/Strip/StrippableComponent.cs @@ -1,24 +1,18 @@ -using System.Collections.Generic; using System.Threading; using Content.Server.Cuffs.Components; using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Server.Inventory; using Content.Server.UserInterface; -using Content.Shared.ActionBlocker; using Content.Shared.DragDrop; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Strip.Components; using Robust.Server.GameObjects; using Robust.Server.Player; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.ViewVariables; namespace Content.Server.Strip { @@ -28,6 +22,7 @@ namespace Content.Server.Strip public sealed class StrippableComponent : SharedStrippableComponent { [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IEntitySystemManager _sysMan = default!; private StrippableSystem _strippableSystem = default!; public const float StripDelay = 2f; @@ -84,18 +79,18 @@ namespace Content.Server.Strip private async void PlaceActiveHandItemInInventory(EntityUid user, string slot) { var userHands = _entities.GetComponent(user); - var item = userHands.GetActiveHandItem; - var invSystem = EntitySystem.Get(); + var invSystem = _sysMan.GetEntitySystem(); + var handSys = _sysMan.GetEntitySystem(); bool Check() { - if (item == null) + if (userHands.ActiveHand?.HeldEntity is not EntityUid held) { user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); return false; } - if (!userHands.CanDrop(userHands.ActiveHand!)) + if (!handSys.CanDropHeld(user, userHands.ActiveHand)) { user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); return false; @@ -110,7 +105,7 @@ namespace Content.Server.Strip return false; } - if (!invSystem.CanEquip(user, Owner, item.Owner, slot, out _)) + if (!invSystem.CanEquip(user, Owner, held, slot, out _)) { user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-equip-message",("owner", Owner))); return false; @@ -134,8 +129,11 @@ namespace Content.Server.Strip var result = await doAfterSystem.WaitDoAfter(doAfterArgs); if (result != DoAfterStatus.Finished) return; - userHands.Drop(item!.Owner, false); - invSystem.TryEquip(user, Owner, item.Owner, slot); + if (userHands.ActiveHand?.HeldEntity is EntityUid held + && handSys.TryDrop(user, userHands.ActiveHand, handsComp: userHands)) + { + invSystem.TryEquip(user, Owner, held, slot); + } UpdateState(); } @@ -143,38 +141,28 @@ namespace Content.Server.Strip /// /// Places item in user's active hand in one of the entity's hands. /// - private async void PlaceActiveHandItemInHands(EntityUid user, string hand) + private async void PlaceActiveHandItemInHands(EntityUid user, string handName) { var hands = _entities.GetComponent(Owner); var userHands = _entities.GetComponent(user); - var item = userHands.GetActiveHandItem; + var sys = _sysMan.GetEntitySystem(); bool Check() { - if (item == null) + if (userHands.ActiveHandEntity == null) { user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); return false; } - if (!userHands.CanDrop(userHands.ActiveHand!)) + if (!sys.CanDropHeld(user, userHands.ActiveHand!)) { user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); return false; } - if (!hands.HasHand(hand)) - { - return false; - } - - if (hands.TryGetItem(hand, out var _)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-occupied-message", ("owner", Owner))); - return false; - } - - if (!hands.CanPickupEntity(hand, item.Owner, checkActionBlocker: false)) + if (!hands.Hands.TryGetValue(handName, out var hand) + || !sys.CanPickupToHand(Owner, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands)) { user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-put-message",("owner", Owner))); return false; @@ -183,7 +171,7 @@ namespace Content.Server.Strip return true; } - var doAfterSystem = EntitySystem.Get(); + var doAfterSystem = _sysMan.GetEntitySystem(); var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) { @@ -198,8 +186,11 @@ namespace Content.Server.Strip var result = await doAfterSystem.WaitDoAfter(doAfterArgs); if (result != DoAfterStatus.Finished) return; - userHands.Drop(hand); - hands.TryPickupEntity(hand, item!.Owner, checkActionBlocker: false, animateUser: true); + if (userHands.ActiveHandEntity is not EntityUid held) + return; + + sys.TryDrop(user, checkActionBlocker: false, handsComp: userHands); + sys.TryPickup(Owner, held, handName, checkActionBlocker: false, animateUser: true, handsComp: hands); // hand update will trigger strippable update } @@ -210,7 +201,7 @@ namespace Content.Server.Strip { var inventory = _entities.GetComponent(Owner); var userHands = _entities.GetComponent(user); - var invSystem = EntitySystem.Get(); + var invSystem = _sysMan.GetEntitySystem(); bool Check() { @@ -232,7 +223,7 @@ namespace Content.Server.Strip return true; } - var doAfterSystem = EntitySystem.Get(); + var doAfterSystem = _sysMan.GetEntitySystem(); var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) { @@ -250,7 +241,8 @@ namespace Content.Server.Strip { // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping _entities.EventBus.RaiseLocalEvent(item.Value, new DroppedEvent(user)); - userHands.PutInHandOrDrop(item.Value); + + _sysMan.GetEntitySystem().PickupOrDrop(user, item.Value); } UpdateState(); @@ -259,26 +251,24 @@ namespace Content.Server.Strip /// /// Takes an item from a hand and places it in the user's active hand. /// - private async void TakeItemFromHands(EntityUid user, string hand) + private async void TakeItemFromHands(EntityUid user, string handName) { var hands = _entities.GetComponent(Owner); var userHands = _entities.GetComponent(user); + var handSys = _sysMan.GetEntitySystem(); bool Check() { - if (!hands.HasHand(hand)) - return false; - - if (!hands.TryGetItem(hand, out var heldItem)) + if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity == null) { user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", Owner))); return false; } - if (_entities.HasComponent(heldItem.Owner)) + if (_entities.HasComponent(hand.HeldEntity)) return false; - if (!hands.CanDrop(hand, false)) + if (!handSys.CanDropHeld(Owner, hand, false)) { user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop-message",("owner", Owner))); return false; @@ -287,7 +277,7 @@ namespace Content.Server.Strip return true; } - var doAfterSystem = EntitySystem.Get(); + var doAfterSystem = _sysMan.GetEntitySystem(); var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) { @@ -301,11 +291,11 @@ namespace Content.Server.Strip var result = await doAfterSystem.WaitDoAfter(doAfterArgs); if (result != DoAfterStatus.Finished) return; - if (!hands.TryGetHeldEntity(hand, out var entity)) + if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity is not EntityUid held) return; - hands.Drop(hand, false); - userHands.PutInHandOrDrop(entity.Value); + handSys.TryDrop(Owner, hand, checkActionBlocker: false, handsComp: hands); + handSys.PickupOrDrop(user, held, handsComp: userHands); // hand update will trigger strippable update } @@ -315,14 +305,14 @@ namespace Content.Server.Strip !_entities.TryGetComponent(user, out HandsComponent? userHands)) return; - var placingItem = userHands.GetActiveHandItem != null; + var placingItem = userHands.ActiveHandEntity != null; switch (obj.Message) { case StrippingInventoryButtonPressed inventoryMessage: if (_entities.TryGetComponent(Owner, out var inventory)) { - if (EntitySystem.Get().TryGetSlotEntity(Owner, inventoryMessage.Slot, out _, inventory)) + if (_sysMan.GetEntitySystem().TryGetSlotEntity(Owner, inventoryMessage.Slot, out _, inventory)) placingItem = false; if (placingItem) @@ -336,7 +326,7 @@ namespace Content.Server.Strip if (_entities.TryGetComponent(Owner, out var hands)) { - if (hands.TryGetItem(handMessage.Hand, out _)) + if (hands.Hands.TryGetValue(handMessage.Hand, out var hand) && !hand.IsEmpty) placingItem = false; if (placingItem) diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 7f93355b41..49d4c0a8a1 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -76,17 +76,15 @@ namespace Content.Server.Strip if (TryComp(uid, out HandsComponent? handsComp)) { - foreach (var hand in handsComp.HandNames) + foreach (var hand in handsComp.Hands.Values) { - var owner = handsComp.GetItem(hand)?.Owner; - - if (!owner.HasValue || HasComp(owner.Value)) + if (hand.HeldEntity == null || HasComp(hand.HeldEntity)) { - hands[hand] = "None"; + hands[hand.Name] = "None"; continue; } - hands[hand] = Name(owner.Value); + hands[hand.Name] = Name(hand.HeldEntity.Value); } } diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs index 538441500c..da24a4e269 100644 --- a/Content.Server/Traitor/Uplink/UplinkSystem.cs +++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Traitor.Uplink.Components; using Content.Server.UserInterface; using Content.Shared.ActionBlocker; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Item; @@ -28,6 +29,7 @@ namespace Content.Server.Traitor.Uplink private readonly UplinkListingSytem _listing = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -123,11 +125,7 @@ namespace Content.Server.Traitor.Uplink return; } - if (EntityManager.TryGetComponent(player, out HandsComponent? hands) && - EntityManager.TryGetComponent(entity.Value, out SharedItemComponent? item)) - { - hands.PutInHandOrDrop(item); - } + _handsSystem.PickupOrDrop(player, entity.Value); SoundSystem.Play(Filter.SinglePlayer(message.Session), uplink.BuySuccessSound.GetSound(), uplink.Owner, AudioParams.Default.WithVolume(-8f)); @@ -149,8 +147,7 @@ namespace Content.Server.Traitor.Uplink return; // try to put it into players hands - if (EntityManager.TryGetComponent(player, out SharedHandsComponent? hands)) - hands.TryPutInAnyHand(tcUid.Value); + _handsSystem.PickupOrDrop(player, tcUid.Value); // play buying sound SoundSystem.Play(Filter.SinglePlayer(args.Session), uplink.BuySuccessSound.GetSound(), @@ -217,14 +214,10 @@ namespace Content.Server.Traitor.Uplink } // Also check hands - if (EntityManager.TryGetComponent(user, out HandsComponent? hands)) + foreach (var item in _handsSystem.EnumerateHeld(user)) { - var heldItems = hands.GetAllHeldItems(); - foreach (var item in heldItems) - { - if (EntityManager.HasComponent(item.Owner)) - return item.Owner; - } + if (HasComp(item)) + return item; } return null; diff --git a/Content.Server/Verbs/VerbSystem.cs b/Content.Server/Verbs/VerbSystem.cs index bdb82fdb76..56b89e5975 100644 --- a/Content.Server/Verbs/VerbSystem.cs +++ b/Content.Server/Verbs/VerbSystem.cs @@ -102,7 +102,7 @@ namespace Content.Server.Verbs // first get the held item. again. EntityUid? holding = null; if (TryComp(user, out SharedHandsComponent? hands) && - hands.TryGetActiveHeldEntity(out var heldEntity)) + hands.ActiveHandEntity is EntityUid heldEntity) { holding = heldEntity; } diff --git a/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs b/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs index 176fc913f3..8806665f97 100644 --- a/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs +++ b/Content.Server/Weapon/Ranged/GunSystem.AmmoBox.cs @@ -152,15 +152,10 @@ public sealed partial class GunSystem return false; } - if (TryComp(ammo, out ItemComponent? item)) + if (!_handsSystem.TryPickup(user, ammo, handsComp: handsComponent)) { - if (!handsComponent.CanPutInHand(item)) - { - TryInsertAmmo(user, ammo, ammoBox); - return false; - } - - handsComponent.PutInHand(item); + TryInsertAmmo(user, ammo, ammoBox); + return false; } UpdateAmmoBoxAppearance(ammoBox.Owner, ammoBox); diff --git a/Content.Server/Weapon/Ranged/GunSystem.Guns.cs b/Content.Server/Weapon/Ranged/GunSystem.Guns.cs index 6f6f2d3781..2f82e33b23 100644 --- a/Content.Server/Weapon/Ranged/GunSystem.Guns.cs +++ b/Content.Server/Weapon/Ranged/GunSystem.Guns.cs @@ -36,7 +36,7 @@ public sealed partial class GunSystem { if (!TryComp(gun.Owner, out ServerRangedBarrelComponent? barrel)) return; - if (!TryComp(user, out HandsComponent? hands) || hands.GetActiveHand()?.HeldEntity != gun.Owner) return; + if (!TryComp(user, out HandsComponent? hands) || hands.ActiveHand?.HeldEntity != gun.Owner) return; if (!TryComp(user, out CombatModeComponent? combat) || !combat.IsInCombatMode || diff --git a/Content.Server/Weapon/Ranged/GunSystem.Magazine.cs b/Content.Server/Weapon/Ranged/GunSystem.Magazine.cs index c2c02245db..c81c94d6ce 100644 --- a/Content.Server/Weapon/Ranged/GunSystem.Magazine.cs +++ b/Content.Server/Weapon/Ranged/GunSystem.Magazine.cs @@ -28,8 +28,8 @@ public sealed partial class GunSystem if (args.Hands == null || !args.CanAccess || !args.CanInteract || - !component.HasMagazine || - !_blocker.CanPickup(args.User)) + component.MagazineContainer.ContainedEntity is not EntityUid mag || + !_blocker.CanPickup(args.User, mag)) return; if (component.MagNeedsOpenBolt && !component.BoltOpen) @@ -37,7 +37,7 @@ public sealed partial class GunSystem AlternativeVerb verb = new() { - Text = MetaData(component.MagazineContainer.ContainedEntity!.Value).EntityName, + Text = MetaData(mag).EntityName, Category = VerbCategory.Eject, Act = () => RemoveMagazine(args.User, component) }; @@ -325,10 +325,7 @@ public sealed partial class GunSystem component.MagazineContainer.Remove(mag.Value); SoundSystem.Play(Filter.Pvs(component.Owner), component.SoundMagEject.GetSound(), component.Owner, AudioParams.Default.WithVolume(-2)); - if (TryComp(user, out HandsComponent? handsComponent)) - { - handsComponent.PutInHandOrDrop(EntityManager.GetComponent(mag.Value)); - } + _handsSystem.PickupOrDrop(user, mag.Value); component.Dirty(EntityManager); UpdateMagazineAppearance(component); diff --git a/Content.Server/Weapon/Ranged/GunSystem.RangedMagazine.cs b/Content.Server/Weapon/Ranged/GunSystem.RangedMagazine.cs index a57be68810..831f2832b0 100644 --- a/Content.Server/Weapon/Ranged/GunSystem.RangedMagazine.cs +++ b/Content.Server/Weapon/Ranged/GunSystem.RangedMagazine.cs @@ -70,16 +70,8 @@ public sealed partial class GunSystem if (TakeAmmo(component) is not {Valid: true} ammo) return; - var itemComponent = EntityManager.GetComponent(ammo); - if (!handsComponent.CanPutInHand(itemComponent)) - { - Transform(ammo).Coordinates = Transform(args.User).Coordinates; - EjectCasing(ammo); - } - else - { - handsComponent.PutInHand(itemComponent); - } + _handsSystem.PickupOrDrop(args.User, ammo, handsComp: handsComponent); + EjectCasing(ammo); args.Handled = true; } diff --git a/Content.Server/Weapon/Ranged/GunSystem.SpeedLoader.cs b/Content.Server/Weapon/Ranged/GunSystem.SpeedLoader.cs index 0df95b9d44..ee5ba3eb9f 100644 --- a/Content.Server/Weapon/Ranged/GunSystem.SpeedLoader.cs +++ b/Content.Server/Weapon/Ranged/GunSystem.SpeedLoader.cs @@ -75,15 +75,10 @@ public sealed partial class GunSystem return; } - var itemComponent = EntityManager.GetComponent(ammo.Value); - if (!handsComponent.CanPutInHand(itemComponent)) + if (!_handsSystem.TryPickup(args.User, ammo.Value, handsComp: handsComponent)) { EjectCasing(ammo.Value); } - else - { - handsComponent.PutInHand(itemComponent); - } UpdateSpeedLoaderAppearance(component); args.Handled = true; diff --git a/Content.Server/Weapon/Ranged/GunSystem.cs b/Content.Server/Weapon/Ranged/GunSystem.cs index 076ebe7cc2..a801ab5175 100644 --- a/Content.Server/Weapon/Ranged/GunSystem.cs +++ b/Content.Server/Weapon/Ranged/GunSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Camera; using Content.Shared.Damage; using Content.Shared.Examine; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; @@ -47,6 +48,7 @@ public sealed partial class GunSystem : EntitySystem [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly StunSystem _stun = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; /// /// How many sounds are allowed to be played on ejecting multiple casings. @@ -141,7 +143,7 @@ public sealed partial class GunSystem : EntitySystem return; // TODO: Not exactly robust - var gun = handsComponent.GetActiveHand()?.HeldEntity; + var gun = handsComponent.ActiveHand?.HeldEntity; if (gun == null || !TryComp(gun, out ServerRangedWeaponComponent? weapon)) return; diff --git a/Content.Server/Wieldable/WieldableSystem.cs b/Content.Server/Wieldable/WieldableSystem.cs index 9f99aa2710..e0da96aa4f 100644 --- a/Content.Server/Wieldable/WieldableSystem.cs +++ b/Content.Server/Wieldable/WieldableSystem.cs @@ -4,15 +4,15 @@ using Content.Server.Hands.Systems; using Content.Server.Weapon.Melee; using Content.Server.Wieldable.Components; using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Popups; using Content.Shared.Verbs; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Player; +using System.Linq; namespace Content.Server.Wieldable { @@ -20,6 +20,7 @@ namespace Content.Server.Wieldable { [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -75,7 +76,8 @@ namespace Content.Server.Wieldable return false; } - if (hands.GetFreeHands() < component.FreeHandsRequired) + if (hands.CountFreeHands() + < component.FreeHandsRequired) { // TODO FLUENT need function to change 'hands' to 'hand' when there's only 1 required if (!quiet) @@ -89,7 +91,7 @@ namespace Content.Server.Wieldable } // Is it.. actually in one of their hands? - if (!hands.TryGetHandHoldingEntity(uid, out _)) + if (!_handsSystem.IsHolding(user, uid, out _, hands)) { if (!quiet) { diff --git a/Content.Server/WireHacking/WiresComponent.cs b/Content.Server/WireHacking/WiresComponent.cs index 7e8021420c..ac99a481f1 100644 --- a/Content.Server/WireHacking/WiresComponent.cs +++ b/Content.Server/WireHacking/WiresComponent.cs @@ -338,7 +338,7 @@ namespace Content.Server.WireHacking return false; } - if (handsComponent.GetActiveHand()?.HeldEntity is not { Valid: true } activeHandEntity || + if (handsComponent.ActiveHand?.HeldEntity is not { Valid: true } activeHandEntity || !_entities.TryGetComponent(activeHandEntity, out tool)) { return false; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs index 29888fc7d4..4bc28eb063 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs @@ -1,7 +1,6 @@ -using Content.Server.Clothing.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; -using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Robust.Shared.Random; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; @@ -9,6 +8,7 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; public sealed class SpawnArtifactSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -40,12 +40,7 @@ public sealed class SpawnArtifactSystem : EntitySystem // if there is an user - try to put spawned item in their hands // doesn't work for spawners - if (args.Activator != null && - EntityManager.TryGetComponent(args.Activator.Value, out SharedHandsComponent? hands) && - EntityManager.HasComponent(spawned)) - { - hands.TryPutInAnyHand(spawned); - } + _handsSystem.PickupOrDrop(args.Activator, spawned); } private void ChooseRandomPrototype(EntityUid uid, SpawnArtifactComponent? component = null) diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 4cfab5ae2b..3f8f193e79 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Shared.Hands.Components; using Content.Shared.Inventory; using Content.Shared.Emag.Systems; using Content.Shared.PDA; using Content.Shared.Access.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Prototypes; +using Content.Shared.Hands.EntitySystems; namespace Content.Shared.Access.Systems { @@ -18,6 +13,7 @@ namespace Content.Shared.Access.Systems { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public override void Initialize() { @@ -54,7 +50,7 @@ namespace Content.Shared.Access.Systems /// /// If no access is found, an empty set is used instead. /// - /// The entity to be searched for access. + /// The entity to bor access. public bool IsAllowed(AccessReaderComponent reader, EntityUid entity) { var tags = FindAccessTags(entity); @@ -78,14 +74,10 @@ namespace Content.Shared.Access.Systems if (FindAccessTagsItem(uid, out var tags)) return tags; - // maybe access component inside its hands? - if (EntityManager.TryGetComponent(uid, out SharedHandsComponent? hands)) + foreach (var item in _handsSystem.EnumerateHeld(uid)) { - if (hands.TryGetActiveHeldEntity(out var heldItem) && - FindAccessTagsItem(heldItem.Value, out tags)) - { + if (FindAccessTagsItem(item, out tags)) return tags; - } } // maybe its inside an inventory slot? diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index 186ca6cdfb..7df72f4946 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -95,12 +95,18 @@ namespace Content.Shared.ActionBlocker return !ev.Cancelled; } - public bool CanPickup(EntityUid uid) + public bool CanPickup(EntityUid user, EntityUid item) { - var ev = new PickupAttemptEvent(uid); - RaiseLocalEvent(uid, ev); + var userEv = new PickupAttemptEvent(user, item); + RaiseLocalEvent(user, userEv, false); + + if (userEv.Cancelled) + return false; + + var itemEv = new GettingPickedUpAttemptEvent(user, item); + RaiseLocalEvent(item, itemEv, false); + return !itemEv.Cancelled; - return !ev.Cancelled; } public bool CanEmote(EntityUid uid) diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 0b987e575c..8b6fb2c22b 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Acts; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; @@ -8,14 +9,9 @@ using Content.Shared.Sound; using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Player; using Robust.Shared.Timing; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Content.Shared.Containers.ItemSlots @@ -27,6 +23,7 @@ namespace Content.Shared.Containers.ItemSlots { [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() @@ -175,11 +172,11 @@ namespace Content.Shared.Containers.ItemSlots continue; // Drop the held item onto the floor. Return if the user cannot drop. - if (!hands.Drop(args.Used)) + if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands)) return; if (slot.Item != null) - hands.TryPutInAnyHand(slot.Item.Value); + _handsSystem.TryPickupAnyHand(args.User, slot.Item.Value, handsComp: hands); Insert(uid, slot, args.Used, args.User, excludeUserAudio: args.Predicted); args.Handled = true; @@ -287,18 +284,17 @@ namespace Content.Shared.Containers.ItemSlots if (!Resolve(user, ref hands, false)) return false; - if (!hands.TryGetActiveHeldEntity(out var item)) + if (hands.ActiveHand?.HeldEntity is not EntityUid held) return false; - var heldItem = item.Value; - if (!CanInsert(uid, item.Value, slot)) + if (!CanInsert(uid, held, slot)) return false; // hands.Drop(item) checks CanDrop action blocker - if (hands.Drop(heldItem)) + if (_handsSystem.TryDrop(user, hands.ActiveHand)) return false; - Insert(uid, slot, heldItem, user); + Insert(uid, slot, held, user); return true; } #endregion @@ -374,8 +370,8 @@ namespace Content.Shared.Containers.ItemSlots if (!TryEject(uid, slot, user, out var item, excludeUserAudio)) return false; - if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands)) - hands.PutInHand(item.Value); + if (user != null) + _handsSystem.PickupOrDrop(user.Value, item.Value); return true; } @@ -384,25 +380,27 @@ namespace Content.Shared.Containers.ItemSlots #region Verbs private void AddEjectVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetVerbsEvent args) { - if (args.Hands == null || !args.CanAccess ||!args.CanInteract || - !_actionBlockerSystem.CanPickup(args.User)) + if (args.Hands == null || !args.CanAccess ||!args.CanInteract) { return; } foreach (var slot in itemSlots.Slots.Values) { - if (!CanEject(slot)) - continue; - if (slot.EjectOnInteract) // For this item slot, ejecting/inserting is a primary interaction. Instead of an eject category // alt-click verb, there will be a "Take item" primary interaction verb. continue; + if (!CanEject(slot)) + continue; + + if (!_actionBlockerSystem.CanPickup(args.User, slot.Item!.Value)) + continue; + var verbSubject = slot.Name != string.Empty ? Loc.GetString(slot.Name) - : EntityManager.GetComponent(slot.Item!.Value).EntityName ?? string.Empty; + : EntityManager.GetComponent(slot.Item.Value).EntityName ?? string.Empty; AlternativeVerb verb = new(); verb.IconEntity = slot.Item; @@ -428,28 +426,28 @@ namespace Content.Shared.Containers.ItemSlots return; // If there are any slots that eject on left-click, add a "Take " verb. - if (_actionBlockerSystem.CanPickup(args.User)) + foreach (var slot in itemSlots.Slots.Values) { - foreach (var slot in itemSlots.Slots.Values) - { - if (!slot.EjectOnInteract || !CanEject(slot)) - continue; + if (!slot.EjectOnInteract || !CanEject(slot)) + continue; - var verbSubject = slot.Name != string.Empty - ? Loc.GetString(slot.Name) - : EntityManager.GetComponent(slot.Item!.Value).EntityName ?? string.Empty; + if (!_actionBlockerSystem.CanPickup(args.User, slot.Item!.Value)) + continue; - InteractionVerb takeVerb = new(); - takeVerb.IconEntity = slot.Item; - takeVerb.Act = () => TryEjectToHands(uid, slot, args.User, excludeUserAudio: true); + var verbSubject = slot.Name != string.Empty + ? Loc.GetString(slot.Name) + : EntityManager.GetComponent(slot.Item!.Value).EntityName ?? string.Empty; - if (slot.EjectVerbText == null) - takeVerb.Text = Loc.GetString("take-item-verb-text", ("subject", verbSubject)); - else - takeVerb.Text = Loc.GetString(slot.EjectVerbText); + InteractionVerb takeVerb = new(); + takeVerb.IconEntity = slot.Item; + takeVerb.Act = () => TryEjectToHands(uid, slot, args.User, excludeUserAudio: true); - args.Verbs.Add(takeVerb); - } + if (slot.EjectVerbText == null) + takeVerb.Text = Loc.GetString("take-item-verb-text", ("subject", verbSubject)); + else + takeVerb.Text = Loc.GetString(slot.EjectVerbText); + + args.Verbs.Add(takeVerb); } // Next, add the insert-item verbs diff --git a/Content.Shared/Hands/Components/HandHelpers.cs b/Content.Shared/Hands/Components/HandHelpers.cs new file mode 100644 index 0000000000..1cf337cc4c --- /dev/null +++ b/Content.Shared/Hands/Components/HandHelpers.cs @@ -0,0 +1,34 @@ +using System.Linq; + +namespace Content.Shared.Hands.Components; + +/// +/// These helpers exist to make getting basic information out of the hands component more convenient, without +/// needing to resolve hands system or something like that. +/// +public static class HandHelpers +{ + /// + /// Returns true if any hand is free. This is a LinQ method, not a property, so + /// cache it instead of accessing this multiple times. + /// + public static bool IsAnyHandFree(this SharedHandsComponent component) => component.Hands.Values.Any(hand => hand.IsEmpty); + + /// + /// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so + /// cache it instead of accessing this multiple times. + /// + public static int CountFreeHands(this SharedHandsComponent component) => component.Hands.Values.Count(hand => hand.IsEmpty); + + /// + /// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache + /// it instead of accessing this multiple times. + /// + public static IEnumerable GetFreeHands(this SharedHandsComponent component) => component.Hands.Values.Where(hand => !hand.IsEmpty); + + /// + /// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache + /// it instead of accessing this multiple times. + /// + public static IEnumerable GetFreeHandNames(this SharedHandsComponent component) => GetFreeHands(component).Select(hand => hand.Name); +} diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index dff5d0a54f..afcde19535 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -1,825 +1,99 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Content.Shared.ActionBlocker; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Item; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Physics; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; -using static Robust.Shared.GameObjects.SharedSpriteComponent; -namespace Content.Shared.Hands.Components +namespace Content.Shared.Hands.Components; + +[NetworkedComponent] +public abstract class SharedHandsComponent : Component { - [NetworkedComponent] - public abstract class SharedHandsComponent : Component - { - [Dependency] private readonly IEntityManager _entMan = default!; - - /// - /// The name of the currently active hand. - /// - [ViewVariables] - public string? ActiveHand; - - [ViewVariables] - public List Hands = new(); - - /// - /// The amount of throw impulse per distance the player is from the throw target. - /// - [DataField("throwForceMultiplier")] - [ViewVariables(VVAccess.ReadWrite)] - public float ThrowForceMultiplier { get; set; } = 10f; //should be tuned so that a thrown item lands about under the player's cursor - - /// - /// Distance after which longer throw targets stop increasing throw impulse. - /// - [DataField("throwRange")] - [ViewVariables(VVAccess.ReadWrite)] - public float ThrowRange { get; set; } = 8f; - - private bool PlayerCanDrop => EntitySystem.Get().CanDrop(Owner); - private bool PlayerCanPickup => EntitySystem.Get().CanPickup(Owner); - - public void AddHand(string handName, HandLocation handLocation) - { - if (HasHand(handName)) - return; - - var container = Owner.EnsureContainer(handName); - container.OccludesLight = false; - - Hands.Add(new Hand(handName, handLocation, container)); - - if (ActiveHand == null) - EntitySystem.Get().TrySetActiveHand(Owner, handName, this); - - HandCountChanged(); - - Dirty(); - } - - public void RemoveHand(string handName) - { - if (!TryGetHand(handName, out var hand)) - return; - - RemoveHand(hand); - } - - private void RemoveHand(Hand hand) - { - DropHeldEntityToFloor(hand); - hand.Container?.Shutdown(); - Hands.Remove(hand); - - if (ActiveHand == hand.Name) - EntitySystem.Get().TrySetActiveHand(Owner, Hands.FirstOrDefault()?.Name, this); - - HandCountChanged(); - - Dirty(); - } - - public Hand? GetActiveHand() - { - if (ActiveHand == null) - return null; - - return GetHandOrNull(ActiveHand); - } - - public bool HasHand(string handName) - { - return TryGetHand(handName, out _); - } - - public Hand? GetHandOrNull(string handName) - { - return TryGetHand(handName, out var hand) ? hand : null; - } - - public Hand GetHand(string handName) - { - if (!TryGetHand(handName, out var hand)) - throw new KeyNotFoundException($"Unable to find hand with name {handName}"); - - return hand; - } - - public bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand) - { - foreach (var hand in Hands) - { - if (hand.Name == handName) - { - foundHand = hand; - return true; - }; - } - - foundHand = null; - return false; - } - - public bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand) - { - activeHand = GetActiveHand(); - return activeHand != null; - } - - #region Held Entities - - public bool ActiveHandIsHoldingEntity() - { - if (!TryGetActiveHand(out var hand)) - return false; - - return hand.HeldEntity != null; - } - - public bool TryGetHeldEntity(string handName, [NotNullWhen(true)] out EntityUid? heldEntity) - { - heldEntity = null; - - if (!TryGetHand(handName, out var hand)) - return false; - - heldEntity = hand.HeldEntity; - return heldEntity != null; - } - - public bool TryGetActiveHeldEntity([NotNullWhen(true)] out EntityUid? heldEntity) - { - heldEntity = GetActiveHand()?.HeldEntity; - return heldEntity != null; - } - - public bool IsHolding(EntityUid entity) - { - foreach (var hand in Hands) - { - if (hand.HeldEntity == entity) - return true; - } - return false; - } - - public IEnumerable GetAllHeldEntities() - { - foreach (var hand in Hands) - { - if (hand.HeldEntity != null) - yield return hand.HeldEntity.Value; - } - } - - /// - /// Returns the number of hands that have no items in them. - /// - /// - public int GetFreeHands() - { - int acc = 0; - foreach (var hand in Hands) - { - if (hand.HeldEntity == null) - acc += 1; - } - - return acc; - } - - public bool TryGetHandHoldingEntity(EntityUid entity, [NotNullWhen(true)] out Hand? handFound) - { - handFound = null; - - foreach (var hand in Hands) - { - if (hand.HeldEntity == entity) - { - handFound = hand; - return true; - } - } - return false; - } - - #endregion - - #region Dropping - - /// - /// Checks all the conditions relevant to a player being able to drop an item. - /// - public bool CanDrop(string handName, bool checkActionBlocker = true) - { - if (!TryGetHand(handName, out var hand)) - return false; - - if (!CanRemoveHeldEntityFromHand(hand)) - return false; - - if (checkActionBlocker && !PlayerCanDrop) - return false; - - return true; - } - - /// - /// Tries to drop the contents of the active hand to the target location. - /// - public bool TryDropActiveHand(EntityCoordinates targetDropLocation, bool doMobChecks = true) - { - if (!TryGetActiveHand(out var hand)) - return false; - - return TryDropHeldEntity(hand, targetDropLocation, doMobChecks); - } - - /// - /// Tries to drop the contents of a hand to the target location. - /// - public bool TryDropHand(string handName, EntityCoordinates targetDropLocation, bool checkActionBlocker = true) - { - if (!TryGetHand(handName, out var hand)) - return false; - - return TryDropHeldEntity(hand, targetDropLocation, checkActionBlocker); - } - - /// - /// Tries to drop a held entity to the target location. - /// - public bool TryDropEntity(EntityUid entity, EntityCoordinates coords, bool doMobChecks = true) - { - if (!TryGetHandHoldingEntity(entity, out var hand)) - return false; - - return TryDropHeldEntity(hand, coords, doMobChecks); - } - - /// - /// Attempts to move the contents of a hand into a container that is not another hand, without dropping it on the floor inbetween. - /// - public bool TryPutHandIntoContainer(string handName, BaseContainer targetContainer, bool checkActionBlocker = true) - { - if (!TryGetHand(handName, out var hand)) - return false; - - if (!CanPutHeldEntityIntoContainer(hand, targetContainer, checkActionBlocker)) - return false; - - PutHeldEntityIntoContainer(hand, targetContainer); - return true; - } - - /// - /// Attempts to move a held item from a hand into a container that is not another hand, without dropping it on the floor in-between. - /// - public bool Drop(EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true) - { - if (!TryGetHandHoldingEntity(entity, out var hand)) - return false; - - if (!CanPutHeldEntityIntoContainer(hand, targetContainer, checkActionBlocker)) - return false; - - PutHeldEntityIntoContainer(hand, targetContainer); - return true; - } - - /// - /// Tries to drop the contents of a hand directly under the player. - /// - public bool Drop(string handName, bool checkActionBlocker = true) - { - if (!TryGetHand(handName, out var hand)) - return false; - - return TryDropHeldEntity(hand, _entMan.GetComponent(Owner).Coordinates, checkActionBlocker); - } - - /// - /// Tries to drop a held entity directly under the player. - /// - public bool Drop(EntityUid entity, bool checkActionBlocker = true) - { - if (!TryGetHandHoldingEntity(entity, out var hand)) - return false; - - return TryDropHeldEntity(hand, _entMan.GetComponent(Owner).Coordinates, checkActionBlocker); - } - - /// - /// Tries to remove the item in the active hand, without dropping it. - /// For transferring the held item to another location, like an inventory slot, - /// which shouldn't trigger the drop interaction - /// - public bool TryDropNoInteraction() - { - if (!TryGetActiveHand(out var hand)) - return false; - - if (!CanRemoveHeldEntityFromHand(hand)) - return false; - - EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); - return true; - } - - /// - /// Checks if the contents of a hand is able to be removed from its container. - /// - private bool CanRemoveHeldEntityFromHand(Hand hand) - { - if (hand.HeldEntity == null) - return false; - - if (!hand.Container!.CanRemove(hand.HeldEntity.Value)) - return false; - - return true; - } - - /// - /// Drops a hands contents to the target location. - /// - public void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation) - { - if (hand.HeldEntity == null) - return; - - var heldEntity = hand.HeldEntity.Value; - - EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); - - EntitySystem.Get().DroppedInteraction(Owner, heldEntity); - - _entMan.GetComponent(heldEntity).WorldPosition = GetFinalDropCoordinates(targetDropLocation); - } - - /// - /// Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path. - /// - private Vector2 GetFinalDropCoordinates(EntityCoordinates targetCoords) - { - var origin = _entMan.GetComponent(Owner).MapPosition; - var target = targetCoords.ToMap(_entMan); - - var dropVector = target.Position - origin.Position; - var requestedDropDistance = dropVector.Length; - - if (dropVector.Length > SharedInteractionSystem.InteractionRange) - { - dropVector = dropVector.Normalized * SharedInteractionSystem.InteractionRange; - target = new MapCoordinates(origin.Position + dropVector, target.MapId); - } - - - var dropLength = EntitySystem.Get().UnobstructedDistance(origin, target, predicate: e => e == Owner); - - if (dropLength < requestedDropDistance) - return origin.Position + dropVector.Normalized * dropLength; - return target.Position; - } - - /// - /// Tries to drop a hands contents to the target location. - /// - private bool TryDropHeldEntity(Hand hand, EntityCoordinates location, bool checkActionBlocker) - { - if (!CanRemoveHeldEntityFromHand(hand)) - return false; - - if (checkActionBlocker && !PlayerCanDrop) - return false; - - DropHeldEntity(hand, location); - return true; - } - - /// - /// Drops the contents of a hand directly under the player. - /// - private void DropHeldEntityToFloor(Hand hand) - { - DropHeldEntity(hand, _entMan.GetComponent(Owner).Coordinates); - } - - private bool CanPutHeldEntityIntoContainer(Hand hand, IContainer targetContainer, bool checkActionBlocker) - { - if (hand.HeldEntity == null) - return false; - - var heldEntity = hand.HeldEntity.Value; - - if (checkActionBlocker && !PlayerCanDrop) - return false; - - if (!targetContainer.CanInsert(heldEntity)) - return false; - - return true; - } - - /// - /// For putting the contents of a hand into a container that is not another hand. - /// - private void PutHeldEntityIntoContainer(Hand hand, IContainer targetContainer) - { - if (hand.HeldEntity == null) - return; - - var heldEntity = hand.HeldEntity.Value; - - EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); - - if (!targetContainer.Insert(heldEntity)) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {Owner} could not insert {heldEntity} into {targetContainer}."); - return; - } - } - - #endregion - - #region Pickup - - public bool CanPickupEntity(string handName, EntityUid entity, bool checkActionBlocker = true) - { - if (!TryGetHand(handName, out var hand)) - return false; - - if (checkActionBlocker && !PlayerCanPickup) - return false; - - if (!CanInsertEntityIntoHand(hand, entity)) - return false; - - return true; - } - - public bool CanPickupEntityToActiveHand(EntityUid entity, bool checkActionBlocker = true) - { - return ActiveHand != null && CanPickupEntity(ActiveHand, entity, checkActionBlocker); - } - - /// - /// Tries to pick up an entity to a specific hand. - /// - public bool TryPickupEntity(string handName, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false) - { - if (!TryGetHand(handName, out var hand)) - return false; - - return TryPickupEntity(hand, entity, checkActionBlocker, animateUser); - } - - public bool TryPickupEntityToActiveHand(EntityUid entity, bool checkActionBlocker = true, bool animateUser = false) - { - return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker, animateUser); - } - - /// - /// Checks if an entity can be put into a hand's container. - /// - protected bool CanInsertEntityIntoHand(Hand hand, EntityUid entity) - { - var handContainer = hand.Container; - if (handContainer == null) return false; - - if (!_entMan.HasComponent(entity)) - return false; - - if (_entMan.TryGetComponent(entity, out IPhysBody? physics) && physics.BodyType == BodyType.Static) - return false; - - if (!handContainer.CanInsert(entity)) return false; - - var @event = new AttemptItemPickupEvent(); - _entMan.EventBus.RaiseLocalEvent(entity, @event); - - if (@event.Cancelled) return false; - - return true; - } - - private bool TryPickupEntity(Hand hand, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false) - { - if (!CanInsertEntityIntoHand(hand, entity)) - return false; - - if (checkActionBlocker && !PlayerCanPickup) - return false; - - // animation - var handSys = EntitySystem.Get(); - var coordinateEntity = _entMan.GetComponent(Owner).Parent?.Owner ?? Owner; - var initialPosition = EntityCoordinates.FromMap(coordinateEntity, _entMan.GetComponent(entity).MapPosition); - var finalPosition = _entMan.GetComponent(Owner).LocalPosition; - - handSys.PickupAnimation(entity, initialPosition, finalPosition, animateUser ? null : Owner); - handSys.PutEntityIntoHand(Owner, hand, entity, this); - - return true; - } - - #endregion - - #region Hand Interactions - - /// - /// Get the name of the hand that a swap hands would result in. - /// - public bool TryGetSwapHandsResult([NotNullWhen(true)] out string? nextHand) - { - nextHand = null; - - if (!TryGetActiveHand(out var activeHand) || Hands.Count == 1) - return false; - - var newActiveIndex = Hands.IndexOf(activeHand) + 1; - var finalHandIndex = Hands.Count - 1; - if (newActiveIndex > finalHandIndex) - newActiveIndex = 0; - - nextHand = Hands[newActiveIndex].Name; - return true; - } - - /// - /// Attempts to interact with the item in a hand using the active held item. - /// - public void InteractHandWithActiveHand(string handName) - { - if (!TryGetActiveHeldEntity(out var activeHeldEntity)) - return; - - if (!TryGetHeldEntity(handName, out var heldEntity)) - return; - - if (activeHeldEntity == heldEntity) - return; - - EntitySystem.Get() - .InteractUsing(Owner, activeHeldEntity.Value, heldEntity.Value, EntityCoordinates.Invalid); - } - - public void ActivateItem(bool altInteract = false) - { - if (!TryGetActiveHeldEntity(out var heldEntity)) - return; - - var sys = EntitySystem.Get(); - if (altInteract) - sys.AltInteract(Owner, heldEntity.Value); - else - sys.UseInHandInteraction(Owner, heldEntity.Value); - } - - public void ActivateHeldEntity(string handName) - { - if (!TryGetHeldEntity(handName, out var heldEntity)) - return; - - EntitySystem.Get() - .InteractionActivate(Owner, heldEntity.Value); - } - - /// - /// Moves an entity from one hand to the active hand. - /// - public bool TryMoveHeldEntityToActiveHand(string handName, bool checkActionBlocker = true) - { - if (!TryGetHand(handName, out var hand) || !TryGetActiveHand(out var activeHand)) - return false; - - if (!TryGetHeldEntity(handName, out var heldEntity)) - return false; - - if (!CanInsertEntityIntoHand(activeHand, heldEntity.Value) || !CanRemoveHeldEntityFromHand(hand)) - return false; - - if (checkActionBlocker && (!PlayerCanDrop || !PlayerCanPickup)) - return false; - - EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); - EntitySystem.Get().PutEntityIntoHand(Owner, activeHand, heldEntity.Value, this); - return true; - } - - #endregion - - private void HandCountChanged() - { - _entMan.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner)); - } - - /// - /// Tries to pick up an entity into the active hand. If it cannot, tries to pick up the entity into each other hand. - /// - public bool PutInHand(SharedItemComponent item, bool checkActionBlocker = true) - { - return PutInHand(item.Owner, checkActionBlocker); - } - - /// - /// Puts an item any hand, preferring the active hand, or puts it on the floor under the player. - /// - public void PutInHandOrDrop(EntityUid entity, bool checkActionBlocker = true) - { - if (PutInHand(entity, checkActionBlocker)) - return; - - _entMan.GetComponent(entity).AttachParentToContainerOrGrid(_entMan); - _entMan.EventBus.RaiseLocalEvent(entity, new DroppedEvent(Owner)); - } - - public void PutInHandOrDrop(SharedItemComponent item, bool checkActionBlocker = true) - { - PutInHandOrDrop(item.Owner, checkActionBlocker); - } - - /// - /// Tries to pick up an entity into the active hand. If it cannot, tries to pick up the entity into each other hand. - /// - public bool PutInHand(EntityUid entity, bool checkActionBlocker = true) - { - return PutInHand(entity, GetActiveHand(), checkActionBlocker); - } - - /// - /// Tries to pick up an entity into the priority hand, if provided. If it cannot, tries to pick up the entity into each other hand. - /// - public bool TryPutInAnyHand(EntityUid entity, string? priorityHandName = null, bool checkActionBlocker = true) - { - Hand? priorityHand = null; - - if (priorityHandName != null) - priorityHand = GetHandOrNull(priorityHandName); - - return PutInHand(entity, priorityHand, checkActionBlocker); - } - - /// - /// Tries to pick up an entity into the priority hand, if provided. If it cannot, tries to pick up the entity into each other hand. - /// - private bool PutInHand(EntityUid entity, Hand? priorityHand = null, bool checkActionBlocker = true) - { - if (priorityHand != null) - { - if (TryPickupEntity(priorityHand, entity, checkActionBlocker)) - return true; - } - - foreach (var hand in Hands) - { - if (TryPickupEntity(hand, entity, checkActionBlocker)) - return true; - } - return false; - } - - /// - /// Checks if any hand can pick up an item. - /// - public bool CanPutInHand(SharedItemComponent item, bool mobCheck = true) - { - var entity = item.Owner; - - if (mobCheck && !PlayerCanPickup) - return false; - - foreach (var hand in Hands) - { - if (CanInsertEntityIntoHand(hand, entity)) - return true; - } - return false; - } - } - - [Serializable, NetSerializable] - public sealed class Hand - { - [ViewVariables] - public string Name { get; } - - [ViewVariables] - public HandLocation Location { get; } - - /// - /// The container used to hold the contents of this hand. Nullable because the client must get the containers via , - /// which may not be synced with the server when the client hands are created. - /// - [ViewVariables, NonSerialized] - public ContainerSlot? Container; - - [ViewVariables] - public EntityUid? HeldEntity => Container?.ContainedEntity; - - public bool IsEmpty => HeldEntity == null; - - public Hand(string name, HandLocation location, ContainerSlot? container = null) - { - Name = name; - Location = location; - Container = container; - } - } - - [Serializable, NetSerializable] - public sealed class HandsComponentState : ComponentState - { - public List Hands { get; } - public string? ActiveHand { get; } - - public HandsComponentState(List hands, string? activeHand = null) - { - Hands = hands; - ActiveHand = activeHand; - } - } + /// + /// The currently active hand. + /// + [ViewVariables] + public Hand? ActiveHand; /// - /// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand. + /// The item currently held in the active hand. /// - [Serializable, NetSerializable] - public sealed class UseInHandMsg : EntityEventArgs - { - } + [ViewVariables] + public EntityUid? ActiveHandEntity => ActiveHand?.HeldEntity; + + [ViewVariables] + public Dictionary Hands = new(); + + public int Count => Hands.Count; /// - /// A message that calls the activate interaction on the item in the specified hand. + /// List of hand-names. These are keys for . The order of this list determines the order in which hands are iterated over. /// - [Serializable, NetSerializable] - public sealed class ActivateInHandMsg : EntityEventArgs - { - public string HandName { get; } - - public ActivateInHandMsg(string handName) - { - HandName = handName; - } - } + public List SortedHands = new(); /// - /// Uses the item in the active hand on the item in the specified hand. + /// The amount of throw impulse per distance the player is from the throw target. /// - [Serializable, NetSerializable] - public sealed class ClientInteractUsingInHandMsg : EntityEventArgs - { - public string HandName { get; } - - public ClientInteractUsingInHandMsg(string handName) - { - HandName = handName; - } - } + [DataField("throwForceMultiplier")] + [ViewVariables(VVAccess.ReadWrite)] + public float ThrowForceMultiplier { get; set; } = 10f; //should be tuned so that a thrown item lands about under the player's cursor /// - /// Moves an item from one hand to the active hand. + /// Distance after which longer throw targets stop increasing throw impulse. /// - [Serializable, NetSerializable] - public sealed class MoveItemFromHandMsg : EntityEventArgs - { - public string HandName { get; } + [DataField("throwRange")] + [ViewVariables(VVAccess.ReadWrite)] + public float ThrowRange { get; set; } = 8f; +} - public MoveItemFromHandMsg(string handName) - { - HandName = handName; - } - } +[Serializable, NetSerializable] +public sealed class Hand +{ + [ViewVariables] + public string Name { get; } + + [ViewVariables] + public HandLocation Location { get; } /// - /// What side of the body this hand is on. + /// The container used to hold the contents of this hand. Nullable because the client must get the containers via , + /// which may not be synced with the server when the client hands are created. /// - public enum HandLocation : byte - { - Left, - Middle, - Right - } + [ViewVariables, NonSerialized] + public ContainerSlot? Container; - public sealed class HandCountChangedEvent : EntityEventArgs - { - public HandCountChangedEvent(EntityUid sender) - { - Sender = sender; - } + [ViewVariables] + public EntityUid? HeldEntity => Container?.ContainedEntity; - public EntityUid Sender { get; } + public bool IsEmpty => HeldEntity == null; + + public Hand(string name, HandLocation location, ContainerSlot? container = null) + { + Name = name; + Location = location; + Container = container; } } + +[Serializable, NetSerializable] +public sealed class HandsComponentState : ComponentState +{ + public readonly List Hands; + public readonly List HandNames; + public readonly string? ActiveHand; + + public HandsComponentState(SharedHandsComponent handComp) + { + Hands = new(handComp.Hands.Values); + HandNames = handComp.SortedHands; + ActiveHand = handComp.ActiveHand?.Name; + } +} + +/// +/// What side of the body this hand is on. +/// +public enum HandLocation : byte +{ + Left, + Middle, + Right +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs new file mode 100644 index 0000000000..ad2b2b9bda --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs @@ -0,0 +1,39 @@ +using Content.Shared.Hands.Components; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Shared.Hands.EntitySystems; + +// These functions are mostly unused except for some AI operator stuff +// Nothing stops them from being used in general. If they ever get used elsewhere, then this file probably needs to be renamed. + +public abstract partial class SharedHandsSystem : EntitySystem +{ + public bool TrySelect(EntityUid uid, EntityUid? entity, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (!IsHolding(uid, entity, out var hand, handsComp)) + return false; + + SetActiveHand(uid, hand, handsComp); + return true; + } + + public bool TrySelect(EntityUid uid, [NotNullWhen(true)] out TComponent? component, SharedHandsComponent? handsComp = null) where TComponent : Component + { + component = null; + if (!Resolve(uid, ref handsComp, false)) + return false; + + foreach (var hand in handsComp.Hands.Values) + { + if (TryComp(hand.HeldEntity, out component)) + return true; + } + + return false; + } + + public bool TrySelectEmptyHand(EntityUid uid, SharedHandsComponent? handsComp = null) => TrySelect(uid, null, handsComp); +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs new file mode 100644 index 0000000000..5419d721c4 --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs @@ -0,0 +1,159 @@ +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Robust.Shared.Containers; +using Robust.Shared.Map; + +namespace Content.Shared.Hands.EntitySystems; + +public abstract partial class SharedHandsSystem : EntitySystem +{ + /// + /// Checks if the contents of a hand is able to be removed from its container. + /// + public bool CanDropHeld(EntityUid uid, Hand hand, bool checkActionBlocker = true) + { + if (hand.HeldEntity == null) + return false; + + if (!hand.Container!.CanRemove(hand.HeldEntity.Value, EntityManager)) + return false; + + if (checkActionBlocker && !_actionBlocker.CanDrop(uid)) + return false; + + return true; + } + + /// + /// Attempts to drop the item in the currently active hand. + /// + public bool TryDrop(EntityUid uid, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp)) + return false; + + if (handsComp.ActiveHand == null) + return false; + + return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp); + } + + /// + /// Drops an item at the target location. + /// + public bool TryDrop(EntityUid uid, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp)) + return false; + + if (!IsHolding(uid, entity, out var hand, handsComp)) + return false; + + return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp); + } + + /// + /// Drops a hands contents at the target location. + /// + public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp)) + return false; + + if (!CanDropHeld(uid, hand, checkActionBlocker)) + return false; + + var entity = hand.HeldEntity!.Value; + DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp); + + var xform = Transform(uid); + + if (targetDropLocation == null) + { + // TODO recursively check upwards for containers + Transform(entity).AttachParentToContainerOrGrid(EntityManager); + return true; + } + + var target = targetDropLocation.Value.ToMap(EntityManager); + Transform(entity).WorldPosition = GetFinalDropCoordinates(uid, xform.MapPosition, target); + return true; + } + + /// + /// Attempts to move a held item from a hand into a container that is not another hand, without dropping it on the floor in-between. + /// + public bool TryDropIntoContainer(EntityUid uid, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp)) + return false; + + if (!IsHolding(uid, entity, out var hand, handsComp)) + return false; + + if (!CanDropHeld(uid, hand, checkActionBlocker)) + return false; + + if (!targetContainer.CanInsert(entity, EntityManager)) + return false; + + DoDrop(uid, hand, false, handsComp); + targetContainer.Insert(entity); + return true; + } + + /// + /// Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path. + /// + private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, MapCoordinates target) + { + var dropVector = target.Position - origin.Position; + var requestedDropDistance = dropVector.Length; + + if (dropVector.Length > SharedInteractionSystem.InteractionRange) + { + dropVector = dropVector.Normalized * SharedInteractionSystem.InteractionRange; + target = new MapCoordinates(origin.Position + dropVector, target.MapId); + } + + var dropLength = _interactionSystem.UnobstructedDistance(origin, target, predicate: e => e == user); + + if (dropLength < requestedDropDistance) + return origin.Position + dropVector.Normalized * dropLength; + return target.Position; + } + + /// + /// Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly. + /// + public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp)) + return; + + if (hand.Container?.ContainedEntity == null) + return; + + var entity = hand.Container.ContainedEntity.Value; + + if (!hand.Container!.Remove(entity, EntityManager)) + { + Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not remove {entity} from {hand.Container}."); + return; + } + + Dirty(handsComp); + + if (doDropInteraction) + _interactionSystem.DroppedInteraction(uid, entity); + + var gotUnequipped = new GotUnequippedHandEvent(uid, entity, hand); + RaiseLocalEvent(entity, gotUnequipped, false); + + var didUnequip = new DidUnequipHandEvent(uid, entity, hand); + RaiseLocalEvent(uid, didUnequip); + + if (hand == handsComp.ActiveHand) + RaiseLocalEvent(entity, new HandDeselectedEvent(uid), false); + } +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs new file mode 100644 index 0000000000..81307ccf26 --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -0,0 +1,172 @@ +using Content.Shared.Examine; +using Content.Shared.Hands.Components; +using Content.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.Map; +using Robust.Shared.Players; + +namespace Content.Shared.Hands.EntitySystems; + +public abstract partial class SharedHandsSystem : EntitySystem +{ + private void InitializeInteractions() + { + SubscribeAllEvent(HandleSetHand); + SubscribeAllEvent(HandleActivateItemInHand); + SubscribeAllEvent(HandleInteractUsingInHand); + SubscribeAllEvent(HandleUseInHand); + SubscribeAllEvent(HandleMoveItemFromHand); + + SubscribeLocalEvent(HandleExamined); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.UseItemInHand, InputCmdHandler.FromDelegate(HandleUseItem, handle: false)) + .Bind(ContentKeyFunctions.AltUseItemInHand, InputCmdHandler.FromDelegate(HandleAltUseInHand, handle: false)) + .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false)) + .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) + .Register(); + } + + #region Event and Key-binding Handlers + private void HandleAltUseInHand(ICommonSession? session) + { + if (session?.AttachedEntity != null) + TryUseItemInHand(session.AttachedEntity.Value, true); + } + + private void HandleUseItem(ICommonSession? session) + { + if (session?.AttachedEntity != null) + TryUseItemInHand(session.AttachedEntity.Value); + } + + private void HandleMoveItemFromHand(RequestMoveHandItemEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity != null) + TryMoveHeldEntityToActiveHand(args.SenderSession.AttachedEntity.Value, msg.HandName); + } + + private void HandleUseInHand(RequestUseInHandEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity != null) + TryUseItemInHand(args.SenderSession.AttachedEntity.Value); + } + + private void HandleActivateItemInHand(RequestActivateInHandEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity != null) + TryActivateItemInHand(args.SenderSession.AttachedEntity.Value); + } + + private void HandleInteractUsingInHand(RequestHandInteractUsingEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity != null) + TryInteractHandWithActiveHand(args.SenderSession.AttachedEntity.Value, msg.HandName); + } + + private void SwapHandsPressed(ICommonSession? session) + { + if (!TryComp(session?.AttachedEntity, out SharedHandsComponent? component)) + return; + + if (component.ActiveHand == null || component.Hands.Count < 2) + return; + + var newActiveIndex = component.SortedHands.IndexOf(component.ActiveHand.Name) + 1; + var nextHand = component.SortedHands[newActiveIndex % component.Hands.Count]; + + TrySetActiveHand(component.Owner, nextHand, component); + } + + private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + if (TryComp(session?.AttachedEntity, out SharedHandsComponent? hands) && hands.ActiveHand != null) + TryDrop(session!.AttachedEntity!.Value, hands.ActiveHand, coords, handsComp: hands); + + // always send to server. + return false; + } + #endregion + + public bool TryActivateItemInHand(EntityUid uid, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (handsComp.ActiveHandEntity == null) + return false; + + return _interactionSystem.InteractionActivate(uid, handsComp.ActiveHandEntity.Value); + } + + public bool TryInteractHandWithActiveHand(EntityUid uid, string handName, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (handsComp.ActiveHandEntity == null) + return false; + + if (!handsComp.Hands.TryGetValue(handName, out var hand)) + return false; + + if (hand.HeldEntity == null) + return false; + + _interactionSystem.InteractUsing(uid, handsComp.ActiveHandEntity.Value, hand.HeldEntity.Value, Transform(hand.HeldEntity.Value).Coordinates); + return true; + } + + public bool TryUseItemInHand(EntityUid uid, bool altInteract = false, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (handsComp.ActiveHandEntity == null) + return false; + + if (altInteract) + return _interactionSystem.AltInteract(uid, handsComp.ActiveHandEntity.Value); + else + return _interactionSystem.UseInHandInteraction(uid, handsComp.ActiveHandEntity.Value); + } + + /// + /// Moves an entity from one hand to the active hand. + /// + public bool TryMoveHeldEntityToActiveHand(EntityUid uid, string handName, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp)) + return false; + + if (handsComp.ActiveHand == null || !handsComp.ActiveHand.IsEmpty) + return false; + + if (!handsComp.Hands.TryGetValue(handName, out var hand)) + return false; + + if (!CanDropHeld(uid, hand, checkActionBlocker)) + return false; + + var entity = hand.HeldEntity!.Value; + + if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp)) + return false; + + DoDrop(uid, hand, false, handsComp); + DoPickup(uid, handsComp.ActiveHand, entity, handsComp); + return true; + } + + //TODO: Actually shows all items/clothing/etc. + private void HandleExamined(EntityUid uid, SharedHandsComponent handsComp, ExaminedEvent args) + { + foreach (var inhand in EnumerateHeld(uid, handsComp)) + { + if (HasComp(inhand)) + continue; + + args.PushText(Loc.GetString("comp-hands-examine", ("user", handsComp.Owner), ("item", inhand))); + } + } +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs new file mode 100644 index 0000000000..f642b3860c --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -0,0 +1,161 @@ +using Content.Shared.Database; +using Content.Shared.Hands.Components; +using Content.Shared.Item; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Physics; + +namespace Content.Shared.Hands.EntitySystems; + +public abstract partial class SharedHandsSystem : EntitySystem +{ + /// + /// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand. + /// + public bool TryPickup(EntityUid uid, EntityUid entity, string? handName = null, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + var hand = handsComp.ActiveHand; + if (handName != null && !handsComp.Hands.TryGetValue(handName, out hand)) + return false; + + if (hand == null) + return false; + + return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, handsComp, item); + } + + /// + /// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand. + /// + /// + /// If one empty hand fails to pick up the item, this will NOT check other hands. If ever hand-specific item + /// restrictions are added, there a might need to be a TryPickupAllHands or something like that. + /// + public bool TryPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (!TryGetEmptyHand(uid, out var hand, handsComp)) + return false; + + return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, handsComp, item); + } + + public bool TryPickup(EntityUid uid, EntityUid entity, Hand hand, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (!Resolve(entity, ref item, false)) + return false; + + if (!CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item)) + return false; + + // animation + var xform = Transform(uid); + var coordinateEntity = xform.ParentUid.IsValid() ? xform.ParentUid : uid; + var initialPosition = EntityCoordinates.FromMap(EntityManager, coordinateEntity, Transform(entity).MapPosition); + + PickupAnimation(entity, initialPosition, xform.LocalPosition, animateUser ? null : uid); + DoPickup(uid, hand, entity, handsComp); + + return true; + } + + public bool CanPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + if (!TryGetEmptyHand(uid, out var hand, handsComp)) + return false; + + return CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item); + } + + /// + /// Checks whether a given item will fit into a specific user's hand. Unless otherwise specified, this will also check the general CanPickup action blocker. + /// + public bool CanPickupToHand(EntityUid uid, EntityUid entity, Hand hand, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) + { + if (!Resolve(uid, ref handsComp, false)) + return false; + + var handContainer = hand.Container; + if (handContainer == null || handContainer.ContainedEntity != null) + return false; + + if (!Resolve(entity, ref item, false)) + return false; + + if (TryComp(entity, out PhysicsComponent? physics) && physics.BodyType == BodyType.Static) + return false; + + if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity)) + return false; + + // check can insert (including raising attempt events). + return handContainer.CanInsert(entity, EntityManager); + } + + /// + /// Puts an item any hand, preferring the active hand, or puts it on the floor. + /// + public void PickupOrDrop(EntityUid? uid, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) + { + if (uid == null + || !Resolve(uid.Value, ref handsComp, false) + || !TryGetEmptyHand(uid.Value, out var hand, handsComp) + || !TryPickup(uid.Value, entity, hand, checkActionBlocker, animateUser, handsComp, item)) + { + // TODO make this check upwards for any container, and parent to that. + // Currently this just checks the direct parent, so items can still teleport through containers. + Transform(entity).AttachParentToContainerOrGrid(EntityManager); + } + } + + /// + /// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly. + /// + public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, SharedHandsComponent? hands = null) + { + if (!Resolve(uid, ref hands)) + return; + + var handContainer = hand.Container; + if (handContainer == null || handContainer.ContainedEntity != null) + return; + + if (!handContainer.Insert(entity, EntityManager)) + { + Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not insert {entity} into {handContainer}."); + return; + } + + _adminLogSystem.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}"); + + Dirty(hands); + + var didEquip = new DidEquipHandEvent(uid, entity, hand); + RaiseLocalEvent(uid, didEquip, false); + + var gotEquipped = new GotEquippedHandEvent(uid, entity, hand); + RaiseLocalEvent(entity, gotEquipped); + + // TODO this should REALLY be a cancellable thing, not a handled event. + // If one of the interactions resulted in the item being dropped, return early. + if (gotEquipped.Handled) + return; + + if (hand == hands.ActiveHand) + RaiseLocalEvent(entity, new HandSelectedEvent(uid), false); + } + + public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, + EntityUid? exclude); +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs new file mode 100644 index 0000000000..5a6636fc85 --- /dev/null +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -0,0 +1,209 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Administration.Logs; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Robust.Shared.Containers; +using Robust.Shared.Input.Binding; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Shared.Hands.EntitySystems; + +public abstract partial class SharedHandsSystem : EntitySystem +{ + [Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + InitializeInteractions(); + } + + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); + } + + public void AddHand(EntityUid uid, string handName, HandLocation handLocation, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + return; + + if (handsComp.Hands.ContainsKey(handName)) + return; + + var container = _containerSystem.EnsureContainer(uid, handName); + container.OccludesLight = false; + + var newHand = new Hand(handName, handLocation, container); + handsComp.Hands.Add(handName, newHand); + handsComp.SortedHands.Add(handName); + + if (handsComp.ActiveHand == null) + SetActiveHand(uid, newHand, handsComp); + + RaiseLocalEvent(uid, new HandCountChangedEvent(uid), false); + Dirty(handsComp); + } + + public void RemoveHand(EntityUid uid, string handName, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + return; + + if (!handsComp.Hands.Remove(handName, out var hand)) + return; + + TryDrop(uid, hand, null, false, true, handsComp); + hand.Container?.Shutdown(); + handsComp.SortedHands.Remove(hand.Name); + + if (handsComp.ActiveHand == hand) + TrySetActiveHand(uid, handsComp.SortedHands.FirstOrDefault(), handsComp); + + RaiseLocalEvent(uid, new HandCountChangedEvent(uid), false); + Dirty(handsComp); + } + + private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) + { + if (eventArgs.SenderSession.AttachedEntity == null) + return; + + TrySetActiveHand(eventArgs.SenderSession.AttachedEntity.Value, msg.HandName); + } + + /// + /// Get any empty hand. Prioritizes the currently active hand. + /// + public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHand, SharedHandsComponent? handComp = null) + { + emptyHand = null; + if (!Resolve(uid, ref handComp, false)) + return false; + + foreach (var hand in EnumerateHands(uid, handComp)) + { + if (hand.IsEmpty) + { + emptyHand = hand; + return true; + } + } + + return false; + } + + /// + /// Enumerate over hands, starting with the currently active hand. + /// + public IEnumerable EnumerateHands(EntityUid uid, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + yield break; + + if (handsComp.ActiveHand != null) + yield return handsComp.ActiveHand; + + foreach (var name in handsComp.SortedHands) + { + if (name != handsComp.ActiveHand?.Name) + yield return handsComp.Hands[name]; + } + } + + /// + /// Enumerate over hands, with the active hand being first. + /// + public IEnumerable EnumerateHeld(EntityUid uid, SharedHandsComponent? handsComp = null) + { + if (!Resolve(uid, ref handsComp, false)) + yield break; + + if (handsComp.ActiveHandEntity != null) + yield return handsComp.ActiveHandEntity.Value; + + foreach (var name in handsComp.SortedHands) + { + if (name == handsComp.ActiveHand?.Name) + continue; + + if (handsComp.Hands[name].HeldEntity is EntityUid held) + yield return held; + } + } + + /// + /// Set the currently active hand and raise hand (de)selection events directed at the held entities. + /// + /// True if the active hand was set to a NEW value. Setting it to the same value returns false and does + /// not trigger interactions. + public virtual bool TrySetActiveHand(EntityUid uid, string? name, SharedHandsComponent? handComp = null) + { + if (!Resolve(uid, ref handComp)) + return false; + + if (name == handComp.ActiveHand?.Name) + return false; + + Hand? hand = null; + if (name != null && !handComp.Hands.TryGetValue(name, out hand)) + return false; + + return SetActiveHand(uid, hand, handComp); + } + + /// + /// Set the currently active hand and raise hand (de)selection events directed at the held entities. + /// + /// True if the active hand was set to a NEW value. Setting it to the same value returns false and does + /// not trigger interactions. + public virtual bool SetActiveHand(EntityUid uid, Hand? hand, SharedHandsComponent? handComp = null) + { + if (!Resolve(uid, ref handComp)) + return false; + + if (hand == handComp.ActiveHand) + return false; + + if (handComp.ActiveHand?.HeldEntity is EntityUid held) + RaiseLocalEvent(held, new HandDeselectedEvent(uid), false); + + if (hand == null) + { + handComp.ActiveHand = null; + return true; + } + + handComp.ActiveHand = hand; + + if (hand.HeldEntity != null) + RaiseLocalEvent(hand.HeldEntity.Value, new HandSelectedEvent(uid), false); + + Dirty(handComp); + return true; + } + + public bool IsHolding(EntityUid uid, EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, SharedHandsComponent? handsComp = null) + { + inHand = null; + if (!Resolve(uid, ref handsComp, false)) + return false; + + foreach (var hand in handsComp.Hands.Values) + { + if (hand.HeldEntity == entity) + { + inHand = hand; + return true; + } + } + + return false; + } +} diff --git a/Content.Shared/Hands/HandEvents.cs b/Content.Shared/Hands/HandEvents.cs index 91bb906a2c..881c420437 100644 --- a/Content.Shared/Hands/HandEvents.cs +++ b/Content.Shared/Hands/HandEvents.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Content.Shared.Hands.Components; using JetBrains.Annotations; -using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Serialization; using static Robust.Shared.GameObjects.SharedSpriteComponent; + namespace Content.Shared.Hands { /// @@ -73,15 +70,9 @@ namespace Content.Shared.Hands /// public EntityUid User { get; } - /// - /// Item in the hand that was deselected. - /// - public EntityUid Item { get; } - - public HandDeselectedEvent(EntityUid user, EntityUid item) + public HandDeselectedEvent(EntityUid user) { User = user; - Item = item; } } @@ -96,15 +87,9 @@ namespace Content.Shared.Hands /// public EntityUid User { get; } - /// - /// Item in the hand that was selected. - /// - public EntityUid Item { get; } - - public HandSelectedEvent(EntityUid user, EntityUid item) + public HandSelectedEvent(EntityUid user) { User = user; - Item = item; } } @@ -231,4 +216,64 @@ namespace Content.Shared.Hands { public DidUnequipHandEvent(EntityUid user, EntityUid unequipped, Hand hand) : base(user, unequipped, hand) { } } + + /// + /// Event raised by a client when they want to use the item currently held in their hands. + /// + [Serializable, NetSerializable] + public sealed class RequestUseInHandEvent : EntityEventArgs + { + } + + /// + /// Event raised by a client when they want to activate the item currently in their hands. + /// + [Serializable, NetSerializable] + public sealed class RequestActivateInHandEvent : EntityEventArgs + { + public string HandName { get; } + + public RequestActivateInHandEvent(string handName) + { + HandName = handName; + } + } + + /// + /// Event raised by a client when they want to use the currently held item on some other held item + /// + [Serializable, NetSerializable] + public sealed class RequestHandInteractUsingEvent : EntityEventArgs + { + public string HandName { get; } + + public RequestHandInteractUsingEvent(string handName) + { + HandName = handName; + } + } + + /// + /// Event raised by a client when they want to move an item held in another hand to their currently active hand + /// + [Serializable, NetSerializable] + public sealed class RequestMoveHandItemEvent : EntityEventArgs + { + public string HandName { get; } + + public RequestMoveHandItemEvent(string handName) + { + HandName = handName; + } + } + + public sealed class HandCountChangedEvent : EntityEventArgs + { + public HandCountChangedEvent(EntityUid sender) + { + Sender = sender; + } + + public EntityUid Sender { get; } + } } diff --git a/Content.Shared/Hands/SharedHandsSystem.cs b/Content.Shared/Hands/SharedHandsSystem.cs deleted file mode 100644 index 2a1166d944..0000000000 --- a/Content.Shared/Hands/SharedHandsSystem.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Content.Shared.Administration.Logs; -using Content.Shared.Database; -using Content.Shared.Hands.Components; -using Content.Shared.Input; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.Input.Binding; -using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Players; - -namespace Content.Shared.Hands -{ - public abstract class SharedHandsSystem : EntitySystem - { - [Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeAllEvent(HandleSetHand); - SubscribeLocalEvent(HandleContainerRemoved); - SubscribeLocalEvent(HandleContainerInserted); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) - .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false)) - .Register(); - } - - public override void Shutdown() - { - base.Shutdown(); - CommandBinds.Unregister(); - } - - #region interactions - private void SwapHandsPressed(ICommonSession? session) - { - if (!TryComp(session?.AttachedEntity, out SharedHandsComponent? hands)) - return; - - if (!hands.TryGetSwapHandsResult(out var nextHand)) - return; - - TrySetActiveHand(hands.Owner, nextHand, hands); - } - - private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) - { - if (TryComp(session?.AttachedEntity, out SharedHandsComponent? hands)) - hands.TryDropActiveHand(coords); - - return false; - } - #endregion - - #region EntityInsertRemove - /// - /// Removes the contents of a hand from its container. Assumes that the removal is allowed. - /// - public virtual void RemoveHeldEntityFromHand(EntityUid uid, Hand hand, SharedHandsComponent? hands = null) - { - if (!Resolve(uid, ref hands)) - return; - - if (hand.Container?.ContainedEntity == null) - return; - - var entity = hand.Container.ContainedEntity.Value; - - if (!hand.Container!.Remove(entity)) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not remove {entity} from {hand.Container}."); - return; - } - - hands.Dirty(); - - var gotUnequipped = new GotUnequippedHandEvent(uid, entity, hand); - RaiseLocalEvent(entity, gotUnequipped, false); - - var didUnequip = new DidUnequipHandEvent(uid, entity, hand); - RaiseLocalEvent(uid, didUnequip); - - if (hand.Name == hands.ActiveHand) - RaiseLocalEvent(entity, new HandDeselectedEvent(uid, entity), false); - } - - /// - /// Puts an entity into the player's hand, assumes that the insertion is allowed. - /// - public virtual void PutEntityIntoHand(EntityUid uid, Hand hand, EntityUid entity, SharedHandsComponent? hands = null) - { - if (!Resolve(uid, ref hands)) - return; - - var handContainer = hand.Container; - if (handContainer == null || handContainer.ContainedEntity != null) - return; - - if (!handContainer.Insert(entity)) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not insert {entity} into {handContainer}."); - return; - } - - _adminLogSystem.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}"); - - hands.Dirty(); - - var didEquip = new DidEquipHandEvent(uid, entity, hand); - RaiseLocalEvent(uid, didEquip, false); - - var gotEquipped = new GotEquippedHandEvent(uid, entity, hand); - RaiseLocalEvent(entity, gotEquipped); - - // TODO this should REALLY be a cancellable thing, not a handled event. - // If one of the interactions resulted in the item being dropped, return early. - if (gotEquipped.Handled) - return; - - if (hand.Name == hands.ActiveHand) - RaiseLocalEvent(entity, new HandSelectedEvent(uid, entity), false); - } - - public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, - EntityUid? exclude); - #endregion - - protected virtual void HandleContainerRemoved(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) - { - HandleContainerModified(uid, component, args); - } - - protected virtual void HandleContainerModified(EntityUid uid, SharedHandsComponent hands, ContainerModifiedMessage args) - { - // client updates hand visuals here. - } - - private void HandleContainerInserted(EntityUid uid, SharedHandsComponent component, EntInsertedIntoContainerMessage args) - { - // un-rotate entities. needed for things like directional flashlights - Transform(args.Entity).LocalRotation = 0; - - HandleContainerModified(uid, component, args); - } - - private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) - { - if (eventArgs.SenderSession.AttachedEntity == null) - return; - - TrySetActiveHand(eventArgs.SenderSession.AttachedEntity.Value, msg.HandName); - } - - /// - /// Set the currently active hand and raise hand (de)selection events directed at the held entities. - /// - /// True if the active hand was set to a NEW value. Setting it to the same value returns false and does - /// not trigger interactions. - public virtual bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null) - { - if (!Resolve(uid, ref handComp)) - return false; - - if (value == handComp.ActiveHand) - return false; - - if (value != null && !handComp.HasHand(value)) - { - Logger.Warning($"{nameof(SharedHandsComponent)} on {handComp.Owner} tried to set its active hand to {value}, which was not a hand."); - return false; - } - if (value == null && handComp.Hands.Count != 0) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {handComp.Owner} tried to set its active hand to null, when it still had another hand."); - return false; - } - - if (handComp.TryGetActiveHeldEntity(out var entity)) - RaiseLocalEvent(entity.Value, new HandDeselectedEvent(uid, entity.Value), false); - - handComp.ActiveHand = value; - - if (handComp.TryGetActiveHeldEntity(out entity)) - RaiseLocalEvent(entity.Value, new HandSelectedEvent(uid, entity.Value), false); - - handComp.Dirty(); - return true; - } - } -} diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 0192cdaff8..6dce5d00ac 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -6,8 +6,8 @@ namespace Content.Shared.Input public static class ContentKeyFunctions { public static readonly BoundKeyFunction WideAttack = "WideAttack"; - public static readonly BoundKeyFunction ActivateItemInHand = "ActivateItemInHand"; - public static readonly BoundKeyFunction AltActivateItemInHand = "AltActivateItemInHand"; + public static readonly BoundKeyFunction UseItemInHand = "ActivateItemInHand"; + public static readonly BoundKeyFunction AltUseItemInHand = "AltActivateItemInHand"; public static readonly BoundKeyFunction ActivateItemInWorld = "ActivateItemInWorld"; public static readonly BoundKeyFunction AltActivateItemInWorld = "AltActivateItemInWorld"; public static readonly BoundKeyFunction Drop = "Drop"; diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 1b201ad6a1..601785f893 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -216,7 +216,7 @@ namespace Content.Shared.Interaction // Does the user have hands? Hand? hand; - if (!TryComp(user, out SharedHandsComponent? hands) || !hands.TryGetActiveHand(out hand)) + if (!TryComp(user, out SharedHandsComponent? hands) || hands.ActiveHand == null) return; var inRangeUnobstructed = target == null @@ -224,7 +224,7 @@ namespace Content.Shared.Interaction : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // empty-hand interactions - if (hand.HeldEntity == null) + if (hands.ActiveHandEntity is not EntityUid held) { if (inRangeUnobstructed && target != null) InteractHand(user, target.Value); @@ -236,7 +236,7 @@ namespace Content.Shared.Interaction if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user)) return; - if (target == hand.HeldEntity) + if (target == held) { UseInHandInteraction(user, target.Value, checkCanUse: false, checkCanInteract: false); return; @@ -246,7 +246,7 @@ namespace Content.Shared.Interaction { InteractUsing( user, - hand.HeldEntity.Value, + held, target.Value, coordinates, checkCanInteract: false, @@ -257,7 +257,7 @@ namespace Content.Shared.Interaction InteractUsingRanged( user, - hand.HeldEntity.Value, + held, target, coordinates, inRangeUnobstructed); diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 81c9d0eb0b..9ac83b056b 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory.Events; @@ -21,6 +22,7 @@ public abstract partial class InventorySystem [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; private void InitializeEquip() @@ -52,7 +54,7 @@ public abstract partial class InventorySystem if (!TryEquip(args.User, uid, slotDef.Name, true, inventory: inv)) continue; - hands.PutInHandOrDrop(slotEntity.Value); + _handsSystem.PickupOrDrop(args.User, slotEntity.Value); } else { @@ -104,7 +106,7 @@ public abstract partial class InventorySystem if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp(actor, out var hands)) return; - hands.TryGetActiveHeldEntity(out var held); + var held = hands.ActiveHandEntity; TryGetSlotEntity(actor, ev.Slot, out var itemUid, inventory); // attempt to perform some interaction @@ -118,8 +120,8 @@ public abstract partial class InventorySystem // un-equip to hands if (itemUid != null) { - if (hands.CanPickupEntityToActiveHand(itemUid.Value) && TryUnequip(actor, ev.Slot, inventory: inventory)) - hands.PutInHand(itemUid.Value, false); + if (_handsSystem.CanPickupAnyHand(actor, itemUid.Value, handsComp: hands) && TryUnequip(actor, ev.Slot, inventory: inventory)) + _handsSystem.TryPickup(actor, itemUid.Value, checkActionBlocker: false, handsComp: hands); return; } @@ -135,7 +137,7 @@ public abstract partial class InventorySystem return; } - if (hands.TryDropNoInteraction()) + if (_handsSystem.TryDrop(actor, hands.ActiveHand!, doDropInteraction: false, handsComp: hands)) TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory); } diff --git a/Content.Shared/Item/PickupAttemptEvent.cs b/Content.Shared/Item/PickupAttemptEvent.cs index aa1ed2be59..eb0b4c8dcc 100644 --- a/Content.Shared/Item/PickupAttemptEvent.cs +++ b/Content.Shared/Item/PickupAttemptEvent.cs @@ -1,27 +1,30 @@ -using Robust.Shared.GameObjects; +namespace Content.Shared.Item; -namespace Content.Shared.Item +/// +/// Raised on a *mob* when it tries to pickup something +/// +public sealed class PickupAttemptEvent : BasePickupAttemptEvent { - /// - /// Raised on a *mob* when it tries to pickup something - /// - public sealed class PickupAttemptEvent : CancellableEntityEventArgs - { - public PickupAttemptEvent(EntityUid uid) - { - Uid = uid; - } + public PickupAttemptEvent(EntityUid user, EntityUid item) : base(user, item) { } +} - public EntityUid Uid { get; } - } +/// +/// Raised directed at entity being picked up when someone tries to pick it up +/// +public sealed class GettingPickedUpAttemptEvent : BasePickupAttemptEvent +{ + public GettingPickedUpAttemptEvent(EntityUid user, EntityUid item) : base(user, item) { } +} - /// - /// Raised on the *item* when tried to be picked up - /// - /// - /// Doesn't just handle "items" but calling it "PickedUpAttempt" is too close to "Pickup" for the sleep deprived brain. - /// - public sealed class AttemptItemPickupEvent : CancellableEntityEventArgs +[Virtual] +public class BasePickupAttemptEvent : CancellableEntityEventArgs +{ + public readonly EntityUid User; + public readonly EntityUid Item; + + public BasePickupAttemptEvent(EntityUid user, EntityUid item) { + User = user; + Item = item; } } diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 5379c5dfb2..4e07a1b272 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -1,17 +1,17 @@ using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Verbs; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.Localization; -using System; namespace Content.Shared.Item { public abstract class SharedItemSystem : EntitySystem { + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + public override void Initialize() { base.Initialize(); @@ -30,13 +30,7 @@ namespace Content.Shared.Item if (args.Handled) return; - if (!TryComp(args.User, out SharedHandsComponent? hands)) - return; - - if (hands.ActiveHand == null) - return; - - args.Handled = hands.TryPickupEntity(hands.ActiveHand, uid, false, animateUser: false); + args.Handled = _handsSystem.TryPickup(args.User, uid, animateUser: false); } private void OnHandleState(EntityUid uid, SharedItemComponent component, ref ComponentHandleState args) @@ -74,11 +68,11 @@ namespace Content.Shared.Item args.Using != null || !args.CanAccess || !args.CanInteract || - !args.Hands.CanPickupEntityToActiveHand(args.Target)) + !_handsSystem.CanPickupAnyHand(args.User, args.Target, handsComp: args.Hands, item: component)) return; InteractionVerb verb = new(); - verb.Act = () => args.Hands.TryPickupEntityToActiveHand(args.Target); + verb.Act = () => _handsSystem.TryPickupAnyHand(args.User, args.Target, checkActionBlocker: false, handsComp: args.Hands, item: component); verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"; // if the item already in a container (that is not the same as the user's), then change the text. diff --git a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs index dd00d01e01..a7153d098e 100644 --- a/Content.Shared/Placeable/PlaceableSurfaceSystem.cs +++ b/Content.Shared/Placeable/PlaceableSurfaceSystem.cs @@ -1,14 +1,14 @@ using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Maths; namespace Content.Shared.Placeable { public sealed class PlaceableSurfaceSystem : EntitySystem { + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + public override void Initialize() { base.Initialize(); @@ -52,16 +52,13 @@ namespace Content.Shared.Placeable if (!surface.IsPlaceable) return; - if(!EntityManager.TryGetComponent(args.User, out var handComponent)) - return; - - if(!handComponent.TryDropEntity(args.Used, EntityManager.GetComponent(surface.Owner).Coordinates)) + if (!_handsSystem.TryDrop(args.User, args.Used)) return; if (surface.PlaceCentered) - EntityManager.GetComponent(args.Used).LocalPosition = EntityManager.GetComponent(uid).LocalPosition + surface.PositionOffset; + Transform(args.Used).LocalPosition = Transform(uid).LocalPosition + surface.PositionOffset; else - EntityManager.GetComponent(args.Used).Coordinates = args.ClickLocation; + Transform(args.Used).Coordinates = args.ClickLocation; args.Handled = true; } diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 138214f1aa..c7716757a3 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -80,7 +80,7 @@ namespace Content.Shared.Verbs EntityUid? @using = null; if (TryComp(user, out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUseHeldEntity(user))) { - hands.TryGetActiveHeldEntity(out @using); + @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 diff --git a/RobustToolbox b/RobustToolbox index 419e63ecd5..ea7012d114 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 419e63ecd548317ba36bf62b6ce18f11140a5666 +Subproject commit ea7012d114d86836e57687d102576d733ec85d5f