diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 8efb99e07b..78be228d2e 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -1,4 +1,6 @@ -using Content.Shared.Interaction.Events; +using System.Threading; +using Content.Server.DoAfter; +using Content.Shared.Interaction.Events; using Content.Shared.Teleportation.Components; using Content.Shared.Teleportation.Systems; using Robust.Server.GameObjects; @@ -12,11 +14,26 @@ public sealed class HandTeleporterSystem : EntitySystem { [Dependency] private readonly LinkedEntitySystem _link = default!; [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly DoAfterSystem _doafter = default!; /// public override void Initialize() { SubscribeLocalEvent(OnUseInHand); + + SubscribeLocalEvent(OnPortalSuccess); + SubscribeLocalEvent(OnPortalCancelled); + } + + private void OnPortalSuccess(EntityUid uid, HandTeleporterComponent component, HandTeleporterSuccessEvent args) + { + component.CancelToken = null; + HandlePortalUpdating(uid, component, args.User); + } + + private void OnPortalCancelled(EntityUid uid, HandTeleporterComponent component, HandTeleporterCancelledEvent args) + { + component.CancelToken = null; } private void OnUseInHand(EntityUid uid, HandTeleporterComponent component, UseInHandEvent args) @@ -27,19 +44,67 @@ public sealed class HandTeleporterSystem : EntitySystem if (Deleted(component.SecondPortal)) component.SecondPortal = null; + if (component.CancelToken != null) + { + component.CancelToken.Cancel(); + return; + } + + if (component.FirstPortal != null && component.SecondPortal != null) + { + // handle removing portals immediately as opposed to a doafter + HandlePortalUpdating(uid, component, args.User); + } + else + { + var xform = Transform(args.User); + if (xform.ParentUid != xform.GridUid) + return; + + component.CancelToken = new CancellationTokenSource(); + var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, + component.CancelToken.Token, used: uid) + { + BreakOnDamage = true, + BreakOnStun = true, + BreakOnUserMove = true, + MovementThreshold = 0.5f, + UsedCancelledEvent = new HandTeleporterCancelledEvent(), + UsedFinishedEvent = new HandTeleporterSuccessEvent(args.User) + }; + + _doafter.DoAfter(doafterArgs); + } + } + + + /// + /// Creates or removes a portal given the state of the hand teleporter. + /// + private void HandlePortalUpdating(EntityUid uid, HandTeleporterComponent component, EntityUid user) + { + if (Deleted(user)) + return; + + var xform = Transform(user); + // Create the first portal. if (component.FirstPortal == null && component.SecondPortal == null) { - var timeout = EnsureComp(args.User); + // don't portal + if (xform.ParentUid != xform.GridUid) + return; + + var timeout = EnsureComp(user); timeout.EnteredPortal = null; - component.FirstPortal = Spawn(component.FirstPortalPrototype, Transform(args.User).Coordinates); + component.FirstPortal = Spawn(component.FirstPortalPrototype, Transform(user).Coordinates); _audio.PlayPvs(component.NewPortalSound, uid); } else if (component.SecondPortal == null) { - var timeout = EnsureComp(args.User); + var timeout = EnsureComp(user); timeout.EnteredPortal = null; - component.SecondPortal = Spawn(component.SecondPortalPrototype, Transform(args.User).Coordinates); + component.SecondPortal = Spawn(component.SecondPortalPrototype, Transform(user).Coordinates); _link.TryLink(component.FirstPortal!.Value, component.SecondPortal.Value, true); _audio.PlayPvs(component.NewPortalSound, uid); } diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index 24ca40d6bb..95e06c8aec 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.Audio; +using System.Threading; +using Content.Shared.Audio; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -33,4 +34,22 @@ public sealed class HandTeleporterComponent : Component [DataField("clearPortalsSound")] public SoundSpecifier ClearPortalsSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); + + /// + /// Delay for creating the portals in seconds. + /// + [DataField("portalCreationDelay")] + public float PortalCreationDelay = 2.5f; + + public CancellationTokenSource? CancelToken = null; } + +/// +/// Raised on doafter success for creating a portal. +/// +public record HandTeleporterSuccessEvent(EntityUid User); + +/// +/// Raised on doafter cancel for creating a portal. +/// +public record HandTeleporterCancelledEvent; diff --git a/Content.Shared/Teleportation/Components/PortalComponent.cs b/Content.Shared/Teleportation/Components/PortalComponent.cs index 4fbeb1b6d8..70cf119593 100644 --- a/Content.Shared/Teleportation/Components/PortalComponent.cs +++ b/Content.Shared/Teleportation/Components/PortalComponent.cs @@ -27,5 +27,5 @@ public sealed class PortalComponent : Component /// If no portals are linked, the subject will be teleported a random distance at maximum this far away. /// [DataField("maxRandomRadius")] - public float MaxRandomRadius = 10.0f; + public float MaxRandomRadius = 7.0f; }