diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index ab389a49d5..37bc9b1232 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -16,7 +16,6 @@ using Robust.Client.UserInterface; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Player; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Client.Hands.Systems @@ -27,16 +26,13 @@ namespace Content.Client.Hands.Systems [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IUserInterfaceManager _ui = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly StrippableSystem _stripSys = default!; [Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly ExamineSystem _examine = default!; [Dependency] private readonly DisplacementMapSystem _displacement = default!; - public event Action? OnPlayerAddHand; - public event Action? OnPlayerRemoveHand; public event Action? OnPlayerSetActiveHand; - public event Action? OnPlayerHandsAdded; + public event Action>? OnPlayerHandsAdded; public event Action? OnPlayerHandsRemoved; public event Action? OnPlayerItemAdded; public event Action? OnPlayerItemRemoved; @@ -58,67 +54,28 @@ namespace Content.Client.Hands.Systems } #region StateHandling - private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args) + private void HandleComponentState(Entity ent, ref ComponentHandleState args) { if (args.Current is not HandsComponentState state) return; - var handsModified = component.Hands.Count != state.Hands.Count; - // we need to check that, even if we have the same amount, that the individual hands didn't change. - if (!handsModified) + var newHands = state.Hands.Keys.Except(ent.Comp.Hands.Keys); // hands that were added between states + var oldHands = ent.Comp.Hands.Keys.Except(state.Hands.Keys); // hands that were removed between states + + foreach (var handId in oldHands) { - foreach (var hand in component.Hands.Values) - { - if (state.Hands.Contains(hand)) - continue; - handsModified = true; - break; - } + RemoveHand(ent.AsNullable(), handId); } - var manager = EnsureComp(uid); - - if (handsModified) + foreach (var handId in state.SortedHands.Intersect(newHands)) { - List addedHands = new(); - foreach (var hand in state.Hands) - { - if (component.Hands.ContainsKey(hand.Name)) - continue; - - var container = _containerSystem.EnsureContainer(uid, hand.Name, manager); - var newHand = new Hand(hand.Name, hand.Location, container); - component.Hands.Add(hand.Name, newHand); - addedHands.Add(newHand); - } - - foreach (var name in component.Hands.Keys) - { - if (!state.HandNames.Contains(name)) - { - RemoveHand(uid, name, component); - } - } - - component.SortedHands.Clear(); - component.SortedHands.AddRange(state.HandNames); - var sorted = addedHands.OrderBy(hand => component.SortedHands.IndexOf(hand.Name)); - - foreach (var hand in sorted) - { - AddHand(uid, hand, component); - } + AddHand(ent.AsNullable(), handId, state.Hands[handId]); } + ent.Comp.SortedHands = new (state.SortedHands); - _stripSys.UpdateUi(uid); + SetActiveHand(ent.AsNullable(), state.ActiveHandId); - if (component.ActiveHand == null && state.ActiveHand == null) - return; //edge case - - if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name) - { - SetActiveHand(uid, component.Hands[state.ActiveHand!], component); - } + _stripSys.UpdateUi(ent); } #endregion @@ -129,47 +86,52 @@ namespace Content.Client.Hands.Systems return; } - OnPlayerHandsAdded?.Invoke(hands); + OnPlayerHandsAdded?.Invoke(hands.Value); } - public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true) + public override void DoDrop(Entity ent, + string handId, + bool doDropInteraction = true, + bool log = true) { - base.DoDrop(uid, hand, doDropInteraction, hands, log); + base.DoDrop(ent, handId, doDropInteraction, log); - if (TryComp(hand.HeldEntity, out SpriteComponent? sprite)) + if (TryGetHeldItem(ent, handId, out var held) && TryComp(held, out SpriteComponent? sprite)) sprite.RenderOrder = EntityManager.CurrentTick.Value; } public EntityUid? GetActiveHandEntity() { - return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null; + return TryGetPlayerHands(out var hands) ? GetActiveItem(hands.Value.AsNullable()) : null; } /// /// Get the hands component of the local player /// - public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands) + public bool TryGetPlayerHands([NotNullWhen(true)] out Entity? hands) { var player = _playerManager.LocalEntity; hands = null; - return player != null && TryComp(player.Value, out hands); + if (player == null || !TryComp(player.Value, out var handsComp)) + return false; + + hands = (player.Value, handsComp); + return true; } /// /// Called when a user clicked on their hands GUI /// - public void UIHandClick(HandsComponent hands, string handName) + public void UIHandClick(Entity ent, string handName) { - if (!hands.Hands.TryGetValue(handName, out var pressedHand)) + var hands = ent.Comp; + if (hands.ActiveHandId == null) return; - if (hands.ActiveHand == null) - return; + var pressedEntity = GetHeldItem(ent.AsNullable(), handName); + var activeEntity = GetActiveItem(ent.AsNullable()); - var pressedEntity = pressedHand.HeldEntity; - var activeEntity = hands.ActiveHand.HeldEntity; - - if (pressedHand == hands.ActiveHand && activeEntity != null) + if (handName == hands.ActiveHandId && activeEntity != null) { // use item in hand // it will always be attack_self() in my heart. @@ -177,24 +139,24 @@ namespace Content.Client.Hands.Systems return; } - if (pressedHand != hands.ActiveHand && pressedEntity == null) + if (handName != hands.ActiveHandId && pressedEntity == null) { // change active hand EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName)); return; } - if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null) + if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity != null) { // use active item on held item - EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name)); + EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(handName)); return; } - if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null) + if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity == null) { // move the item to the active hand - EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name)); + EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(handName)); } } @@ -210,13 +172,12 @@ namespace Content.Client.Hands.Systems public void UIInventoryExamine(string handName) { if (!TryGetPlayerHands(out var hands) || - !hands.Hands.TryGetValue(handName, out var hand) || - hand.HeldEntity is not { Valid: true } entity) + !TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity)) { return; } - _examine.DoExamine(entity); + _examine.DoExamine(heldEntity.Value); } /// @@ -226,13 +187,12 @@ namespace Content.Client.Hands.Systems public void UIHandOpenContextMenu(string handName) { if (!TryGetPlayerHands(out var hands) || - !hands.Hands.TryGetValue(handName, out var hand) || - hand.HeldEntity is not { Valid: true } entity) + !TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity)) { return; } - _ui.GetUIController().OpenVerbMenu(entity); + _ui.GetUIController().OpenVerbMenu(heldEntity.Value); } public void UIHandAltActivateItem(string handName) @@ -246,60 +206,67 @@ namespace Content.Client.Hands.Systems { base.HandleEntityInserted(uid, hands, args); - if (!hands.Hands.TryGetValue(args.Container.ID, out var hand)) + if (!hands.Hands.ContainsKey(args.Container.ID)) return; - UpdateHandVisuals(uid, args.Entity, hand); + + UpdateHandVisuals(uid, args.Entity, args.Container.ID); _stripSys.UpdateUi(uid); if (uid != _playerManager.LocalEntity) return; - OnPlayerItemAdded?.Invoke(hand.Name, args.Entity); + OnPlayerItemAdded?.Invoke(args.Container.ID, args.Entity); if (HasComp(args.Entity)) - OnPlayerHandBlocked?.Invoke(hand.Name); + OnPlayerHandBlocked?.Invoke(args.Container.ID); } protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args) { base.HandleEntityRemoved(uid, hands, args); - if (!hands.Hands.TryGetValue(args.Container.ID, out var hand)) + if (!hands.Hands.ContainsKey(args.Container.ID)) return; - UpdateHandVisuals(uid, args.Entity, hand); + + UpdateHandVisuals(uid, args.Entity, args.Container.ID); _stripSys.UpdateUi(uid); if (uid != _playerManager.LocalEntity) return; - OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity); + OnPlayerItemRemoved?.Invoke(args.Container.ID, args.Entity); if (HasComp(args.Entity)) - OnPlayerHandUnblocked?.Invoke(hand.Name); + OnPlayerHandUnblocked?.Invoke(args.Container.ID); } /// /// Update the players sprite with new in-hand visuals. /// - private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null) + private void UpdateHandVisuals(Entity ent, EntityUid held, string handId) { - if (!Resolve(uid, ref handComp, ref sprite, false)) + if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false)) + return; + var handComp = ent.Comp1; + var sprite = ent.Comp2; + + if (!TryGetHand((ent, handComp), handId, out var hand)) return; // visual update might involve changes to the entity's effective sprite -> need to update hands GUI. - if (uid == _playerManager.LocalEntity) - OnPlayerItemAdded?.Invoke(hand.Name, held); + if (ent == _playerManager.LocalEntity) + OnPlayerItemAdded?.Invoke(handId, held); if (!handComp.ShowInHands) return; // Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this // may eventually bloat the player with lots of layers. - if (handComp.RevealedLayers.TryGetValue(hand.Location, out var revealedLayers)) + if (handComp.RevealedLayers.TryGetValue(hand.Value.Location, out var revealedLayers)) { foreach (var key in revealedLayers) { - _sprite.RemoveLayer((uid, sprite), key); + _sprite.RemoveLayer((ent, sprite), key); } revealedLayers.Clear(); @@ -307,22 +274,22 @@ namespace Content.Client.Hands.Systems else { revealedLayers = new(); - handComp.RevealedLayers[hand.Location] = revealedLayers; + handComp.RevealedLayers[hand.Value.Location] = revealedLayers; } - if (hand.HeldEntity == null) + if (HandIsEmpty((ent, handComp), handId)) { // the held item was removed. - RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true); + RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true); return; } - var ev = new GetInhandVisualsEvent(uid, hand.Location); + var ev = new GetInhandVisualsEvent(ent, hand.Value.Location); RaiseLocalEvent(held, ev); if (ev.Layers.Count == 0) { - RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true); + RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true); return; } @@ -335,7 +302,7 @@ namespace Content.Client.Hands.Systems continue; } - var index = _sprite.LayerMapReserve((uid, sprite), key); + var index = _sprite.LayerMapReserve((ent, sprite), key); // In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries. if (layerData.RsiPath == null @@ -343,35 +310,34 @@ namespace Content.Client.Hands.Systems && sprite[index].Rsi == null) { if (TryComp(held, out var itemComponent) && itemComponent.RsiPath != null) - _sprite.LayerSetRsi((uid, sprite), index, new ResPath(itemComponent.RsiPath)); + _sprite.LayerSetRsi((ent, sprite), index, new ResPath(itemComponent.RsiPath)); else if (TryComp(held, out SpriteComponent? clothingSprite)) - _sprite.LayerSetRsi((uid, sprite), index, clothingSprite.BaseRSI); + _sprite.LayerSetRsi((ent, sprite), index, clothingSprite.BaseRSI); } - _sprite.LayerSetData((uid, sprite), index, layerData); + _sprite.LayerSetData((ent, sprite), index, layerData); // Add displacement maps - var displacement = hand.Location switch + var displacement = hand.Value.Location switch { HandLocation.Left => handComp.LeftHandDisplacement, HandLocation.Right => handComp.RightHandDisplacement, _ => handComp.HandDisplacement }; - if (displacement is not null && _displacement.TryAddDisplacement(displacement, (uid, sprite), index, key, out var displacementKey)) + if (displacement is not null && _displacement.TryAddDisplacement(displacement, (ent, sprite), index, key, out var displacementKey)) revealedLayers.Add(displacementKey); } - RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true); + RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true); } 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.Hands.TryGetValue(args.ContainerId, out var hand)) - { - UpdateHandVisuals(uid, GetEntity(args.Item), hand, component); - } + if (!component.Hands.ContainsKey(args.ContainerId)) + return; + UpdateHandVisuals((uid, component), GetEntity(args.Item), args.ContainerId); } #endregion @@ -379,7 +345,7 @@ namespace Content.Client.Hands.Systems private void HandlePlayerAttached(EntityUid uid, HandsComponent component, LocalPlayerAttachedEvent args) { - OnPlayerHandsAdded?.Invoke(component); + OnPlayerHandsAdded?.Invoke((uid, component)); } private void HandlePlayerDetached(EntityUid uid, HandsComponent component, LocalPlayerDetachedEvent args) @@ -390,7 +356,7 @@ namespace Content.Client.Hands.Systems private void OnHandsStartup(EntityUid uid, HandsComponent component, ComponentStartup args) { if (_playerManager.LocalEntity == uid) - OnPlayerHandsAdded?.Invoke(component); + OnPlayerHandsAdded?.Invoke((uid, component)); } private void OnHandsShutdown(EntityUid uid, HandsComponent component, ComponentShutdown args) @@ -400,36 +366,6 @@ namespace Content.Client.Hands.Systems } #endregion - private void AddHand(EntityUid uid, Hand newHand, HandsComponent? handsComp = null) - { - AddHand(uid, newHand.Name, newHand.Location, handsComp); - } - - public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null) - { - base.AddHand(uid, handName, handLocation, handsComp); - - if (uid == _playerManager.LocalEntity) - OnPlayerAddHand?.Invoke(handName, handLocation); - - if (handsComp == null) - return; - - if (handsComp.ActiveHand == null) - SetActiveHand(uid, handsComp.Hands[handName], handsComp); - } - public override void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null) - { - if (uid == _playerManager.LocalEntity && handsComp != null && - handsComp.Hands.ContainsKey(handName) && uid == - _playerManager.LocalEntity) - { - OnPlayerRemoveHand?.Invoke(handName); - } - - base.RemoveHand(uid, handName, handsComp); - } - private void OnHandActivated(Entity? ent) { if (ent is not { } hand) @@ -438,13 +374,7 @@ namespace Content.Client.Hands.Systems if (_playerManager.LocalEntity != hand.Owner) return; - if (hand.Comp.ActiveHand == null) - { - OnPlayerSetActiveHand?.Invoke(null); - return; - } - - OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHand.Name); + OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHandId); } } } diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs index a9a937d5d8..381406d2ac 100644 --- a/Content.Client/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Numerics; using Content.Client.Examine; +using Content.Client.Hands.Systems; using Content.Client.Strip; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; @@ -34,6 +35,7 @@ namespace Content.Client.Inventory [Dependency] private readonly IUserInterfaceManager _ui = default!; private readonly ExamineSystem _examine; + private readonly HandsSystem _hands; private readonly InventorySystem _inv; private readonly SharedCuffableSystem _cuffable; private readonly StrippableSystem _strippable; @@ -65,6 +67,7 @@ namespace Content.Client.Inventory public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { _examine = EntMan.System(); + _hands = EntMan.System(); _inv = EntMan.System(); _cuffable = EntMan.System(); _strippable = EntMan.System(); @@ -120,28 +123,28 @@ namespace Content.Client.Inventory { // good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands // and not gui hands... which are different... - foreach (var hand in handsComp.Hands.Values) + foreach (var (id, hand) in handsComp.Hands) { if (hand.Location != HandLocation.Right) continue; - AddHandButton(hand); + AddHandButton((Owner, handsComp), id, hand); } - foreach (var hand in handsComp.Hands.Values) + foreach (var (id, hand) in handsComp.Hands) { if (hand.Location != HandLocation.Middle) continue; - AddHandButton(hand); + AddHandButton((Owner, handsComp), id, hand); } - foreach (var hand in handsComp.Hands.Values) + foreach (var (id, hand) in handsComp.Hands) { if (hand.Location != HandLocation.Left) continue; - AddHandButton(hand); + AddHandButton((Owner, handsComp), id, hand); } } @@ -177,20 +180,21 @@ namespace Content.Client.Inventory _strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize); } - private void AddHandButton(Hand hand) + private void AddHandButton(Entity ent, string handId, Hand hand) { - var button = new HandButton(hand.Name, hand.Location); + var button = new HandButton(handId, hand.Location); button.Pressed += SlotPressed; - if (EntMan.TryGetComponent(hand.HeldEntity, out var virt)) + var heldEntity = _hands.GetHeldItem(ent.AsNullable(), handId); + if (EntMan.TryGetComponent(heldEntity, out var virt)) { button.Blocked = true; if (EntMan.TryGetComponent(Owner, out var cuff) && _cuffable.GetAllCuffs(cuff).Contains(virt.BlockingEntity)) button.BlockedRect.MouseFilter = MouseFilterMode.Ignore; } - UpdateEntityIcon(button, hand.HeldEntity); + UpdateEntityIcon(button, heldEntity); _strippingMenu!.HandsContainer.AddChild(button); LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation)); _handCount++; diff --git a/Content.Client/RCD/AlignRCDConstruction.cs b/Content.Client/RCD/AlignRCDConstruction.cs index fbaf62c83d..b155458836 100644 --- a/Content.Client/RCD/AlignRCDConstruction.cs +++ b/Content.Client/RCD/AlignRCDConstruction.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Client.Gameplay; +using Content.Client.Hands.Systems; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.RCD.Components; @@ -17,6 +18,7 @@ public sealed class AlignRCDConstruction : PlacementMode [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; private readonly SharedMapSystem _mapSystem; + private readonly HandsSystem _handsSystem; private readonly RCDSystem _rcdSystem; private readonly SharedTransformSystem _transformSystem; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -34,6 +36,7 @@ public sealed class AlignRCDConstruction : PlacementMode { IoCManager.InjectDependencies(this); _mapSystem = _entityManager.System(); + _handsSystem = _entityManager.System(); _rcdSystem = _entityManager.System(); _transformSystem = _entityManager.System(); @@ -88,11 +91,9 @@ public sealed class AlignRCDConstruction : PlacementMode } // Determine if player is carrying an RCD in their active hand - if (!_entityManager.TryGetComponent(player, out var hands)) + if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity)) return false; - var heldEntity = hands.ActiveHand?.HeldEntity; - if (!_entityManager.TryGetComponent(heldEntity, out var rcd)) return false; diff --git a/Content.Client/RCD/RCDConstructionGhostSystem.cs b/Content.Client/RCD/RCDConstructionGhostSystem.cs index 23dcf6485f..8ec980268b 100644 --- a/Content.Client/RCD/RCDConstructionGhostSystem.cs +++ b/Content.Client/RCD/RCDConstructionGhostSystem.cs @@ -1,3 +1,4 @@ +using Content.Client.Hands.Systems; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.RCD; @@ -15,6 +16,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly HandsSystem _hands = default!; private string _placementMode = typeof(AlignRCDConstruction).Name; private Direction _placementDirection = default; @@ -33,12 +35,11 @@ public sealed class RCDConstructionGhostSystem : EntitySystem return; // Determine if player is carrying an RCD in their active hand - var player = _playerManager.LocalSession?.AttachedEntity; - - if (!TryComp(player, out var hands)) + if (_playerManager.LocalSession?.AttachedEntity is not { } player) return; - var heldEntity = hands.ActiveHand?.HeldEntity; + if (!_hands.TryGetActiveItem(player, out var heldEntity)) + return; if (!TryComp(heldEntity, out var rcd)) { diff --git a/Content.Client/SubFloor/TrayScannerSystem.cs b/Content.Client/SubFloor/TrayScannerSystem.cs index 194453ab75..4c67890f6a 100644 --- a/Content.Client/SubFloor/TrayScannerSystem.cs +++ b/Content.Client/SubFloor/TrayScannerSystem.cs @@ -68,11 +68,15 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem foreach (var hand in _hands.EnumerateHands(player.Value)) { - if (!scannerQuery.TryGetComponent(hand.HeldEntity, out var heldScanner) || !heldScanner.Enabled) + if (!_hands.TryGetHeldItem(player.Value, hand, out var heldEntity)) + continue; + + if (!scannerQuery.TryGetComponent(heldEntity, out var heldScanner) || !heldScanner.Enabled) continue; range = MathF.Max(heldScanner.Range, range); canSee = true; + break; } inRange = new HashSet>(); diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs index edc0260061..ecaddd8f98 100644 --- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs +++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs @@ -7,6 +7,7 @@ using Content.Shared.Hands.Components; using Content.Shared.Input; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Timing; +using JetBrains.Annotations; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; @@ -28,7 +29,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered _handContainerIndices = new(); private readonly Dictionary _handLookup = new(); private HandsComponent? _playerHandsComponent; - private HandButton? _activeHand = null; + private HandButton? _activeHand; // We only have two item status controls (left and right hand), // but we may have more than two hands. @@ -38,7 +39,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered UIManager.GetActiveUIWidgetOrNull(); @@ -48,7 +49,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered entity, string name, HandLocation location) { + if (entity.Owner != _player.LocalEntity) + return; AddHand(name, location); } + private void OnRemoveHand(Entity entity, string name) + { + if (entity.Owner != _player.LocalEntity) + return; + RemoveHand(name); + } + private void HandPressed(GUIBoundKeyEventArgs args, SlotControl hand) { - if (_playerHandsComponent == null) - { + if (!_handsSystem.TryGetPlayerHands(out var hands)) return; - } if (args.Function == EngineKeyFunctions.UIClick) { - _handsSystem.UIHandClick(_playerHandsComponent, hand.SlotName); + _handsSystem.UIHandClick(hands.Value, hand.SlotName); args.Handle(); } else if (args.Function == EngineKeyFunctions.UseSecondary) @@ -122,33 +130,33 @@ public sealed class HandsUIController : UIController, IOnStateEntered handsComp) { DebugTools.Assert(_playerHandsComponent == null); if (HandsGui != null) HandsGui.Visible = true; _playerHandsComponent = handsComp; - foreach (var (name, hand) in handsComp.Hands) + foreach (var (name, hand) in handsComp.Comp.Hands) { var handButton = AddHand(name, hand.Location); - if (_entities.TryGetComponent(hand.HeldEntity, out VirtualItemComponent? virt)) + if (_handsSystem.TryGetHeldItem(handsComp.AsNullable(), name, out var held) && + _entities.TryGetComponent(held, out VirtualItemComponent? virt)) { handButton.SetEntity(virt.BlockingEntity); handButton.Blocked = true; } else { - handButton.SetEntity(hand.HeldEntity); + handButton.SetEntity(held); handButton.Blocked = false; } } - var activeHand = handsComp.ActiveHand; - if (activeHand == null) + if (handsComp.Comp.ActiveHandId == null) return; - SetActiveHand(activeHand.Name); + SetActiveHand(handsComp.Comp.ActiveHandId); } private void HandBlocked(string handName) @@ -260,19 +268,21 @@ public sealed class HandsUIController : UIController, IOnStateEntered(player, out var hands) || - hands.ActiveHandEntity is not { } held || + player == null || + !_handsSystem.TryGetActiveItem(player.Value, out var held) || !_entities.TryGetComponent(held, out SpriteComponent? sprite) || !_inventorySystem.TryGetSlotContainer(player.Value, control.SlotName, out var container, out var slotDef)) { @@ -342,12 +341,12 @@ public sealed class InventoryUIController : UIController, IOnStateEntered(hoverEntity); - var fits = _inventorySystem.CanEquip(player.Value, held, control.SlotName, out _, slotDef) && - _container.CanInsert(held, container); + var fits = _inventorySystem.CanEquip(player.Value, held.Value, control.SlotName, out _, slotDef) && + _container.CanInsert(held.Value, container); if (!fits && _entities.TryGetComponent(container.ContainedEntity, out var storage)) { - fits = _entities.System().CanInsert(container.ContainedEntity.Value, held, out _, storage); + fits = _entities.System().CanInsert(container.ContainedEntity.Value, held.Value, out _, storage); } else if (!fits && _entities.TryGetComponent(container.ContainedEntity, out var itemSlots)) { @@ -357,14 +356,14 @@ public sealed class InventoryUIController : UIController, IOnStateEntered { // Make sure the player's hand starts empty - var heldItem = Hands.ActiveHandEntity; + var heldItem = handsSystem.GetActiveItem((playerUid, Hands)); Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) at start of test."); // Inspect the action prototype to find the item it spawns @@ -43,14 +43,14 @@ public sealed class RetractableItemActionTest : InteractionTest var actionEnt = actionsSystem.GetAction(actionUid); // Make sure the player's hand is still empty - heldItem = Hands.ActiveHandEntity; + heldItem = handsSystem.GetActiveItem((playerUid, Hands)); Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) after adding action."); // Activate the arm blade actionsSystem.PerformAction(ToServer(Player), actionEnt!.Value); // Make sure the player is now holding the expected item - heldItem = Hands.ActiveHandEntity; + heldItem = handsSystem.GetActiveItem((playerUid, Hands)); Assert.That(heldItem, Is.Not.Null, $"Expected player to be holding {spawnedProtoId} but was holding nothing."); AssertPrototype(spawnedProtoId, SEntMan.GetNetEntity(heldItem)); @@ -58,7 +58,7 @@ public sealed class RetractableItemActionTest : InteractionTest actionsSystem.PerformAction(ToServer(Player), actionEnt.Value); // Make sure the player's hand is empty again - heldItem = Hands.ActiveHandEntity; + heldItem = handsSystem.GetActiveItem((playerUid, Hands)); Assert.That(heldItem, Is.Null, $"Player is still holding an item ({SEntMan.ToPrettyString(heldItem)}) after second use."); }); } diff --git a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs index 1b31fe38c2..d6dec6fe15 100644 --- a/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs +++ b/Content.IntegrationTests/Tests/Buckle/BuckleTest.cs @@ -293,9 +293,9 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.That(buckle.Buckled); // With items in all hands - foreach (var hand in hands.Hands.Values) + foreach (var hand in hands.Hands.Keys) { - Assert.That(hand.HeldEntity, Is.Not.Null); + Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Not.Null); } var bodySystem = entityManager.System(); @@ -316,9 +316,9 @@ namespace Content.IntegrationTests.Tests.Buckle Assert.That(buckle.Buckled); // Now with no item in any hand - foreach (var hand in hands.Hands.Values) + foreach (var hand in hands.Hands.Keys) { - Assert.That(hand.HeldEntity, Is.Null); + Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Null); } buckleSystem.Unbuckle(human, human); diff --git a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs index 52b7e555a9..4c5860f235 100644 --- a/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs +++ b/Content.IntegrationTests/Tests/Chemistry/DispenserTest.cs @@ -1,7 +1,6 @@ using Content.Client.Chemistry.UI; using Content.IntegrationTests.Tests.Interaction; using Content.Shared.Chemistry; -using Content.Server.Chemistry.Components; using Content.Shared.Containers.ItemSlots; namespace Content.IntegrationTests.Tests.Chemistry; @@ -19,7 +18,7 @@ public sealed class DispenserTest : InteractionTest // Insert beaker await InteractUsing("Beaker"); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); // Open BUI await Interact(); @@ -29,18 +28,18 @@ public sealed class DispenserTest : InteractionTest await SendBui(ReagentDispenserUiKey.Key, ev); // Beaker is back in the player's hands - Assert.That(Hands.ActiveHandEntity, Is.Not.Null); - AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity)); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null); + AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)))); // Re-insert the beaker await Interact(); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); // Re-eject using the button directly instead of sending a BUI event. This test is really just a test of the // bui/window helper methods. await ClickControl(nameof(ReagentDispenserWindow.EjectButton)); await RunTicks(5); - Assert.That(Hands.ActiveHandEntity, Is.Not.Null); - AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity)); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null); + AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)))); } } diff --git a/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs b/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs index 94dd98425b..b53b87dd5c 100644 --- a/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs +++ b/Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs @@ -267,7 +267,7 @@ public sealed class SuicideCommandTests await server.WaitPost(() => { var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player)); - Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!)); + Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!)); entManager.TryGetComponent(item, out var executionComponent); Assert.That(executionComponent, Is.Not.EqualTo(null)); }); @@ -342,7 +342,7 @@ public sealed class SuicideCommandTests await server.WaitPost(() => { var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player)); - Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!)); + Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!)); entManager.TryGetComponent(item, out var executionComponent); Assert.That(executionComponent, Is.Not.EqualTo(null)); }); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs index 292bf0c55a..b94bda3d95 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/WallConstruction.cs @@ -13,10 +13,10 @@ public sealed class WallConstruction : InteractionTest { await StartConstruction(Wall); await InteractUsing(Steel, 2); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); ClientAssertPrototype(Girder, Target); await InteractUsing(Steel, 2); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); AssertPrototype(WallSolid); } diff --git a/Content.IntegrationTests/Tests/Hands/HandTests.cs b/Content.IntegrationTests/Tests/Hands/HandTests.cs index b88f2dc9eb..d5cf75c463 100644 --- a/Content.IntegrationTests/Tests/Hands/HandTests.cs +++ b/Content.IntegrationTests/Tests/Hands/HandTests.cs @@ -53,20 +53,20 @@ public sealed class HandTests var xform = entMan.GetComponent(player); item = entMan.SpawnEntity("Crowbar", tSys.GetMapCoordinates(player, xform: xform)); hands = entMan.GetComponent(player); - sys.TryPickup(player, item, hands.ActiveHand!); + sys.TryPickup(player, item, hands.ActiveHandId!); }); // run ticks here is important, as errors may happen within the container system's frame update methods. await pair.RunTicksSync(5); - Assert.That(hands.ActiveHandEntity, Is.EqualTo(item)); + Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item)); await server.WaitPost(() => { - sys.TryDrop(player, item, null!); + sys.TryDrop(player, item); }); await pair.RunTicksSync(5); - Assert.That(hands.ActiveHandEntity, Is.Null); + Assert.That(sys.GetActiveItem((player, hands)), Is.Null); await server.WaitPost(() => mapSystem.DeleteMap(data.MapId)); await pair.CleanReturnAsync(); @@ -105,10 +105,10 @@ public sealed class HandTests player = playerMan.Sessions.First().AttachedEntity!.Value; tSys.PlaceNextTo(player, item); hands = entMan.GetComponent(player); - sys.TryPickup(player, item, hands.ActiveHand!); + sys.TryPickup(player, item, hands.ActiveHandId!); }); await pair.RunTicksSync(5); - Assert.That(hands.ActiveHandEntity, Is.EqualTo(item)); + Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item)); // Open then close the box to place the player, who is holding the crowbar, inside of it var storage = server.System(); @@ -125,12 +125,12 @@ public sealed class HandTests // with the item not being in the player's hands await server.WaitPost(() => { - sys.TryDrop(player, item, null!); + sys.TryDrop(player, item); }); await pair.RunTicksSync(5); var xform = entMan.GetComponent(player); var itemXform = entMan.GetComponent(item); - Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item)); + Assert.That(sys.GetActiveItem((player, hands)), Is.Not.EqualTo(item)); Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform))); await server.WaitPost(() => mapSystem.DeleteMap(map.MapId)); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 0f9e9b5ebe..3302b1bafc 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -120,18 +120,18 @@ public abstract partial class InteractionTest /// protected async Task DeleteHeldEntity() { - if (Hands.ActiveHandEntity is { } held) + if (HandSys.GetActiveItem((ToServer(Player), Hands)) is { } held) { await Server.WaitPost(() => { - Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), null, false, true, Hands)); + Assert.That(HandSys.TryDrop((SEntMan.GetEntity(Player), Hands), null, false, true)); SEntMan.DeleteEntity(held); SLogger.Debug($"Deleting held entity"); }); } await RunTicks(1); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null); } /// @@ -152,7 +152,7 @@ public abstract partial class InteractionTest /// Whether or not to automatically enable any toggleable items protected async Task PlaceInHands(EntitySpecifier entity, bool enableToggleable = true) { - if (Hands.ActiveHand == null) + if (Hands.ActiveHandId == null) { Assert.Fail("No active hand"); return default; @@ -169,7 +169,7 @@ public abstract partial class InteractionTest { var playerEnt = SEntMan.GetEntity(Player); - Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands)); + Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHandId, false, false, false, Hands)); // turn on welders if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated) @@ -179,7 +179,7 @@ public abstract partial class InteractionTest }); await RunTicks(1); - Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item)); + Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(item)); if (enableToggleable && itemToggle != null) Assert.That(itemToggle.Activated); @@ -193,7 +193,7 @@ public abstract partial class InteractionTest { entity ??= Target; - if (Hands.ActiveHand == null) + if (Hands.ActiveHandId == null) { Assert.Fail("No active hand"); return; @@ -212,11 +212,11 @@ public abstract partial class InteractionTest await Server.WaitPost(() => { - Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, Hands, item)); + Assert.That(HandSys.TryPickup(ToServer(Player), uid.Value, Hands.ActiveHandId, false, false, false, Hands, item)); }); await RunTicks(1); - Assert.That(Hands.ActiveHandEntity, Is.EqualTo(uid)); + Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(uid)); } /// @@ -224,7 +224,7 @@ public abstract partial class InteractionTest /// protected async Task Drop() { - if (Hands.ActiveHandEntity == null) + if (HandSys.GetActiveItem((ToServer(Player), Hands)) == null) { Assert.Fail("Not holding any entity to drop"); return; @@ -232,11 +232,11 @@ public abstract partial class InteractionTest await Server.WaitPost(() => { - Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), handsComp: Hands)); + Assert.That(HandSys.TryDrop((ToServer(Player), Hands))); }); await RunTicks(1); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null); } #region Interact @@ -246,7 +246,7 @@ public abstract partial class InteractionTest /// protected async Task UseInHand() { - if (Hands.ActiveHandEntity is not { } target) + if (HandSys.GetActiveItem((ToServer(Player), Hands)) is not { } target) { Assert.Fail("Not holding any entity"); return; diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index 47a1f748eb..79756ea5b4 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -1,15 +1,12 @@ #nullable enable -using System.Linq; using System.Numerics; using Content.Client.Construction; using Content.Client.Examine; using Content.Client.Gameplay; using Content.IntegrationTests.Pair; -using Content.Server.Body.Systems; using Content.Server.Hands.Systems; using Content.Server.Stack; using Content.Server.Tools; -using Content.Shared.Body.Part; using Content.Shared.DoAfter; using Content.Shared.Hands.Components; using Content.Shared.Interaction; @@ -135,10 +132,13 @@ public abstract partial class InteractionTest - type: entity id: InteractionTestMob components: - - type: Body - prototype: Aghost - type: DoAfter - type: Hands + hands: + hand_right: # only one hand, so that they do not accidentally pick up deconstruction products + location: Right + sortedHands: + - hand_right - type: ComplexInteraction - type: MindContainer - type: Stripping @@ -230,20 +230,6 @@ public abstract partial class InteractionTest SEntMan.DeleteEntity(old.Value); }); - // Ensure that the player only has one hand, so that they do not accidentally pick up deconstruction products - await Server.WaitPost(() => - { - // I lost an hour of my life trying to track down how the hell interaction tests were breaking - // so greatz to this. Just make your own body prototype! - var bodySystem = SEntMan.System(); - var hands = bodySystem.GetBodyChildrenOfType(SEntMan.GetEntity(Player), BodyPartType.Hand).ToArray(); - - for (var i = 1; i < hands.Length; i++) - { - SEntMan.DeleteEntity(hands[i].Id); - } - }); - // Change UI state to in-game. var state = Client.ResolveDependency(); await Client.WaitPost(() => state.RequestStateChange()); diff --git a/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs b/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs index eef420df20..0827e11b70 100644 --- a/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs +++ b/Content.IntegrationTests/Tests/Tiles/TileConstructionTests.cs @@ -17,7 +17,7 @@ public sealed class TileConstructionTests : InteractionTest await SetTile(null); await InteractUsing(Rod); await AssertTile(Lattice); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); await InteractUsing(Cut); await AssertTile(null); await AssertEntityLookup((Rod, 1)); @@ -49,7 +49,7 @@ public sealed class TileConstructionTests : InteractionTest AssertGridCount(1); // Cut lattice - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); await InteractUsing(Cut); await AssertTile(null); AssertGridCount(0); @@ -83,13 +83,13 @@ public sealed class TileConstructionTests : InteractionTest // Lattice -> Plating await InteractUsing(FloorItem); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); await AssertTile(Plating); AssertGridCount(1); // Plating -> Tile await InteractUsing(FloorItem); - Assert.That(Hands.ActiveHandEntity, Is.Null); + Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null); await AssertTile(Floor); AssertGridCount(1); diff --git a/Content.Server/Administration/Commands/StripAllCommand.cs b/Content.Server/Administration/Commands/StripAllCommand.cs index ee3e595595..90fcde136d 100644 --- a/Content.Server/Administration/Commands/StripAllCommand.cs +++ b/Content.Server/Administration/Commands/StripAllCommand.cs @@ -42,13 +42,12 @@ public sealed class StripAllCommand : LocalizedEntityCommands if (EntityManager.TryGetComponent(targetEntity, out var hands)) { - foreach (var hand in _handsSystem.EnumerateHands(targetEntity.Value, hands)) + foreach (var hand in _handsSystem.EnumerateHands((targetEntity.Value, hands))) { - _handsSystem.TryDrop(targetEntity.Value, + _handsSystem.TryDrop((targetEntity.Value, hands), hand, checkActionBlocker: false, - doDropInteraction: false, - handsComp: hands); + doDropInteraction: false); } } } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index b9916bc5b2..0e5138ba96 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -433,9 +433,9 @@ public sealed class AdminSystem : EntitySystem if (TryComp(entity, out HandsComponent? hands)) { - foreach (var hand in _hands.EnumerateHands(entity, hands)) + foreach (var hand in _hands.EnumerateHands((entity, hands))) { - _hands.TryDrop(entity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands); + _hands.TryDrop((entity, hands), hand, checkActionBlocker: false, doDropInteraction: false); } } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index ccc7a98a1e..22a1ccadc9 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -820,7 +820,7 @@ public sealed partial class AdminVerbSystem } else if (TryComp(target, out var hands)) { - foreach (var held in _handsSystem.EnumerateHeld(target, hands)) + foreach (var held in _handsSystem.EnumerateHeld((target, hands))) { if (HasComp(held)) { diff --git a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs index 21f43c3195..7cadc3bb8f 100644 --- a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs +++ b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs @@ -97,8 +97,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem EntityUid? entity = null; if (args.Type == CryostorageRemoveItemBuiMessage.RemovalType.Hand) { - if (_hands.TryGetHand(cryoContained, args.Key, out var hand)) - entity = hand.HeldEntity; + entity = _hands.GetHeldItem(cryoContained, args.Key); } else { @@ -320,10 +319,10 @@ public sealed class CryostorageSystem : SharedCryostorageSystem foreach (var hand in _hands.EnumerateHands(uid)) { - if (hand.HeldEntity == null) + if (!_hands.TryGetHeldItem(uid, hand, out var heldEntity)) continue; - data.HeldItems.Add(hand.Name, Name(hand.HeldEntity.Value)); + data.HeldItems.Add(hand, Name(heldEntity.Value)); } return data; diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 0686f32097..d8381b2a79 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Botany.Components; +using Content.Server.Hands.Systems; using Content.Server.Kitchen.Components; using Content.Server.Popups; using Content.Shared.Chemistry.EntitySystems; @@ -37,6 +38,7 @@ public sealed class PlantHolderSystem : EntitySystem [Dependency] private readonly MutationSystem _mutation = default!; [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; @@ -45,7 +47,7 @@ public sealed class PlantHolderSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - + public const float HydroponicsSpeedMultiplier = 1f; public const float HydroponicsConsumptionMultiplier = 2f; @@ -706,9 +708,9 @@ public sealed class PlantHolderSystem : EntitySystem if (component.Harvest && !component.Dead) { - if (TryComp(user, out var hands)) + if (_hands.TryGetActiveItem(user, out var activeItem)) { - if (!_botany.CanHarvest(component.Seed, hands.ActiveHandEntity)) + if (!_botany.CanHarvest(component.Seed, activeItem)) { _popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user); return false; diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs index b9a3ede2bd..8727429fc9 100644 --- a/Content.Server/Chat/SuicideSystem.cs +++ b/Content.Server/Chat/SuicideSystem.cs @@ -1,9 +1,9 @@ using Content.Server.Ghost; +using Content.Server.Hands.Systems; using Content.Shared.Administration.Logs; using Content.Shared.Chat; using Content.Shared.Damage; using Content.Shared.Database; -using Content.Shared.Hands.Components; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; using Content.Shared.Item; @@ -22,6 +22,7 @@ public sealed class SuicideSystem : EntitySystem { [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; @@ -116,10 +117,9 @@ public sealed class SuicideSystem : EntitySystem var suicideByEnvironmentEvent = new SuicideByEnvironmentEvent(victim); // Try to suicide by raising an event on the held item - if (EntityManager.TryGetComponent(victim, out HandsComponent? handsComponent) - && handsComponent.ActiveHandEntity is { } item) + if (_hands.TryGetActiveItem(victim.Owner, out var item)) { - RaiseLocalEvent(item, suicideByEnvironmentEvent); + RaiseLocalEvent(item.Value, suicideByEnvironmentEvent); if (suicideByEnvironmentEvent.Handled) { args.Handled = suicideByEnvironmentEvent.Handled; diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index e489cdf100..767399335a 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -471,7 +471,7 @@ namespace Content.Server.Construction } if (!_actionBlocker.CanInteract(user, null) - || !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.ActiveHandEntity == null) + || !EntityManager.TryGetComponent(user, out HandsComponent? hands) || _handsSystem.GetActiveItem((user, hands)) == null) { Cleanup(); return; @@ -496,7 +496,7 @@ namespace Content.Server.Construction var valid = false; - if (hands.ActiveHandEntity is not {Valid: true} holding) + if (_handsSystem.GetActiveItem((user, hands)) is not {Valid: true} holding) { Cleanup(); return; diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 357046be56..1ccb80b32e 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -1,5 +1,4 @@ using System.Numerics; -using Content.Server.Inventory; using Content.Server.Stack; using Content.Server.Stunnable; using Content.Shared.ActionBlocker; @@ -10,9 +9,7 @@ using Content.Shared.Explosion; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; -using Content.Shared.Inventory.VirtualItem; using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Stacks; using Content.Shared.Standing; @@ -24,7 +21,6 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Server.Hands.Systems { @@ -87,10 +83,9 @@ namespace Content.Server.Hands.Systems if (ent.Comp.DisableExplosionRecursion) return; - foreach (var hand in ent.Comp.Hands.Values) + foreach (var held in EnumerateHeld(ent.AsNullable())) { - if (hand.HeldEntity is { } uid) - args.Contents.Add(uid); + args.Contents.Add(held); } } @@ -112,7 +107,7 @@ namespace Content.Server.Hands.Systems args.Handled = true; // no shove/stun. } - private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args) + private void HandleBodyPartAdded(Entity ent, ref BodyPartAddedEvent args) { if (args.Part.Comp.PartType != BodyPartType.Hand) return; @@ -127,7 +122,7 @@ namespace Content.Server.Hands.Systems _ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry)) }; - AddHand(uid, args.Slot, location); + AddHand(ent.AsNullable(), args.Slot, location); } private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args) @@ -155,8 +150,8 @@ namespace Content.Server.Hands.Systems { if (ContainerSystem.IsEntityInContainer(player) || !TryComp(player, out HandsComponent? hands) || - hands.ActiveHandEntity is not { } throwEnt || - !_actionBlockerSystem.CanThrow(player, throwEnt)) + !TryGetActiveItem((player, hands), out var throwEnt) || + !_actionBlockerSystem.CanThrow(player, throwEnt.Value)) return false; if (_timing.CurTime < hands.NextThrowTime) @@ -165,7 +160,7 @@ namespace Content.Server.Hands.Systems if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually) { - var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent(player).Coordinates, stack); + var splitStack = _stackSystem.Split(throwEnt.Value, 1, EntityManager.GetComponent(player).Coordinates, stack); if (splitStack is not {Valid: true}) return false; @@ -185,14 +180,14 @@ namespace Content.Server.Hands.Systems // Let other systems change the thrown entity (useful for virtual items) // or the throw strength. - var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player); + var ev = new BeforeThrowEvent(throwEnt.Value, direction, throwSpeed, player); RaiseLocalEvent(player, ref ev); if (ev.Cancelled) return true; // This can grief the above event so we raise it afterwards - if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands)) + if (IsHolding((player, hands), throwEnt, out _) && !TryDrop(player, throwEnt.Value)) return false; _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp(ev.ItemUid)); @@ -207,20 +202,20 @@ namespace Content.Server.Hands.Systems var spreadMaxAngle = Angle.FromDegrees(DropHeldItemsSpread); var fellEvent = new FellDownEvent(entity); - RaiseLocalEvent(entity, fellEvent, false); + RaiseLocalEvent(entity, fellEvent); - foreach (var hand in entity.Comp.Hands.Values) + foreach (var hand in entity.Comp.Hands.Keys) { - if (hand.HeldEntity is not EntityUid held) + if (!TryGetHeldItem(entity.AsNullable(), hand, out var heldEntity)) continue; var throwAttempt = new FellDownThrowAttemptEvent(entity); - RaiseLocalEvent(hand.HeldEntity.Value, ref throwAttempt); + RaiseLocalEvent(heldEntity.Value, ref throwAttempt); if (throwAttempt.Cancelled) continue; - if (!TryDrop(entity, hand, null, checkActionBlocker: false, handsComp: entity.Comp)) + if (!TryDrop(entity.AsNullable(), hand, checkActionBlocker: false)) continue; // Rotate the item's throw vector a bit for each item @@ -231,12 +226,12 @@ namespace Content.Server.Hands.Systems itemVelocity *= _random.NextFloat(1f); // Heavier objects don't get thrown as far // If the item doesn't have a physics component, it isn't going to get thrown anyway, but we'll assume infinite mass - itemVelocity *= _physicsQuery.TryComp(held, out var heldPhysics) ? heldPhysics.InvMass : 0; + itemVelocity *= _physicsQuery.TryComp(heldEntity, out var heldPhysics) ? heldPhysics.InvMass : 0; // Throw at half the holder's intentional throw speed and // vary the speed a little to make it look more interesting var throwSpeed = entity.Comp.BaseThrowspeed * _random.NextFloat(0.45f, 0.55f); - _throwingSystem.TryThrow(held, + _throwingSystem.TryThrow(heldEntity.Value, itemVelocity, throwSpeed, entity, diff --git a/Content.Server/HotPotato/HotPotatoSystem.cs b/Content.Server/HotPotato/HotPotatoSystem.cs index 115a7b6cb7..8ca33fb8cd 100644 --- a/Content.Server/HotPotato/HotPotatoSystem.cs +++ b/Content.Server/HotPotato/HotPotatoSystem.cs @@ -43,7 +43,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem if (!TryComp(hitEntity, out var hands)) continue; - if (!_hands.IsHolding(hitEntity, uid, out _, hands) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands)) + if (!_hands.IsHolding((hitEntity, hands), uid, out _) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands)) { _popup.PopupEntity(Loc.GetString("hot-potato-passed", ("from", args.User), ("to", hitEntity)), uid, PopupType.Medium); diff --git a/Content.Server/NPC/HTN/Preconditions/ActiveHandComponentPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/ActiveHandComponentPrecondition.cs index e2e65684ff..93f7679766 100644 --- a/Content.Server/NPC/HTN/Preconditions/ActiveHandComponentPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/ActiveHandComponentPrecondition.cs @@ -1,4 +1,4 @@ -using Content.Shared.Hands.Components; +using Content.Server.Hands.Systems; using Robust.Shared.Prototypes; namespace Content.Server.NPC.HTN.Preconditions; @@ -18,14 +18,18 @@ public sealed partial class ActiveHandComponentPrecondition : HTNPrecondition public override bool IsMet(NPCBlackboard blackboard) { - if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out var hand, _entManager) || hand.HeldEntity == null) + if (!blackboard.TryGetValue(NPCBlackboard.Owner, out var owner, _entManager) || + !blackboard.TryGetValue(NPCBlackboard.ActiveHand, out var hand, _entManager)) { return Invert; } + if (!_entManager.System().TryGetHeldItem(owner, hand, out var entity)) + return Invert; + foreach (var comp in Components) { - var hasComp = _entManager.HasComponent(hand.HeldEntity, comp.Value.Component.GetType()); + var hasComp = _entManager.HasComponent(entity, comp.Value.Component.GetType()); if (!hasComp || Invert && hasComp) diff --git a/Content.Server/NPC/HTN/Preconditions/ActiveHandEntityPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/ActiveHandEntityPrecondition.cs index 0e6c63a433..049d81130a 100644 --- a/Content.Server/NPC/HTN/Preconditions/ActiveHandEntityPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/ActiveHandEntityPrecondition.cs @@ -1,4 +1,4 @@ -using Content.Shared.Hands.Components; +using Content.Server.Hands.Systems; namespace Content.Server.NPC.HTN.Preconditions; @@ -11,11 +11,12 @@ public sealed partial class ActiveHandEntityPrecondition : HTNPrecondition public override bool IsMet(NPCBlackboard blackboard) { - if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager)) + if (!blackboard.TryGetValue(NPCBlackboard.Owner, out EntityUid owner, _entManager) || + !blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, _entManager)) { return false; } - return activeHand.HeldEntity != null; + return !_entManager.System().HandIsEmpty(owner, activeHand); } } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DropOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DropOperator.cs index 0ea062a910..89d68283b6 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DropOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DropOperator.cs @@ -12,7 +12,7 @@ public sealed partial class DropOperator : HTNOperator public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { - if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager)) + if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, _entManager)) { return HTNOperatorStatus.Finished; } diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs index ffdb55045d..8b70569177 100644 --- a/Content.Server/NPC/NPCBlackboard.cs +++ b/Content.Server/NPC/NPCBlackboard.cs @@ -1,11 +1,10 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; -using Content.Server.Interaction; +using Content.Server.Hands.Systems; using Content.Shared.Access.Systems; using Content.Shared.ActionBlocker; using Content.Shared.Hands.Components; using Content.Shared.Interaction; -using Content.Shared.Inventory; using JetBrains.Annotations; using Robust.Shared.Utility; @@ -152,6 +151,8 @@ public sealed partial class NPCBlackboard : IEnumerable(); + switch (key) { case Access: @@ -168,25 +169,24 @@ public sealed partial class NPCBlackboard : IEnumerable(owner, out var hands) || - hands.ActiveHand == null) + handSys.GetActiveHand(owner) is not { } activeHand) { return false; } - value = hands.ActiveHand; + value = activeHand; return true; } case ActiveHandFree: { if (!TryGetValue(Owner, out owner, entManager) || !entManager.TryGetComponent(owner, out var hands) || - hands.ActiveHand == null) + handSys.GetActiveHand(owner) is not { } activeHand) { return false; } - value = hands.ActiveHand.IsEmpty; + value = handSys.HandIsEmpty((owner, hands), activeHand); return true; } case CanMove: @@ -204,16 +204,16 @@ public sealed partial class NPCBlackboard : IEnumerable(owner, out var hands) || - hands.ActiveHand == null) + handSys.GetActiveHand(owner) is null) { return false; } var handos = new List(); - foreach (var (id, hand) in hands.Hands) + foreach (var id in hands.Hands.Keys) { - if (!hand.IsEmpty) + if (!handSys.HandIsEmpty((owner, hands), id)) continue; handos.Add(id); @@ -226,16 +226,16 @@ public sealed partial class NPCBlackboard : IEnumerable(owner, out var hands) || - hands.ActiveHand == null) + handSys.GetActiveHand(owner) is null) { return false; } var handos = new List(); - foreach (var (id, hand) in hands.Hands) + foreach (var id in hands.Hands.Keys) { - if (!hand.IsEmpty) + if (!handSys.HandIsEmpty((owner, hands), id)) continue; handos.Add(id); diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index c5b463d0d3..489ac6de55 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.Components; using Content.Server.Fluids.EntitySystems; +using Content.Server.Hands.Systems; using Content.Server.NPC.Queries; using Content.Server.NPC.Queries.Considerations; using Content.Server.NPC.Queries.Curves; @@ -44,6 +45,7 @@ public sealed class NPCUtilitySystem : EntitySystem [Dependency] private readonly DrinkSystem _drink = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly FoodSystem _food = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; @@ -256,8 +258,9 @@ public sealed class NPCUtilitySystem : EntitySystem } case TargetAmmoMatchesCon: { - if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, EntityManager) || - !TryComp(activeHand.HeldEntity, out var heldGun)) + if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, EntityManager) || + !_hands.TryGetHeldItem(owner, activeHand, out var heldEntity) || + !TryComp(heldEntity, out var heldGun)) { return 0f; } diff --git a/Content.Server/Shuttles/Commands/FTLDiskCommand.cs b/Content.Server/Shuttles/Commands/FTLDiskCommand.cs index 014dbe6d99..63d45e2364 100644 --- a/Content.Server/Shuttles/Commands/FTLDiskCommand.cs +++ b/Content.Server/Shuttles/Commands/FTLDiskCommand.cs @@ -152,7 +152,7 @@ public sealed class FTLDiskCommand : LocalizedCommands if (_entManager.TryGetComponent(cdCaseUid, out var storage) && storageSystem.Insert(cdCaseUid, cdUid, out _, storageComp: storage, playSound: false)) { - if (_entManager.TryGetComponent(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) + if (_entManager.TryGetComponent(entity, out var handsComponent) && handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand)) { handsSystem.TryPickup(entity, cdCaseUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent); } @@ -161,7 +161,7 @@ public sealed class FTLDiskCommand : LocalizedCommands { _entManager.DeleteEntity(cdCaseUid); // something went wrong so just yeet the chaf - if (_entManager.TryGetComponent(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) + if (_entManager.TryGetComponent(entity, out var handsComponent) && handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand)) { handsSystem.TryPickup(entity, cdUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent); } diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index 784c2f8fbb..1b6040773c 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -217,8 +217,8 @@ public sealed partial class BorgSystem var handId = $"{uid}-item{component.HandCounter}"; component.HandCounter++; - _hands.AddHand(chassis, handId, HandLocation.Middle, hands); - _hands.DoPickup(chassis, hands.Hands[handId], item, hands); + _hands.AddHand((chassis, hands), handId, HandLocation.Middle); + _hands.DoPickup(chassis, handId, item, hands); EnsureComp(item); component.ProvidedItems.Add(handId, item); } @@ -239,7 +239,7 @@ public sealed partial class BorgSystem foreach (var (hand, item) in component.ProvidedItems) { QueueDel(item); - _hands.RemoveHand(chassis, hand, hands); + _hands.RemoveHand(chassis, hand); } component.ProvidedItems.Clear(); return; @@ -252,7 +252,7 @@ public sealed partial class BorgSystem RemComp(item); _container.Insert(item, component.ProvidedContainer); } - _hands.RemoveHand(chassis, handId, hands); + _hands.RemoveHand(chassis, handId); } component.ProvidedItems.Clear(); } diff --git a/Content.Server/Tabletop/TabletopSystem.cs b/Content.Server/Tabletop/TabletopSystem.cs index 6938372233..f3f5801b2d 100644 --- a/Content.Server/Tabletop/TabletopSystem.cs +++ b/Content.Server/Tabletop/TabletopSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Hands.Systems; using Content.Server.Popups; using Content.Server.Tabletop.Components; using Content.Shared.CCVar; @@ -22,6 +23,7 @@ namespace Content.Server.Tabletop { [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly EyeSystem _eye = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; @@ -84,18 +86,13 @@ namespace Content.Server.Tabletop if (component.Session is not { } session) return; - if (hands.ActiveHand == null) + if (!_hands.TryGetActiveItem(uid, out var handEnt)) return; - if (hands.ActiveHand.HeldEntity == null) - return; - - var handEnt = hands.ActiveHand.HeldEntity.Value; - if (!TryComp(handEnt, out var item)) return; - var meta = MetaData(handEnt); + var meta = MetaData(handEnt.Value); var protoId = meta.EntityPrototype?.ID; var hologram = Spawn(protoId, session.Position.Offset(-1, 0)); diff --git a/Content.Server/Tools/Innate/InnateToolSystem.cs b/Content.Server/Tools/Innate/InnateToolSystem.cs index b8d1dd935c..6afe886303 100644 --- a/Content.Server/Tools/Innate/InnateToolSystem.cs +++ b/Content.Server/Tools/Innate/InnateToolSystem.cs @@ -90,9 +90,9 @@ public sealed class InnateToolSystem : EntitySystem if (TryComp(uid, out var hands)) { - foreach (var hand in hands.Hands) + foreach (var hand in hands.Hands.Keys) { - _sharedHandsSystem.TryDrop(uid, hand.Value, checkActionBlocker: false, handsComp: hands); + _sharedHandsSystem.TryDrop((uid, hands), hand, checkActionBlocker: false); } } } diff --git a/Content.Server/Verbs/VerbSystem.cs b/Content.Server/Verbs/VerbSystem.cs index a0ca0a80df..7a1e5facd0 100644 --- a/Content.Server/Verbs/VerbSystem.cs +++ b/Content.Server/Verbs/VerbSystem.cs @@ -1,10 +1,10 @@ using System.Linq; using Content.Server.Administration.Managers; +using Content.Server.Hands.Systems; using Content.Server.Popups; using Content.Shared.Administration; using Content.Shared.Administration.Logs; using Content.Shared.Database; -using Content.Shared.Hands.Components; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Verbs; using Robust.Shared.Utility; @@ -14,6 +14,7 @@ namespace Content.Server.Verbs public sealed class VerbSystem : SharedVerbSystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly IAdminManager _adminMgr = default!; @@ -91,8 +92,7 @@ namespace Content.Server.Verbs { // first get the held item. again. EntityUid? holding = null; - if (TryComp(user, out HandsComponent? hands) && - hands.ActiveHandEntity is EntityUid heldEntity) + if (_hands.GetActiveItem(user) is { } heldEntity) { holding = heldEntity; } diff --git a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs index c3fde14517..1755c6df08 100644 --- a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs +++ b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Hands.Systems; using Content.Server.Storage.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Database; -using Content.Shared.Hands.Components; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Storage; @@ -40,24 +39,20 @@ public sealed class StorageVoiceControlSystem : EntitySystem if (!TryComp(ent, out var storage)) return; - // Get the hands component - if (!TryComp(args.Source, out var hands)) - return; - // If the player has something in their hands, try to insert it into the storage - if (hands.ActiveHand != null && hands.ActiveHand.HeldEntity.HasValue) + if (_hands.TryGetActiveItem(ent.Owner, out var activeItem)) { // Disallow insertion and provide a reason why if the person decides to insert the item into itself - if (ent.Owner.Equals(hands.ActiveHand.HeldEntity.Value)) + if (ent.Owner.Equals(activeItem.Value)) { - _popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", hands.ActiveHand.HeldEntity.Value)), ent, args.Source); + _popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", activeItem.Value)), ent, args.Source); return; } - if (_storage.CanInsert(ent, hands.ActiveHand.HeldEntity.Value, out var failedReason)) + if (_storage.CanInsert(ent, activeItem.Value, out var failedReason)) { // We adminlog before insertion, otherwise the logger will attempt to pull info on an entity that no longer is present and throw an exception - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control"); - _storage.Insert(ent, hands.ActiveHand.HeldEntity.Value, out _); + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(activeItem.Value)} into {ToPrettyString(ent)} via voice control"); + _storage.Insert(ent, activeItem.Value, out _); return; } { @@ -67,7 +62,7 @@ public sealed class StorageVoiceControlSystem : EntitySystem _popup.PopupEntity(Loc.GetString(failedReason), ent, args.Source); _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control"); + $"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(activeItem.Value)} into {ToPrettyString(ent)} via voice control"); } return; } @@ -80,7 +75,7 @@ public sealed class StorageVoiceControlSystem : EntitySystem // E.g "go go s" would give you the screwdriver because "screwdriver" contains "s" if (Name(item).Contains(args.MessageWithoutPhrase)) { - ExtractItemFromStorage(ent, item, args.Source, hands); + ExtractItemFromStorage(ent, item, args.Source); break; } } @@ -92,16 +87,14 @@ public sealed class StorageVoiceControlSystem : EntitySystem /// The entity with the /// The entity to be extracted from the attached storage /// The entity wearing the item - /// The of the person wearing the item private void ExtractItemFromStorage(Entity ent, EntityUid item, - EntityUid source, - HandsComponent hands) + EntityUid source) { _container.RemoveEntity(ent, item); _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(source)} retrieved {ToPrettyString(item)} from {ToPrettyString(ent)} via voice control"); - _hands.TryPickup(source, item, handsComp: hands); + _hands.TryPickup(source, item); } } diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index 7f382e15bb..e6a50425b4 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using Content.Server.Construction; using Content.Server.Construction.Components; +using Content.Server.Hands.Systems; using Content.Server.Power.Components; using Content.Shared.DoAfter; using Content.Shared.GameTicking; @@ -24,6 +25,7 @@ public sealed class WiresSystem : SharedWiresSystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; @@ -405,19 +407,13 @@ public sealed class WiresSystem : SharedWiresSystem return; } - var activeHand = handsComponent.ActiveHand; - - if (activeHand == null) + if (!_hands.TryGetActiveItem((player, handsComponent), out var heldEntity)) return; - if (activeHand.HeldEntity == null) + if (!EntityManager.TryGetComponent(heldEntity, out ToolComponent? tool)) return; - var activeHandEntity = activeHand.HeldEntity.Value; - if (!EntityManager.TryGetComponent(activeHandEntity, out ToolComponent? tool)) - return; - - TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool); + TryDoWireAction(uid, player, heldEntity.Value, args.Id, args.Action, component, tool); } private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args) diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs index dd603cbb02..513abb20ee 100644 --- a/Content.Shared/Access/Systems/SharedIdCardSystem.cs +++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs @@ -3,7 +3,7 @@ using Content.Shared.Access.Components; using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; -using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Inventory; using Content.Shared.PDA; @@ -21,6 +21,7 @@ public abstract class SharedIdCardSystem : EntitySystem [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedAccessSystem _access = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -83,8 +84,7 @@ public abstract class SharedIdCardSystem : EntitySystem public bool TryFindIdCard(EntityUid uid, out Entity idCard) { // check held item? - if (TryComp(uid, out HandsComponent? hands) && - hands.ActiveHandEntity is EntityUid heldItem && + if (_hands.GetActiveItem(uid) is { } heldItem && TryGetIdCard(heldItem, out idCard)) { return true; diff --git a/Content.Shared/Blocking/BlockingSystem.cs b/Content.Shared/Blocking/BlockingSystem.cs index 619adf7918..6223f8e840 100644 --- a/Content.Shared/Blocking/BlockingSystem.cs +++ b/Content.Shared/Blocking/BlockingSystem.cs @@ -97,7 +97,7 @@ public sealed partial class BlockingSystem : EntitySystem if (!handQuery.TryGetComponent(args.Performer, out var hands)) return; - var shields = _handsSystem.EnumerateHeld(args.Performer, hands).ToArray(); + var shields = _handsSystem.EnumerateHeld((args.Performer, hands)).ToArray(); foreach (var shield in shields) { @@ -277,7 +277,7 @@ public sealed partial class BlockingSystem : EntitySystem if (!handQuery.TryGetComponent(user, out var hands)) return; - var shields = _handsSystem.EnumerateHeld(user, hands).ToArray(); + var shields = _handsSystem.EnumerateHeld((user, hands)).ToArray(); foreach (var shield in shields) { diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs index f536beef2b..e314db0ddb 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs @@ -947,12 +947,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem if (!entity.Comp.HeldOnly) return true; - if (TryComp(examiner, out HandsComponent? handsComp)) - { - return Hands.IsHolding(examiner, entity, out _, handsComp); - } - - return true; + return Hands.IsHolding(examiner, entity, out _); } private void OnMapInit(Entity entity, ref MapInitEvent args) diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 3b5a880d46..4f5f567e43 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -255,7 +255,7 @@ namespace Content.Shared.Containers.ItemSlots } // Drop the held item onto the floor. Return if the user cannot drop. - if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands)) + if (!_handsSystem.TryDrop(args.User, args.Used)) return; slots.Sort(SortEmpty); @@ -395,17 +395,17 @@ namespace Content.Shared.Containers.ItemSlots if (!Resolve(user, ref hands, false)) return false; - if (hands.ActiveHand?.HeldEntity is not { } held) + if (!_handsSystem.TryGetActiveItem((uid, hands), out var held)) return false; - if (!CanInsert(uid, held, user, slot)) + if (!CanInsert(uid, held.Value, user, slot)) return false; // hands.Drop(item) checks CanDrop action blocker - if (!_handsSystem.TryDrop(user, hands.ActiveHand)) + if (!_handsSystem.TryDrop(user, hands.ActiveHandId!)) return false; - Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio); + Insert(uid, slot, held.Value, user, excludeUserAudio: excludeUserAudio); return true; } @@ -428,16 +428,14 @@ namespace Content.Shared.Containers.ItemSlots if (!Resolve(ent, ref ent.Comp, false)) return false; - TryComp(user, out HandsComponent? handsComp); - if (!TryGetAvailableSlot(ent, item, - user == null ? null : (user.Value, handsComp), + user, out var itemSlot, emptyOnly: true)) return false; - if (user != null && !_handsSystem.TryDrop(user.Value, item, handsComp: handsComp)) + if (user != null && !_handsSystem.TryDrop(user.Value, item)) return false; Insert(ent, itemSlot, item, user, excludeUserAudio: excludeUserAudio); @@ -466,7 +464,7 @@ namespace Content.Shared.Containers.ItemSlots && Resolve(user, ref user.Comp) && _handsSystem.IsHolding(user, item)) { - if (!_handsSystem.CanDrop(user, item, user.Comp)) + if (!_handsSystem.CanDrop(user, item)) return false; } diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index c55bbdb152..c16bbd1e85 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -397,6 +397,10 @@ namespace Content.Shared.Cuffs /// private void OnHandCountChanged(Entity ent, ref HandCountChangedEvent message) { + // TODO: either don't store a container ref, or make it actually nullable. + if (ent.Comp.Container == default!) + return; + var dirty = false; var handCount = CompOrNull(ent.Owner)?.Count ?? 0; @@ -431,19 +435,19 @@ namespace Content.Shared.Cuffs return; var freeHands = 0; - foreach (var hand in _hands.EnumerateHands(uid, handsComponent)) + foreach (var hand in _hands.EnumerateHands((uid, handsComponent))) { - if (hand.HeldEntity == null) + if (!_hands.TryGetHeldItem((uid, handsComponent), hand, out var held)) { freeHands++; continue; } // Is this entity removable? (it might be an existing handcuff blocker) - if (HasComp(hand.HeldEntity)) + if (HasComp(held)) continue; - _hands.DoDrop(uid, hand, true, handsComponent); + _hands.DoDrop(uid, hand, true); freeHands++; if (freeHands == 2) break; diff --git a/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs index 1db24c700d..e92552ef6d 100644 --- a/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs +++ b/Content.Shared/Disposal/Unit/SharedDisposalUnitSystem.cs @@ -141,7 +141,7 @@ public abstract class SharedDisposalUnitSystem : EntitySystem Category = VerbCategory.Insert, Act = () => { - _handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands); + _handsSystem.TryDropIntoContainer((args.User, args.Hands), args.Using.Value, component.Container, checkActionBlocker: false); _adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}"); AfterInsert(uid, component, args.Using.Value, args.User); } diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index f9718be52d..f6eb9ef996 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -233,7 +233,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem return true; // If the user changes which hand is active at all, interrupt the do-after - if (args.BreakOnHandChange && hands.ActiveHand?.Name != doAfter.InitialHand) + if (args.BreakOnHandChange && hands.ActiveHandId != doAfter.InitialHand) return true; } diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs index 9765bac912..1dc1e58be6 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -221,8 +221,8 @@ public abstract partial class SharedDoAfterSystem : EntitySystem if (!TryComp(args.User, out HandsComponent? handsComponent)) return false; - doAfter.InitialHand = handsComponent.ActiveHand?.Name; - doAfter.InitialItem = handsComponent.ActiveHandEntity; + doAfter.InitialHand = handsComponent.ActiveHandId; + doAfter.InitialItem = _hands.GetActiveItem((args.User, handsComponent)); } doAfter.NetInitialItem = GetNetEntity(doAfter.InitialItem); diff --git a/Content.Shared/Foldable/DeployFoldableSystem.cs b/Content.Shared/Foldable/DeployFoldableSystem.cs index a83518dfa3..cac73f6428 100644 --- a/Content.Shared/Foldable/DeployFoldableSystem.cs +++ b/Content.Shared/Foldable/DeployFoldableSystem.cs @@ -70,7 +70,7 @@ public sealed class DeployFoldableSystem : EntitySystem } if (!TryComp(args.User, out HandsComponent? hands) - || !_hands.TryDrop(args.User, args.Used, targetDropLocation: args.ClickLocation, handsComp: hands)) + || !_hands.TryDrop((args.User, hands), args.Used, targetDropLocation: args.ClickLocation)) return; if (!_foldable.TrySetFolded(ent, foldable, false)) diff --git a/Content.Shared/Hands/Components/HandHelpers.cs b/Content.Shared/Hands/Components/HandHelpers.cs deleted file mode 100644 index aecf3a6936..0000000000 --- a/Content.Shared/Hands/Components/HandHelpers.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Linq; -using Content.Shared.Hands.EntitySystems; - -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 HandsComponent 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 HandsComponent component) => component.Hands.Values.Count(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 CountFreeableHands(this Entity component, SharedHandsSystem system) - { - return system.CountFreeableHands(component); - } - - /// - /// 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 HandsComponent 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 HandsComponent component) => GetFreeHands(component).Select(hand => hand.Name); -} diff --git a/Content.Shared/Hands/Components/HandsComponent.cs b/Content.Shared/Hands/Components/HandsComponent.cs index 0af318ba06..2aa97e7841 100644 --- a/Content.Shared/Hands/Components/HandsComponent.cs +++ b/Content.Shared/Hands/Components/HandsComponent.cs @@ -1,6 +1,5 @@ using Content.Shared.DisplacementMap; using Content.Shared.Hands.EntitySystems; -using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -13,50 +12,50 @@ public sealed partial class HandsComponent : Component /// /// The currently active hand. /// - [ViewVariables] - public Hand? ActiveHand; + [DataField] + public string? ActiveHandId; /// - /// The item currently held in the active hand. + /// Dictionary relating a unique hand ID corresponding to a container slot on the attached entity to a class containing information about the Hand itself. /// - [ViewVariables] - public EntityUid? ActiveHandEntity => ActiveHand?.HeldEntity; - - [ViewVariables] + [DataField] public Dictionary Hands = new(); + /// + /// The number of hands + /// + [ViewVariables] public int Count => Hands.Count; /// /// List of hand-names. These are keys for . The order of this list determines the order in which hands are iterated over. /// + [DataField] public List SortedHands = new(); /// /// If true, the items in the hands won't be affected by explosions. /// [DataField] - public bool DisableExplosionRecursion = false; + public bool DisableExplosionRecursion; /// /// Modifies the speed at which items are thrown. /// [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public float BaseThrowspeed { get; set; } = 11f; + public float BaseThrowspeed = 11f; /// /// Distance after which longer throw targets stop increasing throw impulse. /// - [DataField("throwRange")] - [ViewVariables(VVAccess.ReadWrite)] - public float ThrowRange { get; set; } = 8f; + [DataField] + public float ThrowRange = 8f; /// /// Whether or not to add in-hand sprites for held items. Some entities (e.g., drones) don't want these. /// Used by the client. /// - [DataField("showInHands")] + [DataField] public bool ShowInHands = true; /// @@ -68,14 +67,13 @@ public sealed partial class HandsComponent : Component /// /// The time at which throws will be allowed again. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - [AutoPausedField] + [DataField, AutoPausedField] public TimeSpan NextThrowTime; /// /// The minimum time inbetween throws. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f); /// @@ -103,48 +101,37 @@ public sealed partial class HandsComponent : Component public bool CanBeStripped = true; } +[DataDefinition] [Serializable, NetSerializable] -public sealed class Hand //TODO: This should definitely be a struct - Jezi +public partial record struct Hand { - [ViewVariables] - public string Name { get; } + [DataField] + public HandLocation Location = HandLocation.Right; - [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) + public Hand() + { + + } + + public Hand(HandLocation location) { - 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 readonly Dictionary Hands; + public readonly List SortedHands; + public readonly string? ActiveHandId; public HandsComponentState(HandsComponent handComp) { // cloning lists because of test networking. - Hands = new(handComp.Hands.Values); - HandNames = new(handComp.SortedHands); - ActiveHand = handComp.ActiveHand?.Name; + Hands = new(handComp.Hands); + SortedHands = new(handComp.SortedHands); + ActiveHandId = handComp.ActiveHandId; } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs index 1eac3a2263..e84b23400b 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.AI.cs @@ -6,17 +6,17 @@ 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 abstract partial class SharedHandsSystem { public bool TrySelect(EntityUid uid, EntityUid? entity, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp, false)) return false; - if (!IsHolding(uid, entity, out var hand, handsComp)) + if (!IsHolding((uid, handsComp), entity, out var hand)) return false; - SetActiveHand(uid, hand, handsComp); + SetActiveHand((uid, handsComp), hand); return true; } @@ -26,9 +26,12 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; - foreach (var hand in handsComp.Hands.Values) + foreach (var hand in handsComp.Hands.Keys) { - if (TryComp(hand.HeldEntity, out component)) + if (!TryGetHeldItem((uid, handsComp), hand, out var held)) + continue; + + if (TryComp(held, out component)) return true; } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs index 95773697db..c1b44efba4 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs @@ -7,6 +7,7 @@ using Content.Shared.Tag; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Shared.Hands.EntitySystems; @@ -28,10 +29,10 @@ public abstract partial class SharedHandsSystem return; } - var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand); + var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand.Value); RaiseLocalEvent(args.Entity, gotUnequipped); - var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand); + var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand.Value); RaiseLocalEvent(uid, didUnequip); if (TryComp(args.Entity, out VirtualItemComponent? @virtual)) @@ -47,26 +48,29 @@ public abstract partial class SharedHandsSystem /// /// Checks whether an entity can drop a given entity. Will return false if they are not holding the entity. /// - public bool CanDrop(EntityUid uid, EntityUid entity, HandsComponent? handsComp = null, bool checkActionBlocker = true) + public bool CanDrop(Entity ent, EntityUid entity, bool checkActionBlocker = true) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (!IsHolding(uid, entity, out var hand, handsComp)) + if (!IsHolding(ent, entity, out var hand)) return false; - return CanDropHeld(uid, hand, checkActionBlocker); + return CanDropHeld(ent, hand, checkActionBlocker); } /// /// 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) + public bool CanDropHeld(EntityUid uid, string handId, bool checkActionBlocker = true) { - if (hand.Container?.ContainedEntity is not {} held) + if (!ContainerSystem.TryGetContainer(uid, handId, out var container)) return false; - if (!ContainerSystem.CanRemove(held, hand.Container)) + if (container.ContainedEntities.FirstOrNull() is not {} held) + return false; + + if (!ContainerSystem.CanRemove(held, container)) return false; if (checkActionBlocker && !_actionBlocker.CanDrop(uid)) @@ -78,98 +82,100 @@ public abstract partial class SharedHandsSystem /// /// Attempts to drop the item in the currently active hand. /// - public bool TryDrop(EntityUid uid, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null) + public bool TryDrop(Entity ent, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (handsComp.ActiveHand == null) + if (ent.Comp.ActiveHandId == null) return false; - return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp); + return TryDrop(ent, ent.Comp.ActiveHandId, targetDropLocation, checkActionBlocker, doDropInteraction); } /// /// Drops an item at the target location. /// - public bool TryDrop(EntityUid uid, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null) + public bool TryDrop(Entity ent, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (!IsHolding(uid, entity, out var hand, handsComp)) + if (!IsHolding(ent, entity, out var hand)) return false; - return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp); + return TryDrop(ent, hand, targetDropLocation, checkActionBlocker, doDropInteraction); } /// /// Drops a hands contents at the target location. /// - public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null) + public bool TryDrop(Entity ent, string handId, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (!CanDropHeld(uid, hand, checkActionBlocker)) + if (!CanDropHeld(ent, handId, checkActionBlocker)) return false; - var entity = hand.HeldEntity!.Value; + if (!TryGetHeldItem(ent, handId, out var entity)) + return false; // if item is a fake item (like with pulling), just delete it rather than bothering with trying to drop it into the world if (TryComp(entity, out VirtualItemComponent? @virtual)) - _virtualSystem.DeleteVirtualItem((entity, @virtual), uid); + _virtualSystem.DeleteVirtualItem((entity.Value, @virtual), ent); if (TerminatingOrDeleted(entity)) return true; - var itemXform = Transform(entity); + var itemXform = Transform(entity.Value); if (itemXform.MapUid == null) return true; - var userXform = Transform(uid); - var isInContainer = ContainerSystem.IsEntityOrParentInContainer(uid, xform: userXform); + var userXform = Transform(ent); + var isInContainer = ContainerSystem.IsEntityOrParentInContainer(ent, xform: userXform); // if the user is in a container, drop the item inside the container - if (isInContainer) { - TransformSystem.DropNextTo((entity, itemXform), (uid, userXform)); + if (isInContainer) + { + TransformSystem.DropNextTo((entity.Value, itemXform), (ent, userXform)); return true; } // drop the item with heavy calculations from their hands and place it at the calculated interaction range position // The DoDrop is handle if there's no drop target - DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp); + DoDrop(ent, handId, doDropInteraction: doDropInteraction); // if there's no drop location stop here if (targetDropLocation == null) return true; // otherwise, also move dropped item and rotate it properly according to grid/map - var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity); + var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity.Value); var origin = new MapCoordinates(itemPos, itemXform.MapID); var target = TransformSystem.ToMapCoordinates(targetDropLocation.Value); - TransformSystem.SetWorldPositionRotation(entity, GetFinalDropCoordinates(uid, origin, target, entity), itemRot); + TransformSystem.SetWorldPositionRotation(entity.Value, GetFinalDropCoordinates(ent, origin, target, entity.Value), itemRot); 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, HandsComponent? handsComp = null) + public bool TryDropIntoContainer(Entity ent, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (!IsHolding(uid, entity, out var hand, handsComp)) + if (!IsHolding(ent, entity, out var hand)) return false; - if (!CanDropHeld(uid, hand, checkActionBlocker)) + if (!CanDropHeld(ent, hand, checkActionBlocker)) return false; if (!ContainerSystem.CanInsert(entity, targetContainer)) return false; - DoDrop(uid, hand, false, handsComp); + DoDrop(ent, hand, false); ContainerSystem.Insert(entity, targetContainer); return true; } @@ -202,34 +208,38 @@ public abstract partial class SharedHandsSystem /// /// 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, HandsComponent? handsComp = null, bool log = true) + public virtual void DoDrop(Entity ent, + string handId, + bool doDropInteraction = true, + bool log = true) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return; - if (hand.Container?.ContainedEntity == null) + if (!ContainerSystem.TryGetContainer(ent, handId, out var container)) return; - var entity = hand.Container.ContainedEntity.Value; - - if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(entity)) + if (!TryGetHeldItem(ent, handId, out var entity)) return; - if (!ContainerSystem.Remove(entity, hand.Container)) + if (TerminatingOrDeleted(ent) || TerminatingOrDeleted(entity)) + return; + + if (!ContainerSystem.Remove(entity.Value, container)) { - Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(uid)}. Hand: {hand.Name}."); + Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(ent)}. Hand: {handId}."); return; } - Dirty(uid, handsComp); + Dirty(ent); if (doDropInteraction) - _interactionSystem.DroppedInteraction(uid, entity); + _interactionSystem.DroppedInteraction(ent, entity.Value); if (log) - _adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(uid):user} dropped {ToPrettyString(entity):entity}"); + _adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(ent):user} dropped {ToPrettyString(entity):entity}"); - if (hand == handsComp.ActiveHand) - RaiseLocalEvent(entity, new HandDeselectedEvent(uid)); + if (handId == ent.Comp.ActiveHandId) + RaiseLocalEvent(entity.Value, new HandDeselectedEvent(ent)); } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index 6e383cd69c..0e8d7a7556 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -97,20 +97,20 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!_actionBlocker.CanInteract(session.AttachedEntity.Value, null)) return; - if (component.ActiveHand == null || component.Hands.Count < 2) + if (component.ActiveHandId == null || component.Hands.Count < 2) return; - var currentIndex = component.SortedHands.IndexOf(component.ActiveHand.Name); + var currentIndex = component.SortedHands.IndexOf(component.ActiveHandId); var newActiveIndex = (currentIndex + (reverse ? -1 : 1) + component.Hands.Count) % component.Hands.Count; var nextHand = component.SortedHands[newActiveIndex]; - TrySetActiveHand(session.AttachedEntity.Value, nextHand, component); + TrySetActiveHand((session.AttachedEntity.Value, component), nextHand); } private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid netEntity) { - if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHand != null) - TryDrop(session.AttachedEntity.Value, hands.ActiveHand, coords, handsComp: hands); + if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHandId != null) + TryDrop((session.AttachedEntity.Value, hands), hands.ActiveHandId, coords); // always send to server. return false; @@ -122,14 +122,14 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; - Hand? hand; - if (handName == null || !handsComp.Hands.TryGetValue(handName, out hand)) - hand = handsComp.ActiveHand; + var hand = handName; + if (!TryGetHand(uid, hand, out _)) + hand = handsComp.ActiveHandId; - if (hand?.HeldEntity is not { } held) + if (!TryGetHeldItem((uid, handsComp), hand, out var held)) return false; - return _interactionSystem.InteractionActivate(uid, held); + return _interactionSystem.InteractionActivate(uid, held.Value); } public bool TryInteractHandWithActiveHand(EntityUid uid, string handName, HandsComponent? handsComp = null) @@ -137,16 +137,13 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; - if (handsComp.ActiveHandEntity == null) + if (!TryGetActiveItem((uid, handsComp), out var activeHeldItem)) return false; - if (!handsComp.Hands.TryGetValue(handName, out var hand)) + if (!TryGetHeldItem((uid, handsComp), handName, out var held)) return false; - if (hand.HeldEntity == null) - return false; - - _interactionSystem.InteractUsing(uid, handsComp.ActiveHandEntity.Value, hand.HeldEntity.Value, Transform(hand.HeldEntity.Value).Coordinates); + _interactionSystem.InteractUsing(uid, activeHeldItem.Value, held.Value, Transform(held.Value).Coordinates); return true; } @@ -155,17 +152,16 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; - Hand? hand; - if (handName == null || !handsComp.Hands.TryGetValue(handName, out hand)) - hand = handsComp.ActiveHand; + var hand = handName; + if (!TryGetHand(uid, hand, out _)) + hand = handsComp.ActiveHandId; - if (hand?.HeldEntity is not { } held) + if (!TryGetHeldItem((uid, handsComp), hand, out var held)) return false; if (altInteract) - return _interactionSystem.AltInteract(uid, held); - else - return _interactionSystem.UseInHandInteraction(uid, held); + return _interactionSystem.AltInteract(uid, held.Value); + return _interactionSystem.UseInHandInteraction(uid, held.Value); } /// @@ -176,22 +172,20 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp)) return false; - if (handsComp.ActiveHand == null || !handsComp.ActiveHand.IsEmpty) + if (handsComp.ActiveHandId == null || !HandIsEmpty((uid, handsComp), handsComp.ActiveHandId)) return false; - if (!handsComp.Hands.TryGetValue(handName, out var hand)) + if (!TryGetHeldItem((uid, handsComp), handName, out var entity)) return false; - if (!CanDropHeld(uid, hand, checkActionBlocker)) + if (!CanDropHeld(uid, handName, checkActionBlocker)) return false; - var entity = hand.HeldEntity!.Value; - - if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp)) + if (!CanPickupToHand(uid, entity.Value, handsComp.ActiveHandId, checkActionBlocker, handsComp)) return false; - DoDrop(uid, hand, false, handsComp, log:false); - DoPickup(uid, handsComp.ActiveHand, entity, handsComp, log: false); + DoDrop(uid, handName, false, log: false); + DoPickup(uid, handsComp.ActiveHandId, entity.Value, handsComp, log: false); return true; } @@ -200,19 +194,19 @@ public abstract partial class SharedHandsSystem : EntitySystem if (args.Handled) return; - if (component.ActiveHandEntity.HasValue) + if (TryGetActiveItem((uid, component), out var activeHeldItem)) { // allow for the item to return a different entity, e.g. virtual items - RaiseLocalEvent(component.ActiveHandEntity.Value, ref args); + RaiseLocalEvent(activeHeldItem.Value, ref args); } - args.Used ??= component.ActiveHandEntity; + args.Used ??= activeHeldItem; } //TODO: Actually shows all items/clothing/etc. private void HandleExamined(EntityUid examinedUid, HandsComponent handsComp, ExaminedEvent args) { - var heldItemNames = EnumerateHeld(examinedUid, handsComp) + var heldItemNames = EnumerateHeld((examinedUid, handsComp)) .Where(entity => !HasComp(entity)) .Select(item => FormattedMessage.EscapeText(Identity.Name(item, EntityManager))) .Select(itemName => Loc.GetString("comp-hands-examine-wrapper", ("item", itemName))) diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index 5addd7c029..4558f6c528 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -1,15 +1,15 @@ -using Content.Shared.Clothing.Components; +using System.Diagnostics; 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; using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; namespace Content.Shared.Hands.EntitySystems; -public abstract partial class SharedHandsSystem : EntitySystem +public abstract partial class SharedHandsSystem { private void InitializePickup() { @@ -23,11 +23,11 @@ public abstract partial class SharedHandsSystem : EntitySystem return; } - var didEquip = new DidEquipHandEvent(uid, args.Entity, hand); - RaiseLocalEvent(uid, didEquip, false); + var didEquip = new DidEquipHandEvent(uid, args.Entity, hand.Value); + RaiseLocalEvent(uid, didEquip); - var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand); - RaiseLocalEvent(args.Entity, gotEquipped, false); + var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand.Value); + RaiseLocalEvent(args.Entity, gotEquipped); } /// @@ -35,32 +35,6 @@ public abstract partial class SharedHandsSystem : EntitySystem /// public const float MaxAnimationRange = 10; - /// - /// 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, - bool animate = true, - HandsComponent? handsComp = null, - ItemComponent? 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, animate, handsComp, item); - } - /// /// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand. /// @@ -80,17 +54,21 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; - if (!TryGetEmptyHand(uid, out var hand, handsComp)) + if (!TryGetEmptyHand((uid, handsComp), out var hand)) return false; - return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item); + return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item); } + /// + /// 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, - Hand hand, + string? handId = null, bool checkActionBlocker = true, + bool animateUser = false, bool animate = true, HandsComponent? handsComp = null, ItemComponent? item = null) @@ -98,10 +76,15 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; + handId ??= handsComp.ActiveHandId; + + if (handId == null) + return false; + if (!Resolve(entity, ref item, false)) return false; - if (!CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item)) + if (!CanPickupToHand(uid, entity, handId, checkActionBlocker, handsComp, item)) return false; if (animate) @@ -119,7 +102,7 @@ public abstract partial class SharedHandsSystem : EntitySystem _storage.PlayPickupAnimation(entity, initialPosition, xform.Coordinates, itemXform.LocalRotation, uid); } } - DoPickup(uid, hand, entity, handsComp); + DoPickup(uid, handId, entity, handsComp); return true; } @@ -129,20 +112,20 @@ public abstract partial class SharedHandsSystem : EntitySystem /// By default it does check if it's possible to drop items. /// public bool TryForcePickup( - EntityUid uid, + Entity ent, EntityUid entity, - Hand hand, + string hand, bool checkActionBlocker = true, bool animate = true, HandsComponent? handsComp = null, ItemComponent? item = null) { - if (!Resolve(uid, ref handsComp, false)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp); + TryDrop(ent, hand, checkActionBlocker: checkActionBlocker); - return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item); + return TryPickup(ent, entity, hand, checkActionBlocker, animate: animate, handsComp: handsComp, item: item); } /// @@ -157,9 +140,9 @@ public abstract partial class SharedHandsSystem : EntitySystem if (TryPickupAnyHand(uid, entity, checkActionBlocker: checkActionBlocker, handsComp: handsComp)) return true; - foreach (var hand in handsComp.Hands.Values) + foreach (var hand in handsComp.Hands.Keys) { - if (TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp) && + if (TryDrop((uid, handsComp), hand, checkActionBlocker: checkActionBlocker) && TryPickup(uid, entity, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp)) { return true; @@ -173,7 +156,7 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!Resolve(uid, ref handsComp, false)) return false; - if (!TryGetEmptyHand(uid, out var hand, handsComp)) + if (!TryGetEmptyHand((uid, handsComp), out var hand)) return false; return CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item); @@ -182,13 +165,15 @@ public abstract partial class SharedHandsSystem : EntitySystem /// /// 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, HandsComponent? handsComp = null, ItemComponent? item = null) + public bool CanPickupToHand(EntityUid uid, EntityUid entity, string handId, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null) { if (!Resolve(uid, ref handsComp, false)) return false; - var handContainer = hand.Container; - if (handContainer == null || handContainer.ContainedEntity != null) + if (!ContainerSystem.TryGetContainer(uid, handId, out var handContainer)) + return false; + + if (handContainer.ContainedEntities.FirstOrNull() != null) return false; if (!Resolve(entity, ref item, false)) @@ -231,8 +216,7 @@ public abstract partial class SharedHandsSystem : EntitySystem { if (uid == null || !Resolve(uid.Value, ref handsComp, false) - || !TryGetEmptyHand(uid.Value, out var hand, handsComp) - || !TryPickup(uid.Value, entity, hand, checkActionBlocker, animate, handsComp, item)) + || !TryPickupAnyHand(uid.Value, entity, checkActionBlocker, animateUser, animate, 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. @@ -248,18 +232,20 @@ public abstract partial class SharedHandsSystem : EntitySystem /// /// 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, HandsComponent? hands = null, bool log = true) + public virtual void DoPickup(EntityUid uid, string hand, EntityUid entity, HandsComponent? hands = null, bool log = true) { if (!Resolve(uid, ref hands)) return; - var handContainer = hand.Container; - if (handContainer == null || handContainer.ContainedEntity != null) + if (!ContainerSystem.TryGetContainer(uid, hand, out var handContainer)) + return; + + if (handContainer.ContainedEntities.FirstOrNull() != null) return; if (!ContainerSystem.Insert(entity, handContainer)) { - Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand.Name}."); + Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand}."); return; } @@ -270,7 +256,7 @@ public abstract partial class SharedHandsSystem : EntitySystem Dirty(uid, hands); - if (hand == hands.ActiveHand) - RaiseLocalEvent(entity, new HandSelectedEvent(uid), false); + if (hand == hands.ActiveHandId) + RaiseLocalEvent(entity, new HandSelectedEvent(uid)); } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs index e6f21abf1b..bfb9a41b0b 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs @@ -41,7 +41,7 @@ public abstract partial class SharedHandsSystem { var ev = new HeldRelayedEvent(args); - foreach (var held in EnumerateHeld(entity, entity.Comp)) + foreach (var held in EnumerateHeld(entity.AsNullable())) { RaiseLocalEvent(held, ref ev); } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index 84beabf9ac..4a24b9de02 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Inventory.VirtualItem; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; +using Robust.Shared.Utility; namespace Content.Shared.Hands.EntitySystems; @@ -23,6 +24,8 @@ public abstract partial class SharedHandsSystem [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!; + public event Action, string, HandLocation>? OnPlayerAddHand; + public event Action, string>? OnPlayerRemoveHand; protected event Action?>? OnHandSetActive; public override void Initialize() @@ -33,6 +36,9 @@ public abstract partial class SharedHandsSystem InitializeDrop(); InitializePickup(); InitializeRelay(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMapInit); } public override void Shutdown() @@ -41,71 +47,94 @@ public abstract partial class SharedHandsSystem CommandBinds.Unregister(); } - public virtual void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null) + private void OnInit(Entity ent, ref ComponentInit args) { - 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)); - Dirty(uid, handsComp); + var container = EnsureComp(ent); + foreach (var id in ent.Comp.Hands.Keys) + { + ContainerSystem.EnsureContainer(ent, id, container); + } } - public virtual void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null) + private void OnMapInit(Entity ent, ref MapInitEvent args) { - if (!Resolve(uid, ref handsComp, false)) + if (ent.Comp.ActiveHandId == null) + SetActiveHand(ent.AsNullable(), ent.Comp.SortedHands.FirstOrDefault()); + } + + /// + /// Adds a hand with the given container id and supplied location to the specified entity. + /// + public void AddHand(Entity ent, string handName, HandLocation handLocation) + { + AddHand(ent, handName, new Hand(handLocation)); + } + + /// + /// Adds a hand with the given container id and supplied hand definition to the given entity. + /// + public void AddHand(Entity ent, string handName, Hand hand) + { + if (!Resolve(ent, ref ent.Comp, false)) return; - if (!handsComp.Hands.Remove(handName, out var hand)) + if (ent.Comp.Hands.ContainsKey(handName)) return; - handsComp.SortedHands.Remove(hand.Name); - TryDrop(uid, hand, null, false, true, handsComp); - if (hand.Container != null) - ContainerSystem.ShutdownContainer(hand.Container); + var container = ContainerSystem.EnsureContainer(ent, handName); + container.OccludesLight = false; - if (handsComp.ActiveHand == hand) - TrySetActiveHand(uid, handsComp.SortedHands.FirstOrDefault(), handsComp); + ent.Comp.Hands.Add(handName, hand); + ent.Comp.SortedHands.Add(handName); + Dirty(ent); - RaiseLocalEvent(uid, new HandCountChangedEvent(uid)); - Dirty(uid, handsComp); + OnPlayerAddHand?.Invoke((ent, ent.Comp), handName, hand.Location); + + if (ent.Comp.ActiveHandId == null) + SetActiveHand(ent, handName); + + RaiseLocalEvent(ent, new HandCountChangedEvent(ent)); + } + + /// + /// Removes the specified hand from the specified entity + /// + public virtual void RemoveHand(Entity ent, string handName) + { + if (!Resolve(ent, ref ent.Comp, false)) + return; + + OnPlayerRemoveHand?.Invoke((ent, ent.Comp), handName); + + TryDrop(ent, handName, null, false); + + if (!ent.Comp.Hands.Remove(handName)) + return; + + if (ContainerSystem.TryGetContainer(ent, handName, out var container)) + ContainerSystem.ShutdownContainer(container); + + ent.Comp.SortedHands.Remove(handName); + if (ent.Comp.ActiveHandId == handName) + TrySetActiveHand(ent, ent.Comp.SortedHands.FirstOrDefault()); + + RaiseLocalEvent(ent, new HandCountChangedEvent(ent)); + Dirty(ent); } /// /// Gets rid of all the entity's hands. /// - /// - /// - public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null) + public void RemoveHands(Entity ent) { - if (!Resolve(uid, ref handsComp)) + if (!Resolve(ent, ref ent.Comp, false)) return; - RemoveHands(uid, EnumerateHands(uid), handsComp); - } - - private void RemoveHands(EntityUid uid, IEnumerable hands, HandsComponent handsComp) - { - if (!hands.Any()) - return; - - var hand = hands.First(); - RemoveHand(uid, hand.Name, handsComp); - - // Repeats it for any additional hands. - RemoveHands(uid, hands, handsComp); + var handIds = new List(ent.Comp.Hands.Keys); + foreach (var handId in handIds) + { + RemoveHand(ent, handId); + } } private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) @@ -119,15 +148,15 @@ public abstract partial class SharedHandsSystem /// /// Get any empty hand. Prioritizes the currently active hand. /// - public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHand, HandsComponent? handComp = null) + public bool TryGetEmptyHand(Entity ent, [NotNullWhen(true)] out string? emptyHand) { emptyHand = null; - if (!Resolve(uid, ref handComp, false)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - foreach (var hand in EnumerateHands(uid, handComp)) + foreach (var hand in EnumerateHands(ent)) { - if (hand.IsEmpty) + if (HandIsEmpty(ent, hand)) { emptyHand = hand; return true; @@ -137,28 +166,20 @@ public abstract partial class SharedHandsSystem return false; } - public bool TryGetActiveHand(Entity entity, [NotNullWhen(true)] out Hand? hand) - { - if (!Resolve(entity, ref entity.Comp, false)) - { - hand = null; - return false; - } - - hand = entity.Comp.ActiveHand; - return hand != null; - } - + /// + /// Attempts to retrieve the item held in the entity's active hand. + /// public bool TryGetActiveItem(Entity entity, [NotNullWhen(true)] out EntityUid? item) { - if (!TryGetActiveHand(entity, out var hand)) - { - item = null; + item = null; + if (!Resolve(entity, ref entity.Comp, false)) return false; - } - item = hand.HeldEntity; - return item != null; + if (!TryGetHeldItem(entity, entity.Comp.ActiveHandId, out var held)) + return false; + + item = held; + return true; } /// @@ -174,55 +195,73 @@ public abstract partial class SharedHandsSystem return item.Value; } - public Hand? GetActiveHand(Entity entity) + /// + /// Gets the current active hand's Id for the specified entity + /// + /// + /// + public string? GetActiveHand(Entity entity) { - if (!Resolve(entity, ref entity.Comp)) + if (!Resolve(entity, ref entity.Comp, false)) return null; - return entity.Comp.ActiveHand; + return entity.Comp.ActiveHandId; } + /// + /// Gets the current active hand's held entity for the specified entity + /// + /// + /// public EntityUid? GetActiveItem(Entity entity) { - return GetActiveHand(entity)?.HeldEntity; + if (!Resolve(entity, ref entity.Comp, false)) + return null; + + return GetHeldItem(entity, entity.Comp.ActiveHandId); + } + + public bool ActiveHandIsEmpty(Entity entity) + { + return GetActiveItem(entity) == null; } /// /// Enumerate over hands, starting with the currently active hand. /// - public IEnumerable EnumerateHands(EntityUid uid, HandsComponent? handsComp = null) + public IEnumerable EnumerateHands(Entity ent) { - if (!Resolve(uid, ref handsComp, false)) + if (!Resolve(ent, ref ent.Comp, false)) yield break; - if (handsComp.ActiveHand != null) - yield return handsComp.ActiveHand; + if (ent.Comp.ActiveHandId != null) + yield return ent.Comp.ActiveHandId; - foreach (var name in handsComp.SortedHands) + foreach (var name in ent.Comp.SortedHands) { - if (name != handsComp.ActiveHand?.Name) - yield return handsComp.Hands[name]; + if (name != ent.Comp.ActiveHandId) + yield return name; } } /// /// Enumerate over held items, starting with the item in the currently active hand (if there is one). /// - public IEnumerable EnumerateHeld(EntityUid uid, HandsComponent? handsComp = null) + public IEnumerable EnumerateHeld(Entity ent) { - if (!Resolve(uid, ref handsComp, false)) + if (!Resolve(ent, ref ent.Comp, false)) yield break; - if (handsComp.ActiveHandEntity != null) - yield return handsComp.ActiveHandEntity.Value; + if (TryGetActiveItem(ent, out var activeHeld)) + yield return activeHeld.Value; - foreach (var name in handsComp.SortedHands) + foreach (var name in ent.Comp.SortedHands) { - if (name == handsComp.ActiveHand?.Name) + if (name == ent.Comp.ActiveHandId) continue; - if (handsComp.Hands[name].HeldEntity is { } held) - yield return held; + if (TryGetHeldItem(ent, name, out var held)) + yield return held.Value; } } @@ -231,18 +270,17 @@ public abstract partial class SharedHandsSystem /// /// 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, HandsComponent? handComp = null) + public bool TrySetActiveHand(Entity ent, string? name) { - if (!Resolve(uid, ref handComp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (name == handComp.ActiveHand?.Name) + if (name == ent.Comp.ActiveHandId) return false; - Hand? hand = null; - if (name != null && !handComp.Hands.TryGetValue(name, out hand)) + if (name != null && !ent.Comp.Hands.ContainsKey(name)) return false; - return SetActiveHand(uid, hand, handComp); + return SetActiveHand(ent, name); } /// @@ -250,50 +288,50 @@ public abstract partial class SharedHandsSystem /// /// 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 bool SetActiveHand(EntityUid uid, Hand? hand, HandsComponent? handComp = null) + public bool SetActiveHand(Entity ent, string? handId) { - if (!Resolve(uid, ref handComp)) + if (!Resolve(ent, ref ent.Comp)) return false; - if (hand == handComp.ActiveHand) + if (handId == ent.Comp.ActiveHandId) return false; - if (handComp.ActiveHand?.HeldEntity is { } held) - RaiseLocalEvent(held, new HandDeselectedEvent(uid)); + if (TryGetHeldItem(ent, handId, out var oldHeld)) + RaiseLocalEvent(oldHeld.Value, new HandDeselectedEvent(ent)); - if (hand == null) + if (handId == null) { - handComp.ActiveHand = null; + ent.Comp.ActiveHandId = null; return true; } - handComp.ActiveHand = hand; - OnHandSetActive?.Invoke((uid, handComp)); + ent.Comp.ActiveHandId = handId; + OnHandSetActive?.Invoke((ent, ent.Comp)); - if (hand.HeldEntity != null) - RaiseLocalEvent(hand.HeldEntity.Value, new HandSelectedEvent(uid)); + if (TryGetHeldItem(ent, handId, out var newHeld)) + RaiseLocalEvent(newHeld.Value, new HandSelectedEvent(ent)); - Dirty(uid, handComp); + Dirty(ent); return true; } public bool IsHolding(Entity entity, [NotNullWhen(true)] EntityUid? item) { - return IsHolding(entity, item, out _, entity); + return IsHolding(entity, item, out _); } - public bool IsHolding(EntityUid uid, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null) + public bool IsHolding(Entity ent, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out string? inHand) { inHand = null; if (entity == null) return false; - if (!Resolve(uid, ref handsComp, false)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - foreach (var hand in handsComp.Hands.Values) + foreach (var hand in ent.Comp.Hands.Keys) { - if (hand.HeldEntity == entity) + if (GetHeldItem(ent, hand) == entity) { inHand = hand; return true; @@ -303,23 +341,89 @@ public abstract partial class SharedHandsSystem return false; } - public bool TryGetHand(EntityUid handsUid, string handId, [NotNullWhen(true)] out Hand? hand, - HandsComponent? hands = null) + /// + /// Attempts to retrieve the associated hand struct corresponding to a hand ID on a given entity. + /// + public bool TryGetHand(Entity ent, [NotNullWhen(true)] string? handId, [NotNullWhen(true)] out Hand? hand) { hand = null; - if (!Resolve(handsUid, ref hands)) + if (handId == null) return false; - return hands.Hands.TryGetValue(handId, out hand); + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + if (!ent.Comp.Hands.TryGetValue(handId, out var handsHand)) + return false; + + hand = handsHand; + return true; + } + + /// + /// Gets the item currently held in the entity's specified hand. Returns null if no hands are present or there is no item. + /// + public EntityUid? GetHeldItem(Entity ent, string? handId) + { + TryGetHeldItem(ent, handId, out var held); + return held; + } + + /// + /// Gets the item currently held in the entity's specified hand. Returns false if no hands are present or there is no item. + /// + public bool TryGetHeldItem(Entity ent, string? handId, [NotNullWhen(true)] out EntityUid? held) + { + held = null; + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + // Sanity check to make sure this is actually a hand. + if (handId == null || !ent.Comp.Hands.ContainsKey(handId)) + return false; + + if (!ContainerSystem.TryGetContainer(ent, handId, out var container)) + return false; + + held = container.ContainedEntities.FirstOrNull(); + return held != null; + } + + public bool HandIsEmpty(Entity ent, string handId) + { + return GetHeldItem(ent, handId) == null; + } + + public int GetHandCount(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return 0; + + return ent.Comp.Hands.Count; + } + + public int CountFreeHands(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return 0; + + var free = 0; + foreach (var name in ent.Comp.Hands.Keys) + { + if (HandIsEmpty(ent, name)) + free++; + } + + return free; } public int CountFreeableHands(Entity hands) { var freeable = 0; - foreach (var hand in hands.Comp.Hands.Values) + foreach (var name in hands.Comp.Hands.Keys) { - if (hand.IsEmpty || CanDropHeld(hands, hand)) + if (HandIsEmpty(hands.AsNullable(), name) || CanDropHeld(hands, name)) freeable++; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 2c75cef132..b91f56a836 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Database; using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Events; @@ -58,6 +59,7 @@ namespace Content.Shared.Interaction [Dependency] private readonly ISharedChatManager _chat = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PullingSystem _pullSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; @@ -343,7 +345,7 @@ namespace Content.Shared.Interaction public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) { // Always allow attack in these cases - if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) + if (target == null || !_handsQuery.TryComp(user, out var hands) || _hands.GetActiveItem((user, hands)) is not null) return false; // Only eat input if: diff --git a/Content.Shared/Interaction/SmartEquipSystem.cs b/Content.Shared/Interaction/SmartEquipSystem.cs index 746bc994ee..797dc367b4 100644 --- a/Content.Shared/Interaction/SmartEquipSystem.cs +++ b/Content.Shared/Interaction/SmartEquipSystem.cs @@ -64,10 +64,10 @@ public sealed class SmartEquipSystem : EntitySystem return; // early out if we don't have any hands or a valid inventory slot - if (!TryComp(uid, out var hands) || hands.ActiveHand == null) + if (!TryComp(uid, out var hands) || hands.ActiveHandId == null) return; - var handItem = hands.ActiveHand.HeldEntity; + var handItem = _hands.GetActiveItem((uid, hands)); // can the user interact, and is the item interactable? e.g. virtual items if (!_actionBlocker.CanInteract(uid, handItem)) @@ -80,7 +80,7 @@ public sealed class SmartEquipSystem : EntitySystem } // early out if we have an item and cant drop it at all - if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand)) + if (hands.ActiveHandId != null && !_hands.CanDropHeld(uid, hands.ActiveHandId)) { _popup.PopupClient(Loc.GetString("smart-equip-cant-drop"), uid, uid); return; @@ -121,7 +121,7 @@ public sealed class SmartEquipSystem : EntitySystem return; } - _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands); + _hands.TryDrop((uid, hands), hands.ActiveHandId!); _inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true, checkDoafter:true); return; } @@ -149,7 +149,7 @@ public sealed class SmartEquipSystem : EntitySystem return; } - _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands); + _hands.TryDrop((uid, hands), hands.ActiveHandId!); _storage.Insert(slotItem, handItem.Value, out var stacked, out _); // if the hand item stacked with the things in inventory, but there's no more space left for the rest diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index f2fb3987b7..de24a64a1c 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -83,7 +83,7 @@ public abstract partial class InventorySystem if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp(actor, out var hands)) return; - var held = hands.ActiveHandEntity; + var held = _handsSystem.GetActiveItem((actor, hands)); TryGetSlotEntity(actor, ev.Slot, out var itemUid, inventory); // attempt to perform some interaction @@ -115,7 +115,7 @@ public abstract partial class InventorySystem return; } - if (!_handsSystem.CanDropHeld(actor, hands.ActiveHand!, checkActionBlocker: false)) + if (!_handsSystem.CanDropHeld(actor, hands.ActiveHandId!, checkActionBlocker: false)) return; RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor)); diff --git a/Content.Shared/Inventory/InventorySystem.Helpers.cs b/Content.Shared/Inventory/InventorySystem.Helpers.cs index 746342e2f1..44dede02c7 100644 --- a/Content.Shared/Inventory/InventorySystem.Helpers.cs +++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs @@ -16,12 +16,9 @@ public partial class InventorySystem { if (Resolve(user.Owner, ref user.Comp1, false)) { - foreach (var hand in user.Comp1.Hands.Values) + foreach (var held in _handsSystem.EnumerateHeld(user)) { - if (hand.HeldEntity == null) - continue; - - yield return hand.HeldEntity.Value; + yield return held; } } diff --git a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs index 779ad18789..d76a4a3415 100644 --- a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs +++ b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Hands; -using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; @@ -8,7 +7,6 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Containers; -using Robust.Shared.Network; using Robust.Shared.Prototypes; namespace Content.Shared.Inventory.VirtualItem; @@ -88,9 +86,9 @@ public abstract class SharedVirtualItemSystem : EntitySystem // if the user is holding the real item the virtual item points to, // we allow them to use it in the interaction - foreach (var hand in _handsSystem.EnumerateHands(args.User)) + foreach (var held in _handsSystem.EnumerateHeld(args.User)) { - if (hand.HeldEntity == ent.Comp.BlockingEntity) + if (held == ent.Comp.BlockingEntity) { args.Used = ent.Comp.BlockingEntity; return; @@ -112,7 +110,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem } /// - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, Hand? empty = null) + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, string? empty = null) { virtualItem = null; if (empty == null && !_handsSystem.TryGetEmptyHand(user, out empty)) @@ -122,7 +120,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem foreach (var hand in _handsSystem.EnumerateHands(user)) { - if (hand.HeldEntity is not { } held) + if (!_handsSystem.TryGetHeldItem(user, hand, out var held)) continue; if (held == blockingEnt) @@ -155,11 +153,11 @@ public abstract class SharedVirtualItemSystem : EntitySystem /// public void DeleteInHandsMatching(EntityUid user, EntityUid matching) { - foreach (var hand in _handsSystem.EnumerateHands(user)) + foreach (var held in _handsSystem.EnumerateHeld(user)) { - if (TryComp(hand.HeldEntity, out VirtualItemComponent? virt) && virt.BlockingEntity == matching) + if (TryComp(held, out VirtualItemComponent? virt) && virt.BlockingEntity == matching) { - DeleteVirtualItem((hand.HeldEntity.Value, virt), user); + DeleteVirtualItem((held, virt), user); } } } diff --git a/Content.Shared/Item/MultiHandedItemSystem.cs b/Content.Shared/Item/MultiHandedItemSystem.cs index da9d895dd2..f17ccdc922 100644 --- a/Content.Shared/Item/MultiHandedItemSystem.cs +++ b/Content.Shared/Item/MultiHandedItemSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.Hands; -using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory.VirtualItem; using Content.Shared.Popups; @@ -38,7 +37,7 @@ public sealed class MultiHandedItemSystem : EntitySystem private void OnAttemptPickup(Entity ent, ref GettingPickedUpAttemptEvent args) { - if (TryComp(args.User, out var hands) && hands.CountFreeHands() >= ent.Comp.HandsNeeded) + if (_hands.CountFreeHands(ent.Owner) >= ent.Comp.HandsNeeded) return; args.Cancel(); diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index c277bb7e87..9319755501 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -110,7 +110,7 @@ public abstract class SharedItemSystem : EntitySystem if (args.Handled) return; - args.Handled = _handsSystem.TryPickup(args.User, uid, animateUser: false); + args.Handled = _handsSystem.TryPickup(args.User, uid, null, animateUser: false); } private void AddPickupVerb(EntityUid uid, ItemComponent component, GetVerbsEvent args) diff --git a/Content.Shared/Magic/SharedMagicSystem.cs b/Content.Shared/Magic/SharedMagicSystem.cs index 09b289c2be..a860f1dcfb 100644 --- a/Content.Shared/Magic/SharedMagicSystem.cs +++ b/Content.Shared/Magic/SharedMagicSystem.cs @@ -434,7 +434,7 @@ public abstract class SharedMagicSystem : EntitySystem return; EntityUid? wand = null; - foreach (var item in _hands.EnumerateHeld(ev.Performer, handsComp)) + foreach (var item in _hands.EnumerateHeld((ev.Performer, handsComp))) { if (!_tag.HasTag(item, ev.WandTag)) continue; diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index bc505ee989..e1c07df3c3 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -109,16 +109,12 @@ public sealed class PullingSystem : EntitySystem // Try find hand that is doing this pull. // and clear it. - foreach (var hand in component.Hands.Values) + foreach (var held in _handsSystem.EnumerateHeld((uid, component))) { - if (hand.HeldEntity == null - || !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem) - || virtualItem.BlockingEntity != args.PulledUid) - { + if (!TryComp(held, out VirtualItemComponent? virtualItem) || virtualItem.BlockingEntity != args.PulledUid) continue; - } - _handsSystem.TryDrop(args.PullerUid, hand, handsComp: component); + _handsSystem.TryDrop((args.PullerUid, component), held); break; } } diff --git a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs index 8b892190b7..1f3cc3881d 100644 --- a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs +++ b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Clothing.Components; using Content.Shared.CombatMode; using Content.Shared.Examine; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Item.ItemToggle; @@ -19,6 +20,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedCombatModeSystem _combatMode = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly ItemToggleSystem _toggle = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; @@ -128,8 +130,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem target = args.Target; return _timing.IsFirstTimePredicted && !_combatMode.IsInCombatMode(uid) - && TryComp(uid, out var hands) - && hands.ActiveHandEntity == null + && _hands.GetActiveItem(uid) == null && _interaction.InRangeUnobstructed(uid, target); } } diff --git a/Content.Shared/Nutrition/EntitySystems/FoodSystem.cs b/Content.Shared/Nutrition/EntitySystems/FoodSystem.cs index a4122168e4..6099939725 100644 --- a/Content.Shared/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/FoodSystem.cs @@ -459,7 +459,7 @@ public sealed class FoodSystem : EntitySystem var usedTypes = UtensilType.None; - foreach (var item in _hands.EnumerateHeld(user, hands)) + foreach (var item in _hands.EnumerateHeld((user, hands))) { // Is utensil? if (!TryComp(item, out var utensil)) diff --git a/Content.Shared/RCD/Systems/RCDSystem.cs b/Content.Shared/RCD/Systems/RCDSystem.cs index 6d85b171e0..68056ef267 100644 --- a/Content.Shared/RCD/Systems/RCDSystem.cs +++ b/Content.Shared/RCD/Systems/RCDSystem.cs @@ -5,7 +5,7 @@ using Content.Shared.Construction; using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.Examine; -using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Maps; using Content.Shared.Physics; @@ -35,6 +35,7 @@ public sealed class RCDSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TurfSystem _turf = default!; @@ -296,11 +297,10 @@ public sealed class RCDSystem : EntitySystem var uid = GetEntity(ev.NetEntity); // Determine if player that send the message is carrying the specified RCD in their active hand - if (session.SenderSession.AttachedEntity == null) + if (session.SenderSession.AttachedEntity is not { } player) return; - if (!TryComp(session.SenderSession.AttachedEntity, out var hands) || - uid != hands.ActiveHand?.HeldEntity) + if (_hands.GetActiveItem(player) != uid) return; if (!TryComp(uid, out var rcd)) diff --git a/Content.Shared/RetractableItemAction/RetractableItemActionSystem.cs b/Content.Shared/RetractableItemAction/RetractableItemActionSystem.cs index c24da14c68..1820541746 100644 --- a/Content.Shared/RetractableItemAction/RetractableItemActionSystem.cs +++ b/Content.Shared/RetractableItemAction/RetractableItemActionSystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Actions; using Content.Shared.Cuffs; using Content.Shared.Hands; -using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Components; using Content.Shared.Inventory; @@ -42,7 +41,7 @@ public sealed class RetractableItemActionSystem : EntitySystem private void OnRetractableItemAction(Entity ent, ref OnRetractableItemActionEvent args) { - if (_hands.GetActiveHand(args.Performer) is not { } userHand) + if (_hands.GetActiveHand(args.Performer) is not { } activeHand) return; if (_actions.GetAction(ent.Owner) is not { } action) @@ -55,7 +54,9 @@ public sealed class RetractableItemActionSystem : EntitySystem return; // Don't allow to summon an item if holding an unremoveable item unless that item is summoned by the action. - if (userHand.HeldEntity != null && !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid) && !_hands.CanDropHeld(args.Performer, userHand, false)) + if (_hands.GetActiveItem(ent.Owner) != null + && !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid) + && !_hands.CanDropHeld(args.Performer, activeHand, false)) { _popups.PopupClient(Loc.GetString("retractable-item-hand-cannot-drop"), args.Performer, args.Performer); return; @@ -67,7 +68,7 @@ public sealed class RetractableItemActionSystem : EntitySystem } else { - SummonRetractableItem(args.Performer, ent.Comp.ActionItemUid.Value, userHand, ent.Owner); + SummonRetractableItem(args.Performer, ent.Comp.ActionItemUid.Value, activeHand, ent.Owner); } args.Handled = true; @@ -93,7 +94,7 @@ public sealed class RetractableItemActionSystem : EntitySystem if (action.Comp.AttachedEntity == null) return; - if (_hands.GetActiveHand(action.Comp.AttachedEntity.Value) is not { } userHand) + if (_hands.GetActiveHand(action.Comp.AttachedEntity.Value) is not { }) return; RetractRetractableItem(action.Comp.AttachedEntity.Value, ent, action.Owner); @@ -128,7 +129,7 @@ public sealed class RetractableItemActionSystem : EntitySystem _audio.PlayPredicted(action.Comp.RetractSounds, holder, holder); } - private void SummonRetractableItem(EntityUid holder, EntityUid item, Hand hand, Entity action) + private void SummonRetractableItem(EntityUid holder, EntityUid item, string hand, Entity action) { if (!Resolve(action, ref action.Comp, false)) return; diff --git a/Content.Shared/Stacks/SharedStackSystem.cs b/Content.Shared/Stacks/SharedStackSystem.cs index c444b8936d..cd2f38d47f 100644 --- a/Content.Shared/Stacks/SharedStackSystem.cs +++ b/Content.Shared/Stacks/SharedStackSystem.cs @@ -146,7 +146,7 @@ namespace Content.Shared.Stacks } // This is shit code until hands get fixed and give an easy way to enumerate over items, starting with the currently active item. - foreach (var held in Hands.EnumerateHeld(user, hands)) + foreach (var held in Hands.EnumerateHeld((user, hands))) { TryMergeStacks(item, held, out _, donorStack: itemStack); diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 389f696db2..a0a2257402 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -141,7 +141,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem { var inhandEntity = EntityManager.SpawnEntity(prototype, coords); - if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) + if (_handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand)) { _handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent); } diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 8143cb9cdd..adba19e047 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -689,7 +689,7 @@ public abstract class SharedStorageSystem : EntitySystem return; // If the user's active hand is empty, try pick up the item. - if (player.Comp.ActiveHandEntity == null) + if (!_sharedHandsSystem.TryGetActiveItem(player.AsNullable(), out var activeItem)) { _adminLog.Add( LogType.Storage, @@ -709,11 +709,11 @@ public abstract class SharedStorageSystem : EntitySystem _adminLog.Add( LogType.Storage, LogImpact.Low, - $"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(player.Comp.ActiveHandEntity):used}"); + $"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(activeItem):used}"); // Else, interact using the held item if (_interactionSystem.InteractUsing(player, - player.Comp.ActiveHandEntity.Value, + activeItem.Value, item, Transform(item).Coordinates, checkCanInteract: false)) @@ -1208,10 +1208,10 @@ public abstract class SharedStorageSystem : EntitySystem { if (!Resolve(ent.Owner, ref ent.Comp) || !Resolve(player.Owner, ref player.Comp) - || player.Comp.ActiveHandEntity == null) + || !_sharedHandsSystem.TryGetActiveItem(player, out var activeItem)) return false; - var toInsert = player.Comp.ActiveHandEntity; + var toInsert = activeItem; if (!CanInsert(ent, toInsert.Value, out var reason, ent.Comp)) { @@ -1219,7 +1219,7 @@ public abstract class SharedStorageSystem : EntitySystem return false; } - if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, player.Comp)) + if (!_sharedHandsSystem.CanDrop(player, toInsert.Value)) { _popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), ent, player); return false; @@ -1933,7 +1933,7 @@ public abstract class SharedStorageSystem : EntitySystem if (held) { - if (!_sharedHandsSystem.IsHolding(player, itemUid, out _)) + if (!_sharedHandsSystem.IsHolding(player.AsNullable(), itemUid, out _)) return false; } else diff --git a/Content.Shared/Strip/SharedStrippableSystem.cs b/Content.Shared/Strip/SharedStrippableSystem.cs index 2b971ae7bb..49be180503 100644 --- a/Content.Shared/Strip/SharedStrippableSystem.cs +++ b/Content.Shared/Strip/SharedStrippableSystem.cs @@ -104,8 +104,8 @@ public abstract class SharedStrippableSystem : EntitySystem var hasEnt = _inventorySystem.TryGetSlotEntity(strippable, args.Slot, out var held, inventory); - if (userHands.ActiveHandEntity != null && !hasEnt) - StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot); + if (_handsSystem.GetActiveItem((user, userHands)) is { } activeItem && !hasEnt) + StartStripInsertInventory((user, userHands), strippable.Owner, activeItem, args.Slot); else if (hasEnt) StartStripRemoveInventory(user, strippable.Owner, held!.Value, args.Slot); } @@ -124,11 +124,10 @@ public abstract class SharedStrippableSystem : EntitySystem if (!target.Comp.CanBeStripped) return; - if (!_handsSystem.TryGetHand(target.Owner, handId, out var handSlot)) - return; + var heldEntity = _handsSystem.GetHeldItem(target.Owner, handId); // Is the target a handcuff? - if (TryComp(handSlot.HeldEntity, out var virtualItem) && + if (TryComp(heldEntity, out var virtualItem) && TryComp(target.Owner, out var cuffable) && _cuffableSystem.GetAllCuffs(cuffable).Contains(virtualItem.BlockingEntity)) { @@ -136,10 +135,10 @@ public abstract class SharedStrippableSystem : EntitySystem return; } - if (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null) - StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable); - else if (handSlot.HeldEntity != null) - StartStripRemoveHand(user, target, handSlot.HeldEntity.Value, handId, targetStrippable); + if (_handsSystem.GetActiveItem(user.AsNullable()) is { } activeItem && heldEntity == null) + StartStripInsertHand(user, target, activeItem, handId, targetStrippable); + else if (heldEntity != null) + StartStripRemoveHand(user, target, heldEntity.Value, handId, targetStrippable); } /// @@ -154,16 +153,10 @@ public abstract class SharedStrippableSystem : EntitySystem if (!Resolve(user, ref user.Comp)) return false; - if (user.Comp.ActiveHand == null) + if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held) return false; - if (user.Comp.ActiveHandEntity == null) - return false; - - if (user.Comp.ActiveHandEntity != held) - return false; - - if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand)) + if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!)) { _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop")); return false; @@ -210,10 +203,14 @@ public abstract class SharedStrippableSystem : EntitySystem var (time, stealth) = GetStripTimeModifiers(user, target, held, slotDef.StripTime); if (!stealth) + { _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", Identity.Entity(user, EntityManager)), - ("item", user.Comp.ActiveHandEntity!.Value)), - target, target, PopupType.Large); + ("item", _handsSystem.GetActiveItem((user, user.Comp))!.Value)), + target, + target, + PopupType.Large); + } var prefix = stealth ? "stealthily " : ""; _adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot"); @@ -246,7 +243,7 @@ public abstract class SharedStrippableSystem : EntitySystem if (!CanStripInsertInventory(user, target, held, slot)) return; - if (!_handsSystem.TryDrop(user, handsComp: user.Comp)) + if (!_handsSystem.TryDrop(user)) return; _inventorySystem.TryEquip(user, target, held, slot, triggerHandContact: true); @@ -305,10 +302,15 @@ public abstract class SharedStrippableSystem : EntitySystem if (IsStripHidden(slotDef, user)) _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target, target, PopupType.Large); else + { _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", item)), - target, target, PopupType.Large); + target, + target, + PopupType.Large); + + } } var prefix = stealth ? "stealthily " : ""; @@ -368,23 +370,16 @@ public abstract class SharedStrippableSystem : EntitySystem if (!target.Comp.CanBeStripped) return false; - if (user.Comp.ActiveHand == null) + if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held) return false; - if (user.Comp.ActiveHandEntity == null) - return false; - - if (user.Comp.ActiveHandEntity != held) - return false; - - if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand)) + if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!)) { _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop")); return false; } - if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp) || - !_handsSystem.CanPickupToHand(target, user.Comp.ActiveHandEntity.Value, handSlot, checkActionBlocker: false, target.Comp)) + if (!_handsSystem.CanPickupToHand(target, activeItem.Value, handName, checkActionBlocker: false, target.Comp)) { _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-put-message", ("owner", Identity.Entity(target, EntityManager)))); return false; @@ -414,10 +409,15 @@ public abstract class SharedStrippableSystem : EntitySystem var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay); if (!stealth) + { _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand", ("user", Identity.Entity(user, EntityManager)), - ("item", user.Comp.ActiveHandEntity!.Value)), - target, target, PopupType.Large); + ("item", _handsSystem.GetActiveItem(user)!.Value)), + target, + target, + PopupType.Large); + + } var prefix = stealth ? "stealthily " : ""; _adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands"); @@ -452,7 +452,7 @@ public abstract class SharedStrippableSystem : EntitySystem if (!CanStripInsertHand(user, target, held, handName)) return; - _handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: user.Comp); + _handsSystem.TryDrop(user, checkActionBlocker: false); _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: !stealth, handsComp: target.Comp); _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands"); @@ -474,22 +474,22 @@ public abstract class SharedStrippableSystem : EntitySystem if (!target.Comp.CanBeStripped) return false; - if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp)) + if (!_handsSystem.TryGetHand(target, handName, out _)) { _popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Entity(target, EntityManager)))); return false; } - if (HasComp(handSlot.HeldEntity)) + if (!_handsSystem.TryGetHeldItem(target, handName, out var heldEntity)) return false; - if (handSlot.HeldEntity == null) + if (HasComp(heldEntity)) return false; - if (handSlot.HeldEntity != item) + if (heldEntity != item) return false; - if (!_handsSystem.CanDropHeld(target, handSlot, false)) + if (!_handsSystem.CanDropHeld(target, handName, false)) { _popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message", ("owner", Identity.Entity(target, EntityManager)))); return false; @@ -519,10 +519,13 @@ public abstract class SharedStrippableSystem : EntitySystem var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay); if (!stealth) + { _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", item)), - target, target); + target, + target); + } var prefix = stealth ? "stealthily " : ""; _adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands"); @@ -560,7 +563,7 @@ public abstract class SharedStrippableSystem : EntitySystem if (!CanStripRemoveHand(user, target, item, handName)) return; - _handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: target.Comp); + _handsSystem.TryDrop(target, item, checkActionBlocker: false); _handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp); _adminLogger.Add(LogType.Stripping, LogImpact.High, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands"); @@ -580,13 +583,17 @@ public abstract class SharedStrippableSystem : EntitySystem { if ( ev.Event.InsertOrRemove && !CanStripInsertInventory((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) || !ev.Event.InsertOrRemove && !CanStripRemoveInventory(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName)) - ev.Cancel(); + { + ev.Cancel(); + } } else { if ( ev.Event.InsertOrRemove && !CanStripInsertHand((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) || !ev.Event.InsertOrRemove && !CanStripRemoveHand(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName)) - ev.Cancel(); + { + ev.Cancel(); + } } } diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index 7eb195c0b1..a3f9a033df 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -108,10 +108,10 @@ public sealed partial class ActivatableUISystem : EntitySystem if (component.InHandsOnly) { - if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands)) + if (!_hands.IsHolding((args.User, args.Hands), uid, out var hand )) return false; - if (component.RequireActiveHand && args.Hands.ActiveHand != hand) + if (component.RequireActiveHand && args.Hands.ActiveHandId != hand) return false; } } @@ -202,10 +202,10 @@ public sealed partial class ActivatableUISystem : EntitySystem if (!TryComp(user, out HandsComponent? hands)) return false; - if (!_hands.IsHolding(user, uiEntity, out var hand, hands)) + if (!_hands.IsHolding((user, hands), uiEntity, out var hand)) return false; - if (aui.RequireActiveHand && hands.ActiveHand != hand) + if (aui.RequireActiveHand && hands.ActiveHandId != hand) return false; } diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index cf9120f5f3..953abc8107 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Hands; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Inventory; @@ -51,6 +52,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; [Dependency] protected readonly ActionBlockerSystem Blocker = default!; [Dependency] protected readonly DamageableSystem Damageable = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly MeleeSoundSystem _meleeSound = default!; [Dependency] protected readonly MobStateSystem MobState = default!; @@ -288,15 +290,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem } // Use inhands entity if we got one. - if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) && - hands.ActiveHandEntity is { } held) + if (_hands.TryGetActiveItem(entity, out var held)) { // Make sure the entity is a weapon AND it doesn't need // to be equipped to be used (E.g boxing gloves). if (EntityManager.TryGetComponent(held, out melee) && !melee.MustBeEquippedToUse) { - weaponUid = held; + weaponUid = held.Value; return true; } @@ -858,9 +859,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem EntityUid? inTargetHand = null; - if (targetHandsComponent?.ActiveHand is { IsEmpty: false }) + if (_hands.TryGetActiveItem(target.Value, out var activeHeldEntity)) { - inTargetHand = targetHandsComponent.ActiveHand.HeldEntity!.Value; + inTargetHand = activeHeldEntity.Value; } var attemptEvent = new DisarmAttemptEvent(target.Value, user, inTargetHand); diff --git a/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs b/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs index e790973538..d27efa4d76 100644 --- a/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs +++ b/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs @@ -1,7 +1,7 @@ using System.Numerics; using Content.Shared.CombatMode; using Content.Shared.Hands; -using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Movement.Events; using Content.Shared.Physics; @@ -25,6 +25,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedJointSystem _joints = default!; [Dependency] private readonly SharedGunSystem _gun = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; @@ -80,9 +81,11 @@ public abstract class SharedGrapplingGunSystem : EntitySystem private void OnGrapplingReel(RequestGrapplingReelMessage msg, EntitySessionEventArgs args) { - var player = args.SenderSession.AttachedEntity; - if (!TryComp(player, out var hands) || - !TryComp(hands.ActiveHandEntity, out var grappling)) + if (args.SenderSession.AttachedEntity is not { } player) + return; + + if (!_hands.TryGetActiveItem(player, out var activeItem) || + !TryComp(activeItem, out var grappling)) { return; } @@ -94,7 +97,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem return; } - SetReeling(hands.ActiveHandEntity.Value, grappling, msg.Reeling, player.Value); + SetReeling(activeItem.Value, grappling, msg.Reeling, player); } private void OnWeightlessMove(ref CanWeightlessMoveEvent ev) diff --git a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs index f6702569bf..0f6baf06f2 100644 --- a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs +++ b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.ActionBlocker; using Content.Shared.Buckle.Components; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Events; @@ -23,6 +24,7 @@ public abstract partial class SharedTetherGunSystem : EntitySystem { [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly MobStateSystem _mob = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -139,14 +141,14 @@ public abstract partial class SharedTetherGunSystem : EntitySystem gunUid = null; gun = null; - if (!TryComp(user, out var hands) || - !TryComp(hands.ActiveHandEntity, out gun) || + if (!_hands.TryGetActiveItem(user, out var activeItem) || + !TryComp(activeItem, out gun) || _container.IsEntityInContainer(user)) { return false; } - gunUid = hands.ActiveHandEntity.Value; + gunUid = activeItem.Value; return true; } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index bebfe6f922..b6dee7fc1b 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -10,7 +10,7 @@ using Content.Shared.Damage; using Content.Shared.Examine; using Content.Shared.Gravity; using Content.Shared.Hands; -using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Popups; using Content.Shared.Projectiles; using Content.Shared.Tag; @@ -49,6 +49,7 @@ public abstract partial class SharedGunSystem : EntitySystem [Dependency] protected readonly ISharedAdminLogManager Logs = default!; [Dependency] protected readonly DamageableSystem Damageable = default!; [Dependency] protected readonly ExamineSystemShared Examine = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; [Dependency] private readonly RechargeBasicEntityAmmoSystem _recharge = default!; [Dependency] protected readonly SharedActionsSystem Actions = default!; @@ -173,8 +174,7 @@ public abstract partial class SharedGunSystem : EntitySystem gunEntity = default; gunComp = null; - if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) && - hands.ActiveHandEntity is { } held && + if (_hands.GetActiveItem(entity) is { } held && TryComp(held, out GunComponent? gun)) { gunEntity = held; diff --git a/Content.Shared/Wieldable/SharedWieldableSystem.cs b/Content.Shared/Wieldable/SharedWieldableSystem.cs index 83ed58388a..c3375c3fe1 100644 --- a/Content.Shared/Wieldable/SharedWieldableSystem.cs +++ b/Content.Shared/Wieldable/SharedWieldableSystem.cs @@ -112,7 +112,7 @@ public abstract class SharedWieldableSystem : EntitySystem private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args) { - if (_hands.EnumerateHands(args.User).Count() > 2) + if (_hands.GetHandCount(uid) > 2) return; TryUnwield(uid, component, args.User); @@ -168,7 +168,7 @@ public abstract class SharedWieldableSystem : EntitySystem if (args.Hands == null || !args.CanAccess || !args.CanInteract) return; - if (!_hands.IsHolding(args.User, uid, out _, args.Hands)) + if (!_hands.IsHolding((args.User, args.Hands), uid, out _)) return; // TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable @@ -252,7 +252,7 @@ public abstract class SharedWieldableSystem : EntitySystem } // Is it.. actually in one of their hands? - if (!_hands.IsHolding(user, uid, out _, hands)) + if (!_hands.IsHolding((user, hands), uid, out _)) { if (!quiet) _popup.PopupClient(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user); @@ -373,7 +373,7 @@ public abstract class SharedWieldableSystem : EntitySystem /// If this is true we will bypass UnwieldAttemptEvent. public void UnwieldAll(Entity wielder, bool force = false) { - foreach (var held in _hands.EnumerateHeld(wielder.Owner, wielder.Comp)) + foreach (var held in _hands.EnumerateHeld(wielder)) { if (TryComp(held, out var wieldable)) TryUnwield(held, wieldable, wielder, force); diff --git a/Resources/Prototypes/Body/Prototypes/a_ghost.yml b/Resources/Prototypes/Body/Prototypes/a_ghost.yml deleted file mode 100644 index 7d358a5762..0000000000 --- a/Resources/Prototypes/Body/Prototypes/a_ghost.yml +++ /dev/null @@ -1,22 +0,0 @@ -- type: body - id: Aghost - name: "aghost" - root: torso - slots: - torso: - part: TorsoHuman - connections: - - right_arm - - left_arm - right_arm: - part: RightArmHuman - connections: - - right_hand - left_arm: - part: LeftArmHuman - connections: - - left_hand - right_hand: - part: RightHandHuman - left_hand: - part: LeftHandHuman diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 2304c0d386..0a5577c6d3 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -22,6 +22,14 @@ canInteract: true - type: GhostHearing - type: Hands + hands: + hand_right: + location: Right + hand_left: + location: Left + sortedHands: + - hand_right + - hand_left - type: ComplexInteraction - type: Puller needsHands: false @@ -29,8 +37,6 @@ - type: Physics ignorePaused: true bodyType: Kinematic - - type: Body - prototype: Aghost - type: Access groups: - AllAccess