using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; namespace Content.Shared.Hands.EntitySystems; public abstract partial class SharedHandsSystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedItemSystem _items = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedHandVirtualItemSystem _virtualSystem = default!; protected event Action?>? OnHandSetActive; public override void Initialize() { base.Initialize(); InitializeInteractions(); InitializeDrop(); InitializePickup(); InitializeVirtual(); InitializeRelay(); } public override void Shutdown() { base.Shutdown(); CommandBinds.Unregister(); } public virtual void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp, false)) return; if (handsComp.Hands.ContainsKey(handName)) return; var container = ContainerSystem.EnsureContainer(uid, handName); container.OccludesLight = false; var newHand = new Hand(handName, handLocation, container); handsComp.Hands.Add(handName, newHand); handsComp.SortedHands.Add(handName); if (handsComp.ActiveHand == null) SetActiveHand(uid, newHand, handsComp); RaiseLocalEvent(uid, new HandCountChangedEvent(uid)); Dirty(uid, handsComp); } public virtual void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp, false)) return; if (!handsComp.Hands.Remove(handName, out var hand)) return; handsComp.SortedHands.Remove(hand.Name); TryDrop(uid, hand, null, false, true, handsComp); if (hand.Container != null) ContainerSystem.ShutdownContainer(hand.Container); if (handsComp.ActiveHand == hand) TrySetActiveHand(uid, handsComp.SortedHands.FirstOrDefault(), handsComp); RaiseLocalEvent(uid, new HandCountChangedEvent(uid)); Dirty(uid, handsComp); } /// /// Gets rid of all the entity's hands. /// /// /// public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp)) 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); } private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) { if (eventArgs.SenderSession.AttachedEntity == null) return; TrySetActiveHand(eventArgs.SenderSession.AttachedEntity.Value, msg.HandName); } /// /// Get any empty hand. Prioritizes the currently active hand. /// public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHand, HandsComponent? handComp = null) { emptyHand = null; if (!Resolve(uid, ref handComp, false)) return false; foreach (var hand in EnumerateHands(uid, handComp)) { if (hand.IsEmpty) { emptyHand = hand; return true; } } return false; } /// /// Enumerate over hands, starting with the currently active hand. /// public IEnumerable EnumerateHands(EntityUid uid, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp, false)) yield break; if (handsComp.ActiveHand != null) yield return handsComp.ActiveHand; foreach (var name in handsComp.SortedHands) { if (name != handsComp.ActiveHand?.Name) yield return handsComp.Hands[name]; } } /// /// Enumerate over held items, starting with the item in the currently active hand (if there is one). /// public IEnumerable EnumerateHeld(EntityUid uid, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp, false)) yield break; if (handsComp.ActiveHandEntity != null) yield return handsComp.ActiveHandEntity.Value; foreach (var name in handsComp.SortedHands) { if (name == handsComp.ActiveHand?.Name) continue; if (handsComp.Hands[name].HeldEntity is { } held) yield return held; } } /// /// Set the currently active hand and raise hand (de)selection events directed at the held entities. /// /// True if the active hand was set to a NEW value. Setting it to the same value returns false and does /// not trigger interactions. public virtual bool TrySetActiveHand(EntityUid uid, string? name, HandsComponent? handComp = null) { if (!Resolve(uid, ref handComp)) return false; if (name == handComp.ActiveHand?.Name) return false; Hand? hand = null; if (name != null && !handComp.Hands.TryGetValue(name, out hand)) return false; return SetActiveHand(uid, hand, handComp); } /// /// Set the currently active hand and raise hand (de)selection events directed at the held entities. /// /// True if the active hand was set to a NEW value. Setting it to the same value returns false and does /// not trigger interactions. public bool SetActiveHand(EntityUid uid, Hand? hand, HandsComponent? handComp = null) { if (!Resolve(uid, ref handComp)) return false; if (hand == handComp.ActiveHand) return false; if (handComp.ActiveHand?.HeldEntity is { } held) RaiseLocalEvent(held, new HandDeselectedEvent(uid)); if (hand == null) { handComp.ActiveHand = null; return true; } handComp.ActiveHand = hand; OnHandSetActive?.Invoke((uid, handComp)); if (hand.HeldEntity != null) RaiseLocalEvent(hand.HeldEntity.Value, new HandSelectedEvent(uid)); Dirty(uid, handComp); return true; } public bool IsHolding(EntityUid uid, EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null) { inHand = null; if (!Resolve(uid, ref handsComp, false)) return false; foreach (var hand in handsComp.Hands.Values) { if (hand.HeldEntity == entity) { inHand = hand; return true; } } return false; } public bool TryGetHand(EntityUid handsUid, string handId, [NotNullWhen(true)] out Hand? hand, HandsComponent? hands = null) { hand = null; if (!Resolve(handsUid, ref hands)) return false; return hands.Hands.TryGetValue(handId, out hand); } }