using System.Threading.Tasks; using Content.Server.Hands.Components; using Content.Shared.Stunnable; using Robust.Shared.Map; using Robust.Shared.Timing; namespace Content.Server.DoAfter { public sealed class DoAfter { public Task AsTask { get; } private TaskCompletionSource Tcs { get; } public readonly DoAfterEventArgs EventArgs; public TimeSpan StartTime { get; } public float Elapsed { get; set; } public EntityCoordinates UserGrid { get; } public EntityCoordinates TargetGrid { get; } public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running; // NeedHand private readonly string? _activeHand; private readonly EntityUid? _activeItem; public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager) { EventArgs = eventArgs; StartTime = IoCManager.Resolve().CurTime; if (eventArgs.BreakOnUserMove) { UserGrid = entityManager.GetComponent(eventArgs.User).Coordinates; } if (eventArgs.Target != null && eventArgs.BreakOnTargetMove) { // Target should never be null if the bool is set. TargetGrid = entityManager.GetComponent(eventArgs.Target!.Value).Coordinates; } // For this we need to stay on the same hand slot and need the same item in that hand slot // (or if there is no item there we need to keep it free). if (eventArgs.NeedHand && entityManager.TryGetComponent(eventArgs.User, out HandsComponent? handsComponent)) { _activeHand = handsComponent.ActiveHand?.Name; _activeItem = handsComponent.ActiveHandEntity; } Tcs = new TaskCompletionSource(); AsTask = Tcs.Task; } public void Cancel() { if (Status == DoAfterStatus.Running) Tcs.SetResult(DoAfterStatus.Cancelled); } public void Run(float frameTime, IEntityManager entityManager) { switch (Status) { case DoAfterStatus.Running: break; case DoAfterStatus.Cancelled: case DoAfterStatus.Finished: return; default: throw new ArgumentOutOfRangeException(); } Elapsed += frameTime; if (IsFinished()) { // Do the final checks here if (!TryPostCheck()) { Tcs.SetResult(DoAfterStatus.Cancelled); } else { Tcs.SetResult(DoAfterStatus.Finished); } return; } if (IsCancelled(entityManager)) { Tcs.SetResult(DoAfterStatus.Cancelled); } } private bool IsCancelled(IEntityManager entityManager) { if (!entityManager.EntityExists(EventArgs.User) || EventArgs.Target is {} target && !entityManager.EntityExists(target)) { return true; } //https://github.com/tgstation/tgstation/blob/1aa293ea337283a0191140a878eeba319221e5df/code/__HELPERS/mobs.dm if (EventArgs.CancelToken.IsCancellationRequested) { return true; } // TODO :Handle inertia in space. if (EventArgs.BreakOnUserMove && !entityManager.GetComponent(EventArgs.User).Coordinates.InRange( entityManager, UserGrid, EventArgs.MovementThreshold)) { return true; } if (EventArgs.Target != null && EventArgs.BreakOnTargetMove && !entityManager.GetComponent(EventArgs.Target!.Value).Coordinates.InRange(entityManager, TargetGrid, EventArgs.MovementThreshold)) { return true; } if (EventArgs.ExtraCheck != null && !EventArgs.ExtraCheck.Invoke()) { return true; } if (EventArgs.BreakOnStun && entityManager.HasComponent(EventArgs.User)) { return true; } if (EventArgs.NeedHand) { if (!entityManager.TryGetComponent(EventArgs.User, out HandsComponent? handsComponent)) { // If we had a hand but no longer have it that's still a paddlin' if (_activeHand != null) { return true; } } else { var currentActiveHand = handsComponent.ActiveHand?.Name; if (_activeHand != currentActiveHand) { return true; } var currentItem = handsComponent.ActiveHandEntity; if (_activeItem != currentItem) { return true; } } } return false; } private bool TryPostCheck() { return EventArgs.PostCheck?.Invoke() != false; } private bool IsFinished() { if (Elapsed <= EventArgs.Delay) { return false; } return true; } } }