#nullable enable using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Buckle; using Content.Shared.Alert; using Content.Shared.GameObjects.Components.Strap; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems.ActionBlocker; using Content.Shared.GameObjects.Verbs; using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Utility; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Localization; using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Strap { [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 /// [field: DataField("position")] public StrapPosition Position { get; } = StrapPosition.None; /// /// The sound to be played when a mob is buckled /// [ViewVariables] [field: DataField("buckleSound")] public string BuckleSound { get; } = "/Audio/Effects/buckle.ogg"; /// /// The sound to be played when a mob is unbuckled /// [ViewVariables] [field: DataField("unbuckleSound")] public string UnbuckleSound { get; } = "/Audio/Effects/unbuckle.ogg"; /// /// ID of the alert to show when buckled /// [ViewVariables] [field: 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)); } } public 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); } [Verb] private sealed class StrapVerb : Verb { protected override void GetData(IEntity user, StrapComponent component, VerbData data) { data.Visibility = VerbVisibility.Invisible; if (!ActionBlockerSystem.CanInteract(component.Owner) || !user.TryGetComponent(out var buckle) || buckle.BuckledTo != null && buckle.BuckledTo != component || user == component.Owner) { return; } var parent = component.Owner.Transform.Parent; while (parent != null) { if (parent == user.Transform) { return; } parent = parent.Parent; } if (!user.InRangeUnobstructed(component, buckle.Range)) { return; } data.Visibility = VerbVisibility.Visible; data.IconTexture = buckle.BuckledTo == null ? "/Textures/Interface/VerbIcons/buckle.svg.96dpi.png" : "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"; data.Text = Loc.GetString(buckle.BuckledTo == null ? "Buckle" : "Unbuckle"); } protected override void Activate(IEntity user, StrapComponent component) { if (!user.TryGetComponent(out var buckle)) { return; } buckle.ToggleBuckle(user, component.Owner); } } public override bool DragDropOn(DragDropEventArgs eventArgs) { if (!eventArgs.Dragged.TryGetComponent(out BuckleComponent? buckleComponent)) return false; return buckleComponent.TryBuckle(eventArgs.User, Owner); } } }