using Content.Shared.DisplacementMap; using Content.Shared.Hands.EntitySystems; 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. /// [DataField] public string? ActiveHandId; /// /// Dictionary relating a unique hand ID corresponding to a container slot on the attached entity to a class containing information about the Hand itself. /// [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; /// /// Modifies the speed at which items are thrown. /// [DataField] public float BaseThrowspeed = 11f; /// /// Distance after which longer throw targets stop increasing throw impulse. /// [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] 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, AutoPausedField] public TimeSpan NextThrowTime; /// /// The minimum time inbetween throws. /// [DataField] public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f); /// /// Fallback displacement map applied to all sprites in the hand, unless otherwise specified /// [DataField] public DisplacementData? HandDisplacement; /// /// If defined, applies to all sprites in the left hand, ignoring /// [DataField] public DisplacementData? LeftHandDisplacement; /// /// If defined, applies to all sprites in the right hand, ignoring /// [DataField] public DisplacementData? RightHandDisplacement; /// /// If false, hands cannot be stripped, and they do not show up in the stripping menu. /// [DataField] public bool CanBeStripped = true; } [DataDefinition] [Serializable, NetSerializable] public partial record struct Hand { [DataField] public HandLocation Location = HandLocation.Right; public Hand() { } public Hand(HandLocation location) { Location = location; } } [Serializable, NetSerializable] public sealed class HandsComponentState : ComponentState { 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); SortedHands = new(handComp.SortedHands); ActiveHandId = handComp.ActiveHandId; } } /// /// 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) }; } }