using System.Collections.Generic; using System.Linq; using Content.Shared.Acts; using Content.Shared.Alert; using Content.Shared.Buckle.Components; using Content.Shared.DragDrop; using Content.Shared.Interaction; using Content.Shared.Sound; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Content.Server.Buckle.Components { [RegisterComponent] [ComponentReference(typeof(SharedStrapComponent))] public class StrapComponent : SharedStrapComponent, IInteractHand, ISerializationHooks, IDestroyAct { [ComponentDependency] public readonly SpriteComponent? SpriteComponent = null; private readonly HashSet _buckledEntities = new(); /// /// The angle in degrees to rotate the player by when they get strapped /// [ViewVariables] [DataField("rotation")] private int _rotation; /// /// The size of the strap which is compared against when buckling entities /// [ViewVariables] [DataField("size")] private int _size = 100; private int _occupiedSize; /// /// The buckled entity will be offset by this amount from the center of the strap object. /// If this offset it too big, it will be clamped to /// [DataField("buckleOffset", required: false)] private Vector2 _buckleOffset = Vector2.Zero; private bool _enabled = true; /// /// If disabled, nothing can be buckled on this object, and it will unbuckle anything that's already buckled /// public bool Enabled { get => _enabled; set { _enabled = value; if (_enabled == value) return; RemoveAll(); } } /// /// The distance above which a buckled entity will be automatically unbuckled. /// Don't change it unless you really have to /// [DataField("maxBuckleDistance", required: false)] public float MaxBuckleDistance = 0.1f; /// /// You can specify the offset the entity will have after unbuckling. /// [DataField("unbuckleOffset", required: false)] public Vector2 UnbuckleOffset = Vector2.Zero; /// /// Gets and clamps the buckle offset to MaxBuckleDistance /// public Vector2 BuckleOffset => Vector2.Clamp( _buckleOffset, Vector2.One * -MaxBuckleDistance, Vector2.One * MaxBuckleDistance); /// /// The entity that is currently buckled here, synced from /// public IReadOnlyCollection BuckledEntities => _buckledEntities; /// /// The change in position to the strapped mob /// [DataField("position")] public StrapPosition Position { get; } = StrapPosition.None; /// /// The sound to be played when a mob is buckled /// [ViewVariables] [DataField("buckleSound")] public SoundSpecifier BuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/buckle.ogg"); /// /// The sound to be played when a mob is unbuckled /// [ViewVariables] [DataField("unbuckleSound")] public SoundSpecifier UnbuckleSound { get; } = new SoundPathSpecifier("/Audio/Effects/unbuckle.ogg"); /// /// ID of the alert to show when buckled /// [ViewVariables] [DataField("buckledAlertType")] public AlertType BuckledAlertType { get; } = AlertType.Buckled; /// /// The sum of the sizes of all the buckled entities in this strap /// [ViewVariables] public int OccupiedSize => _occupiedSize; /// /// Checks if this strap has enough space for a new occupant. /// /// The new occupant /// true if there is enough space, false otherwise public bool HasSpace(BuckleComponent buckle) { return OccupiedSize + buckle.Size <= _size; } /// /// DO NOT CALL THIS DIRECTLY. /// Adds a buckled entity. Called from /// /// The component to add /// /// Whether or not to check if the strap has enough space /// /// True if added, false otherwise public bool TryAdd(BuckleComponent buckle, bool force = false) { if (!Enabled) return false; if (!force && !HasSpace(buckle)) { return false; } if (!_buckledEntities.Add(buckle.Owner)) { return false; } _occupiedSize += buckle.Size; buckle.Appearance?.SetData(StrapVisuals.RotationAngle, _rotation); // Update the visuals of the strap object if (IoCManager.Resolve().TryGetComponent(Owner, out var appearance)) { appearance.SetData("StrapState", true); } #pragma warning disable 618 SendMessage(new StrapMessage(buckle.Owner, Owner)); #pragma warning restore 618 return true; } /// /// Removes a buckled entity. /// Called from /// /// The component to remove public void Remove(BuckleComponent buckle) { if (_buckledEntities.Remove(buckle.Owner)) { if (IoCManager.Resolve().TryGetComponent(Owner, out var appearance)) { appearance.SetData("StrapState", false); } _occupiedSize -= buckle.Size; #pragma warning disable 618 SendMessage(new UnStrapMessage(buckle.Owner, Owner)); #pragma warning restore 618 } } protected override void OnRemove() { base.OnRemove(); RemoveAll(); } void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) { RemoveAll(); } private void RemoveAll() { var entManager = IoCManager.Resolve(); foreach (var entity in _buckledEntities.ToArray()) { if (entManager.TryGetComponent(entity, out var buckle)) { buckle.TryUnbuckle(entity, true); } } _buckledEntities.Clear(); _occupiedSize = 0; } public override ComponentState GetComponentState() { return new StrapComponentState(Position); } bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) { var entManager = IoCManager.Resolve(); if (!entManager.TryGetComponent(eventArgs.User, out var buckle)) { return false; } return buckle.ToggleBuckle(eventArgs.User, Owner); } public override bool DragDropOn(DragDropEvent eventArgs) { var entManager = IoCManager.Resolve(); if (!entManager.TryGetComponent(eventArgs.Dragged, out BuckleComponent? buckleComponent)) return false; return buckleComponent.TryBuckle(eventArgs.User, Owner); } } }