using System.Collections.Generic; using System.Linq; using Content.Shared.ActionBlocker; 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.Players; 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 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 (!force && !HasSpace(buckle)) { return false; } if (!_buckledEntities.Add(buckle.Owner)) { return false; } _occupiedSize += buckle.Size; buckle.Appearance?.SetData(StrapVisuals.RotationAngle, _rotation); SendMessage(new StrapMessage(buckle.Owner, Owner)); return true; } /// /// Removes a buckled entity. /// Called from /// /// The component to remove public void Remove(BuckleComponent buckle) { if (_buckledEntities.Remove(buckle.Owner)) { _occupiedSize -= buckle.Size; SendMessage(new UnStrapMessage(buckle.Owner, Owner)); } } protected override void OnRemove() { base.OnRemove(); RemoveAll(); } void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) { RemoveAll(); } private void RemoveAll() { foreach (var entity in _buckledEntities.ToArray()) { if (entity.TryGetComponent(out var buckle)) { buckle.TryUnbuckle(entity, true); } } _buckledEntities.Clear(); _occupiedSize = 0; } public override ComponentState GetComponentState(ICommonSession player) { return new StrapComponentState(Position); } bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) { if (!eventArgs.User.TryGetComponent(out var buckle)) { return false; } return buckle.ToggleBuckle(eventArgs.User, Owner); } public override bool DragDropOn(DragDropEvent eventArgs) { if (!eventArgs.Dragged.TryGetComponent(out BuckleComponent? buckleComponent)) return false; return buckleComponent.TryBuckle(eventArgs.User, Owner); } } }