using System; using System.Threading.Tasks; using Content.Server.Coordinates.Helpers; using Content.Server.Pulling; using Content.Server.Tools; using Content.Server.Tools.Components; using Content.Shared.Interaction; using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Pulling.Components; using Robust.Shared.GameObjects; using Robust.Shared.Physics; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.ViewVariables; namespace Content.Server.Construction.Components { // TODO: Move this component's logic to an EntitySystem. [RegisterComponent] public class AnchorableComponent : Component, IInteractUsing { public override string Name => "Anchorable"; [ViewVariables] [DataField("tool", customTypeSerializer:typeof(PrototypeIdSerializer))] public string Tool { get; private set; } = "Anchoring"; [ViewVariables] int IInteractUsing.Priority => 1; [ViewVariables(VVAccess.ReadWrite)] [DataField("snap")] public bool Snap { get; private set; } = true; /// /// Checks if a tool can change the anchored status. /// /// The user doing the action /// The tool being used /// True if we're anchoring, and false if we're unanchoring. /// true if it is valid, false otherwise private async Task Valid(IEntity user, IEntity utilizing, bool anchoring) { if (!Owner.HasComponent()) { return false; } if (!utilizing.TryGetComponent(out ToolComponent? tool)) return false; BaseAnchoredAttemptEvent attempt = anchoring ? new AnchorAttemptEvent(user, utilizing) : new UnanchorAttemptEvent(user, utilizing); // Need to cast the event or it will be raised as BaseAnchoredAttemptEvent. if (anchoring) Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, (AnchorAttemptEvent) attempt, false); else Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, (UnanchorAttemptEvent) attempt, false); if (attempt.Cancelled) return false; return await EntitySystem.Get().UseTool(utilizing.Uid, user.Uid, Owner.Uid, 0f, 0.5f + attempt.Delay, Tool, toolComponent:tool); } /// /// Tries to anchor the owner of this component. /// /// The entity doing the anchoring /// The tool being used /// true if anchored, false otherwise public async Task TryAnchor(IEntity user, IEntity utilizing) { if (!(await Valid(user, utilizing, true))) { return false; } // Snap rotation to cardinal (multiple of 90) var rot = Owner.Transform.LocalRotation; Owner.Transform.LocalRotation = Math.Round(rot / (Math.PI / 2)) * (Math.PI / 2); if (Owner.TryGetComponent(out SharedPullableComponent? pullableComponent)) { if (pullableComponent.Puller != null) { EntitySystem.Get().TryStopPull(pullableComponent); } } if (Snap) Owner.SnapToGrid(Owner.EntityManager); Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new BeforeAnchoredEvent(user, utilizing), false); Owner.Transform.Anchored = true; Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new AnchoredEvent(user, utilizing), false); return true; } /// /// Tries to unanchor the owner of this component. /// /// The entity doing the unanchoring /// The tool being used, if any /// Whether or not to ignore valid tool checks /// true if unanchored, false otherwise public async Task TryUnAnchor(IEntity user, IEntity utilizing) { if (!(await Valid(user, utilizing, false))) { return false; } Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new BeforeUnanchoredEvent(user, utilizing), false); Owner.Transform.Anchored = false; Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new UnanchoredEvent(user, utilizing), false); return true; } /// /// Tries to toggle the anchored status of this component's owner. /// /// The entity doing the unanchoring /// The tool being used /// true if toggled, false otherwise public async Task TryToggleAnchor(IEntity user, IEntity utilizing) { return Owner.Transform.Anchored ? await TryUnAnchor(user, utilizing) : await TryAnchor(user, utilizing); } async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { return await TryToggleAnchor(eventArgs.User, eventArgs.Using); } } public abstract class BaseAnchoredAttemptEvent : CancellableEntityEventArgs { public IEntity User { get; } public IEntity Tool { get; } /// /// Extra delay to add to the do_after. /// Add to this, don't replace it. /// Output parameter. /// public float Delay { get; set; } = 0f; protected BaseAnchoredAttemptEvent(IEntity user, IEntity tool) { User = user; Tool = tool; } } public class AnchorAttemptEvent : BaseAnchoredAttemptEvent { public AnchorAttemptEvent(IEntity user, IEntity tool) : base(user, tool) { } } public class UnanchorAttemptEvent : BaseAnchoredAttemptEvent { public UnanchorAttemptEvent(IEntity user, IEntity tool) : base(user, tool) { } } public abstract class BaseAnchoredEvent : EntityEventArgs { public IEntity User { get; } public IEntity Tool { get; } protected BaseAnchoredEvent(IEntity user, IEntity tool) { User = user; Tool = tool; } } /// /// Raised just before the entity's body type is changed. /// public class BeforeAnchoredEvent : BaseAnchoredEvent { public BeforeAnchoredEvent(IEntity user, IEntity tool) : base(user, tool) { } } public class AnchoredEvent : BaseAnchoredEvent { public AnchoredEvent(IEntity user, IEntity tool) : base(user, tool) { } } /// /// Raised just before the entity's body type is changed. /// public class BeforeUnanchoredEvent : BaseAnchoredEvent { public BeforeUnanchoredEvent(IEntity user, IEntity tool) : base(user, tool) { } } public class UnanchoredEvent : BaseAnchoredEvent { public UnanchoredEvent(IEntity user, IEntity tool) : base(user, tool) { } } }