using Content.Shared.DisplacementMap; using Content.Shared.Hands.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared.Hands.Components; [RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] [Access(typeof(SharedHandsSystem))] public sealed partial class HandsComponent : Component { /// /// The currently active hand. /// [ViewVariables] public Hand? ActiveHand; /// /// The item currently held in the active hand. /// [ViewVariables] public EntityUid? ActiveHandEntity => ActiveHand?.HeldEntity; [ViewVariables] public Dictionary Hands = new(); 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. /// public List SortedHands = new(); /// /// If true, the items in the hands won't be affected by explosions. /// [DataField] public bool DisableExplosionRecursion = false; /// /// Modifies the speed at which items are thrown. /// [DataField] [ViewVariables(VVAccess.ReadWrite)] public float BaseThrowspeed { get; set; } = 11f; /// /// Distance after which longer throw targets stop increasing throw impulse. /// [DataField("throwRange")] [ViewVariables(VVAccess.ReadWrite)] public float ThrowRange { get; set; } = 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")] public bool ShowInHands = true; /// /// Data about the current sprite layers that the hand is contributing to the owner entity. Used for sprite in-hands. /// Used by the client. /// public readonly Dictionary> RevealedLayers = new(); /// /// The time at which throws will be allowed again. /// [DataField, ViewVariables(VVAccess.ReadWrite)] [AutoPausedField] public TimeSpan NextThrowTime; /// /// The minimum time inbetween throws. /// [DataField, ViewVariables(VVAccess.ReadWrite)] public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f); [DataField] public DisplacementData? HandDisplacement; /// /// If false, hands cannot be stripped, and they do not show up in the stripping menu. /// [DataField] public bool CanBeStripped = true; } [Serializable, NetSerializable] public sealed class Hand //TODO: This should definitely be a struct - Jezi { [ViewVariables] public string Name { get; } [ViewVariables] public HandLocation Location { get; } /// /// The container used to hold the contents of this hand. Nullable because the client must get the containers via , /// which may not be synced with the server when the client hands are created. /// [ViewVariables, NonSerialized] public ContainerSlot? Container; [ViewVariables] public EntityUid? HeldEntity => Container?.ContainedEntity; public bool IsEmpty => HeldEntity == null; public Hand(string name, HandLocation location, ContainerSlot? container = null) { Name = name; Location = location; Container = container; } } [Serializable, NetSerializable] public sealed class HandsComponentState : ComponentState { public readonly List Hands; public readonly List HandNames; public readonly string? ActiveHand; public HandsComponentState(HandsComponent handComp) { // cloning lists because of test networking. Hands = new(handComp.Hands.Values); HandNames = new(handComp.SortedHands); ActiveHand = handComp.ActiveHand?.Name; } } /// /// What side of the body this hand is on. /// /// /// public enum HandLocation : byte { Left, Middle, Right } /// /// What side of the UI a hand is on. /// /// /// public enum HandUILocation : byte { Left, Right } /// /// Helper functions for working with . /// public static class HandLocationExt { /// /// Convert a into the appropriate . /// This maps "middle" hands to . /// public static HandUILocation GetUILocation(this HandLocation location) { return location switch { HandLocation.Left => HandUILocation.Left, HandLocation.Middle => HandUILocation.Right, HandLocation.Right => HandUILocation.Right, _ => throw new ArgumentOutOfRangeException(nameof(location), location, null) }; } }