From 9ebb452a3cd713d2ef673ea5561cc577dcaa769c Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Fri, 24 Feb 2023 19:01:25 -0500 Subject: [PATCH] DoAfter Refactor (#13225) Co-authored-by: DrSmugleaf --- Content.Client/DoAfter/DoAfterComponent.cs | 12 - Content.Client/DoAfter/DoAfterOverlay.cs | 10 +- Content.Client/DoAfter/DoAfterSystem.cs | 323 +++++++-------- .../Medical/Cryogenics/CryoPodSystem.cs | 2 - .../Tests/DoAfter/DoAfterServerTest.cs | 44 +- .../AirlockPainter/AirlockPainterSystem.cs | 74 ++-- Content.Server/Alert/Click/RemoveEnsnare.cs | 2 +- Content.Server/Animals/Systems/UdderSystem.cs | 57 +-- .../Anomaly/AnomalySystem.Scanner.cs | 30 +- Content.Server/Anomaly/AnomalySystem.cs | 1 + .../Components/AnomalyScannerComponent.cs | 19 - .../Body/Components/InternalsComponent.cs | 2 - .../Body/Systems/InternalsSystem.cs | 78 ++-- .../Botany/Components/BotanySwabComponent.cs | 5 - .../Botany/Systems/BotanySwabSystem.cs | 182 +++------ .../Chemistry/Components/InjectorComponent.cs | 7 - .../EntitySystems/ChemistrySystem.Injector.cs | 173 +++----- Content.Server/Climbing/ClimbSystem.cs | 40 +- .../Construction/AnchorableSystem.cs | 58 +-- .../Components/PartExchangerComponent.cs | 2 - .../ConstructionSystem.Initial.cs | 1 + .../ConstructionSystem.Interactions.cs | 65 ++- .../Construction/ConstructionSystem.cs | 4 +- .../Construction/PartExchangerSystem.cs | 49 +-- Content.Server/Construction/RefiningSystem.cs | 8 +- .../Cuffs/Components/CuffableComponent.cs | 1 + .../Cuffs/Components/HandcuffComponent.cs | 1 + .../Components/DiseaseSwabComponent.cs | 5 - .../Components/DiseaseVaccineComponent.cs | 4 - .../Disease/DiseaseDiagnosisSystem.cs | 74 +--- Content.Server/Disease/DiseaseSystem.cs | 88 +--- .../Unit/EntitySystems/DisposalUnitSystem.cs | 42 +- Content.Server/DoAfter/DoAfter.cs | 209 ---------- Content.Server/DoAfter/DoAfterComponent.cs | 20 - Content.Server/DoAfter/DoAfterEventArgs.cs | 144 ------- Content.Server/DoAfter/DoAfterSystem.cs | 224 +--------- Content.Server/Doors/Systems/DoorSystem.cs | 11 +- .../Dragon/Components/DragonComponent.cs | 2 - Content.Server/Dragon/DragonSystem.cs | 107 ++--- .../DisassembleOnAltVerbSystem.cs | 1 + .../EntitySystems/SpawnAfterInteractSystem.cs | 1 + .../Ensnaring/EnsnareableSystem.Ensnaring.cs | 52 +-- Content.Server/Ensnaring/EnsnareableSystem.cs | 36 +- .../EyeProtection/EyeProtectionSystem.cs | 1 + .../Fluids/Components/SpillableComponent.cs | 4 - .../Fluids/EntitySystems/MoppingSystem.cs | 72 ++-- .../Fluids/EntitySystems/SpillableSystem.cs | 68 +--- .../Components/ForensicPadComponent.cs | 4 - .../Forensics/Systems/ForensicPadSystem.cs | 86 ++-- .../Systems/ForensicScannerSystem.cs | 75 +--- .../Components/GatheringToolComponent.cs | 2 +- Content.Server/Gatherable/GatherableSystem.cs | 55 +-- Content.Server/Guardian/GuardianSystem.cs | 180 ++++---- Content.Server/Implants/ImplanterSystem.cs | 108 +++-- .../EntitySystems/KitchenSpikeSystem.cs | 75 +--- .../Kitchen/EntitySystems/SharpSystem.cs | 62 +-- .../Light/Components/PoweredLightComponent.cs | 2 - .../Light/EntitySystems/PoweredLightSystem.cs | 56 +-- .../Magic/Components/SpellbookComponent.cs | 5 +- Content.Server/Magic/MagicSystem.cs | 50 +-- .../Mech/Components/MechComponent.cs | 5 +- .../Components/MechGrabberComponent.cs | 25 -- .../EntitySystems/MechGrabberSystem.cs | 34 +- .../Mech/Systems/MechEquipmentSystem.cs | 36 +- Content.Server/Mech/Systems/MechSystem.cs | 112 ++--- .../BiomassReclaimerComponent.cs | 2 - .../BiomassReclaimerSystem.cs | 53 +-- .../Medical/Components/HealingComponent.cs | 2 - .../Components/HealthAnalyzerComponent.cs | 5 +- Content.Server/Medical/CryoPodSystem.cs | 40 +- Content.Server/Medical/HealingSystem.cs | 116 ++---- .../Medical/HealthAnalyzerSystem.cs | 104 ++--- .../Components/StethoscopeComponent.cs | 2 - .../Medical/Stethoscope/StethoscopeSystem.cs | 66 +-- Content.Server/Nuke/NukeComponent.cs | 2 - Content.Server/Nuke/NukeSystem.cs | 89 ++-- .../Nutrition/Components/DrinkComponent.cs | 7 - .../Nutrition/Components/FoodComponent.cs | 7 - .../Nutrition/EntitySystems/DrinkSystem.cs | 140 +++---- .../Nutrition/EntitySystems/FoodSystem.cs | 159 ++++---- .../Nutrition/EntitySystems/UtensilSystem.cs | 4 +- Content.Server/Nutrition/IngestionEvents.cs | 69 ---- .../Power/EntitySystems/ApcSystem.cs | 17 +- .../Power/EntitySystems/CableSystem.cs | 9 +- Content.Server/RCD/Systems/RCDSystem.cs | 1 + Content.Server/Repairable/RepairableSystem.cs | 8 +- .../Resist/CanEscapeInventoryComponent.cs | 7 - .../Resist/EscapeInventorySystem.cs | 37 +- .../Resist/ResistLockerComponent.cs | 4 +- Content.Server/Resist/ResistLockerSystem.cs | 79 ++-- .../EntitySystems/RevenantSystem.Abilities.cs | 94 ++--- .../Sticky/Components/StickyComponent.cs | 5 + Content.Server/Sticky/Systems/StickySystem.cs | 60 +-- .../Components/BluespaceLockerComponent.cs | 6 +- .../Components/ServerStorageComponent.cs | 5 - .../EntitySystems/BluespaceLockerSystem.cs | 33 +- .../Storage/EntitySystems/DumpableSystem.cs | 98 ++--- .../Storage/EntitySystems/StorageSystem.cs | 226 ++++------ Content.Server/Strip/StrippableSystem.cs | 3 +- .../Teleportation/HandTeleporterSystem.cs | 30 +- Content.Server/Toilet/ToiletSystem.cs | 10 +- .../Components/LatticeCuttingComponent.cs | 8 +- .../Tools/Components/TilePryingComponent.cs | 5 - .../Tools/Systems/WeldableSystem.cs | 9 +- .../Tools/ToolSystem.LatticeCutting.cs | 40 +- Content.Server/Tools/ToolSystem.TilePrying.cs | 48 +-- Content.Server/Tools/ToolSystem.cs | 304 -------------- .../Restock/VendingMachineRestockComponent.cs | 2 - .../Restock/VendingMachineRestockSystem.cs | 56 +-- .../VendingMachines/VendingMachineSystem.cs | 41 +- Content.Server/Wieldable/WieldableSystem.cs | 75 +--- Content.Server/Wires/WiresSystem.cs | 73 ++-- .../Components/AnchorableComponent.cs | 3 - .../DoAfter/ActiveDoAfterComponent.cs | 12 + Content.Shared/DoAfter/DoAfter.cs | 82 ++++ Content.Shared/DoAfter/DoAfterComponent.cs | 89 ++++ Content.Shared/DoAfter/DoAfterEventArgs.cs | 121 ++++++ .../DoAfter/SharedDoAfterComponent.cs | 90 ---- Content.Shared/DoAfter/SharedDoAfterSystem.cs | 385 ++++++++++++++++++ .../Components/EnsnaringComponent.cs | 1 - .../Components/MechEquipmentComponent.cs | 2 - .../Cryogenics/SharedCryoPodComponent.cs | 5 +- .../Medical/Cryogenics/SharedCryoPodSystem.cs | 16 +- .../EntitySystems/EncryptionKeySystem.cs | 4 +- .../Revenant/Components/RevenantComponent.cs | 4 - .../Storage/Components/DumpableComponent.cs | 5 +- .../Components/HandTeleporterComponent.cs | 15 +- .../Tools/Components/ToolComponent.cs | 64 +++ .../Systems/SharedToolSystem.MultipleTool.cs | 211 +++++++++- 129 files changed, 2624 insertions(+), 4132 deletions(-) delete mode 100644 Content.Client/DoAfter/DoAfterComponent.cs delete mode 100644 Content.Server/DoAfter/DoAfter.cs delete mode 100644 Content.Server/DoAfter/DoAfterComponent.cs delete mode 100644 Content.Server/DoAfter/DoAfterEventArgs.cs create mode 100644 Content.Shared/DoAfter/ActiveDoAfterComponent.cs create mode 100644 Content.Shared/DoAfter/DoAfter.cs create mode 100644 Content.Shared/DoAfter/DoAfterComponent.cs create mode 100644 Content.Shared/DoAfter/DoAfterEventArgs.cs delete mode 100644 Content.Shared/DoAfter/SharedDoAfterComponent.cs create mode 100644 Content.Shared/DoAfter/SharedDoAfterSystem.cs diff --git a/Content.Client/DoAfter/DoAfterComponent.cs b/Content.Client/DoAfter/DoAfterComponent.cs deleted file mode 100644 index 8857b373c7..0000000000 --- a/Content.Client/DoAfter/DoAfterComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Shared.DoAfter; - -namespace Content.Client.DoAfter -{ - [RegisterComponent, Access(typeof(DoAfterSystem))] - public sealed class DoAfterComponent : SharedDoAfterComponent - { - public readonly Dictionary DoAfters = new(); - - public readonly Dictionary CancelledDoAfters = new(); - } -} diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 84b4fbc271..1af3d8a6b2 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -1,4 +1,4 @@ -using Content.Client.Resources; +using Content.Shared.DoAfter; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; @@ -54,11 +54,11 @@ public sealed class DoAfterOverlay : Overlay var index = 0; var worldMatrix = Matrix3.CreateTranslation(worldPosition); - foreach (var (_, doAfter) in comp.DoAfters) + foreach (var doAfter in comp.DoAfters.Values) { - var elapsed = doAfter.Accumulator; + var elapsed = doAfter.Elapsed; var displayRatio = MathF.Min(1.0f, - elapsed / doAfter.Delay); + (float)elapsed.TotalSeconds / doAfter.Delay); Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); @@ -94,7 +94,7 @@ public sealed class DoAfterOverlay : Overlay // if we're cancelled then flick red / off. if (cancelled) { - var flash = Math.Floor(doAfter.CancelledAccumulator / flashTime) % 2 == 0; + var flash = Math.Floor((float)doAfter.CancelledElapsed.TotalSeconds / flashTime) % 2 == 0; color = new Color(1f, 0f, 0f, flash ? 1f : 0f); } else diff --git a/Content.Client/DoAfter/DoAfterSystem.cs b/Content.Client/DoAfter/DoAfterSystem.cs index d36a32d474..c4e06376da 100644 --- a/Content.Client/DoAfter/DoAfterSystem.cs +++ b/Content.Client/DoAfter/DoAfterSystem.cs @@ -1,223 +1,182 @@ using Content.Shared.DoAfter; -using JetBrains.Annotations; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Timing; using Robust.Shared.Utility; -namespace Content.Client.DoAfter +namespace Content.Client.DoAfter; + +/// +/// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors +/// such as moving. +/// +public sealed class DoAfterSystem : SharedDoAfterSystem { + [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + /// - /// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors - /// such as moving. + /// We'll use an excess time so stuff like finishing effects can show. /// - [UsedImplicitly] - public sealed class DoAfterSystem : EntitySystem + public const float ExcessTime = 0.5f; + + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IOverlayManager _overlay = default!; - [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; + base.Initialize(); + UpdatesOutsidePrediction = true; + SubscribeNetworkEvent(OnCancelledDoAfter); + SubscribeLocalEvent(OnDoAfterHandleState); + _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype)); + } - /// - /// We'll use an excess time so stuff like finishing effects can show. - /// - public const float ExcessTime = 0.5f; + public override void Shutdown() + { + base.Shutdown(); + _overlay.RemoveOverlay(); + } - public override void Initialize() + private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args) + { + if (args.Current is not DoAfterComponentState state) + return; + + foreach (var (_, doAfter) in state.DoAfters) { - base.Initialize(); - UpdatesOutsidePrediction = true; - SubscribeNetworkEvent(OnCancelledDoAfter); - SubscribeLocalEvent(OnDoAfterHandleState); - _overlay.AddOverlay( - new DoAfterOverlay( - EntityManager, - _prototype)); + if (component.DoAfters.ContainsKey(doAfter.ID)) + continue; + + component.DoAfters.Add(doAfter.ID, doAfter); } + } - public override void Shutdown() + private void OnCancelledDoAfter(CancelledDoAfterMessage ev) + { + if (!TryComp(ev.Uid, out var doAfter)) + return; + + Cancel(doAfter, ev.ID); + } + + /// + /// Remove a DoAfter without showing a cancellation graphic. + /// + public void Remove(DoAfterComponent component, Shared.DoAfter.DoAfter doAfter, bool found = false) + { + component.DoAfters.Remove(doAfter.ID); + component.CancelledDoAfters.Remove(doAfter.ID); + } + + /// + /// Mark a DoAfter as cancelled and show a cancellation graphic. + /// + /// Actual removal is handled by DoAfterEntitySystem. + public void Cancel(DoAfterComponent component, byte id) + { + if (component.CancelledDoAfters.ContainsKey(id)) + return; + + if (!component.DoAfters.ContainsKey(id)) + return; + + var doAfterMessage = component.DoAfters[id]; + doAfterMessage.Cancelled = true; + doAfterMessage.CancelledTime = GameTiming.CurTime; + component.CancelledDoAfters.Add(id, doAfterMessage); + } + + // TODO separate DoAfter & ActiveDoAfter components for the entity query. + public override void Update(float frameTime) + { + if (!GameTiming.IsFirstTimePredicted) + return; + + var playerEntity = _player.LocalPlayer?.ControlledEntity; + + foreach (var (comp, xform) in EntityQuery()) { - base.Shutdown(); - _overlay.RemoveOverlay(); - } + var doAfters = comp.DoAfters; - private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args) - { - if (args.Current is not DoAfterComponentState state) - return; + if (doAfters.Count == 0) + continue; - var toRemove = new RemQueue(); + var userGrid = xform.Coordinates; + var toRemove = new RemQueue(); - foreach (var (id, doAfter) in component.DoAfters) + // Check cancellations / finishes + foreach (var (id, doAfter) in doAfters) { - var found = false; - - foreach (var clientdoAfter in state.DoAfters) + // If we've passed the final time (after the excess to show completion graphic) then remove. + if ((float)doAfter.Elapsed.TotalSeconds + (float)doAfter.CancelledElapsed.TotalSeconds > + doAfter.Delay + ExcessTime) { - if (clientdoAfter.ID == id) + toRemove.Add(doAfter); + continue; + } + + if (doAfter.Cancelled) + { + doAfter.CancelledElapsed = GameTiming.CurTime - doAfter.CancelledTime; + continue; + } + + doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime; + + // Well we finished so don't try to predict cancels. + if ((float)doAfter.Elapsed.TotalSeconds > doAfter.Delay) + continue; + + // Predictions + if (comp.Owner != playerEntity) + continue; + + // TODO: Add these back in when I work out some system for changing the accumulation rate + // based on ping. Right now these would show as cancelled near completion if we moved at the end + // despite succeeding. + continue; + + if (doAfter.EventArgs.BreakOnUserMove) + { + if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.EventArgs.MovementThreshold)) { - found = true; - break; + Cancel(comp, id); + continue; } } - if (!found) + if (doAfter.EventArgs.BreakOnTargetMove) { - toRemove.Add(doAfter); + if (!Deleted(doAfter.EventArgs.Target) && + !Transform(doAfter.EventArgs.Target.Value).Coordinates.InRange(EntityManager, + doAfter.TargetGrid, + doAfter.EventArgs.MovementThreshold)) + { + Cancel(comp, id); + continue; + } } } foreach (var doAfter in toRemove) { - Remove(component, doAfter); + Remove(comp, doAfter); } - foreach (var doAfter in state.DoAfters) - { - if (component.DoAfters.ContainsKey(doAfter.ID)) - continue; + // Remove cancelled DoAfters after ExcessTime has elapsed + var toRemoveCancelled = new RemQueue(); - component.DoAfters.Add(doAfter.ID, doAfter); + foreach (var (_, doAfter) in comp.CancelledDoAfters) + { + var cancelledElapsed = (float)doAfter.CancelledElapsed.TotalSeconds; + + if (cancelledElapsed > ExcessTime) + toRemoveCancelled.Add(doAfter); } - } - private void OnCancelledDoAfter(CancelledDoAfterMessage ev) - { - if (!TryComp(ev.Uid, out var doAfter)) - return; - - Cancel(doAfter, ev.ID); - } - - /// - /// Remove a DoAfter without showing a cancellation graphic. - /// - public void Remove(DoAfterComponent component, ClientDoAfter clientDoAfter) - { - component.DoAfters.Remove(clientDoAfter.ID); - - var found = false; - - component.CancelledDoAfters.Remove(clientDoAfter.ID); - - if (!found) - component.DoAfters.Remove(clientDoAfter.ID); - } - - /// - /// Mark a DoAfter as cancelled and show a cancellation graphic. - /// - /// Actual removal is handled by DoAfterEntitySystem. - public void Cancel(DoAfterComponent component, byte id) - { - if (component.CancelledDoAfters.ContainsKey(id)) - return; - - if (!component.DoAfters.ContainsKey(id)) - return; - - var doAfterMessage = component.DoAfters[id]; - doAfterMessage.Cancelled = true; - component.CancelledDoAfters.Add(id, doAfterMessage); - } - - // TODO separate DoAfter & ActiveDoAfter components for the entity query. - public override void Update(float frameTime) - { - if (!_gameTiming.IsFirstTimePredicted) - return; - - var playerEntity = _player.LocalPlayer?.ControlledEntity; - - foreach (var (comp, xform) in EntityQuery()) + foreach (var doAfter in toRemoveCancelled) { - var doAfters = comp.DoAfters; - - if (doAfters.Count == 0) - { - continue; - } - - var userGrid = xform.Coordinates; - var toRemove = new RemQueue(); - - // Check cancellations / finishes - foreach (var (id, doAfter) in doAfters) - { - // If we've passed the final time (after the excess to show completion graphic) then remove. - if ((doAfter.Accumulator + doAfter.CancelledAccumulator) > doAfter.Delay + ExcessTime) - { - toRemove.Add(doAfter); - continue; - } - - if (doAfter.Cancelled) - { - doAfter.CancelledAccumulator += frameTime; - continue; - } - - doAfter.Accumulator += frameTime; - - // Well we finished so don't try to predict cancels. - if (doAfter.Accumulator > doAfter.Delay) - { - continue; - } - - // Predictions - if (comp.Owner != playerEntity) - continue; - - // TODO: Add these back in when I work out some system for changing the accumulation rate - // based on ping. Right now these would show as cancelled near completion if we moved at the end - // despite succeeding. - continue; - - if (doAfter.BreakOnUserMove) - { - if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold)) - { - Cancel(comp, id); - continue; - } - } - - if (doAfter.BreakOnTargetMove) - { - if (!EntityManager.Deleted(doAfter.Target) && - !Transform(doAfter.Target.Value).Coordinates.InRange(EntityManager, doAfter.TargetGrid, - doAfter.MovementThreshold)) - { - Cancel(comp, id); - continue; - } - } - } - - foreach (var doAfter in toRemove) - { - Remove(comp, doAfter); - } - - // Remove cancelled DoAfters after ExcessTime has elapsed - var toRemoveCancelled = new List(); - - foreach (var (_, doAfter) in comp.CancelledDoAfters) - { - if (doAfter.CancelledAccumulator > ExcessTime) - { - toRemoveCancelled.Add(doAfter); - } - } - - foreach (var doAfter in toRemoveCancelled) - { - Remove(comp, doAfter); - } + Remove(comp, doAfter); } } } diff --git a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs index b3aba4e804..fffbe3469b 100644 --- a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs +++ b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs @@ -18,8 +18,6 @@ public sealed class CryoPodSystem: SharedCryoPodSystem SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent>(AddAlternativeVerbs); SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(DoInsertCryoPod); - SubscribeLocalEvent(DoInsertCancelCryoPod); SubscribeLocalEvent(OnCryoPodPryFinished); SubscribeLocalEvent(OnCryoPodPryInterrupted); diff --git a/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs b/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs index 7a6c3f7f87..186a6a6d8c 100644 --- a/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs +++ b/Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using Content.Server.DoAfter; +using Content.Shared.DoAfter; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -21,17 +22,34 @@ namespace Content.IntegrationTests.Tests.DoAfter - type: DoAfter "; + public sealed class TestDoAfterSystem : EntitySystem + { + public override void Initialize() + { + SubscribeLocalEvent>(OnTestDoAfterFinishEvent); + } + + private void OnTestDoAfterFinishEvent(DoAfterEvent ev) + { + ev.AdditionalData.Cancelled = ev.Cancelled; + } + } + + private sealed class TestDoAfterData + { + public bool Cancelled; + }; + [Test] public async Task TestFinished() { - Task task = null; await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; await server.WaitIdleAsync(); - var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem(); + var data = new TestDoAfterData(); // That it finishes successfully await server.WaitPost(() => @@ -39,15 +57,12 @@ namespace Content.IntegrationTests.Tests.DoAfter var tickTime = 1.0f / IoCManager.Resolve().TickRate; var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace); var cancelToken = new CancellationTokenSource(); - var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token); - task = doAfterSystem.WaitDoAfter(args); + var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token) { Broadcast = true }; + doAfterSystem.DoAfter(args, data); }); await server.WaitRunTicks(1); - Assert.That(task.Status, Is.EqualTo(TaskStatus.RanToCompletion)); -#pragma warning disable RA0004 - Assert.That(task.Result == DoAfterStatus.Finished); -#pragma warning restore RA0004 + Assert.That(data.Cancelled, Is.False); await pairTracker.CleanReturnAsync(); } @@ -55,13 +70,11 @@ namespace Content.IntegrationTests.Tests.DoAfter [Test] public async Task TestCancelled() { - Task task = null; - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes}); var server = pairTracker.Pair.Server; var entityManager = server.ResolveDependency(); - var mapManager = server.ResolveDependency(); var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem(); + var data = new TestDoAfterData(); await server.WaitPost(() => { @@ -69,16 +82,13 @@ namespace Content.IntegrationTests.Tests.DoAfter var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace); var cancelToken = new CancellationTokenSource(); - var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token); - task = doAfterSystem.WaitDoAfter(args); + var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token) { Broadcast = true }; + doAfterSystem.DoAfter(args, data); cancelToken.Cancel(); }); await server.WaitRunTicks(3); - Assert.That(task.Status, Is.EqualTo(TaskStatus.RanToCompletion)); -#pragma warning disable RA0004 - Assert.That(task.Result, Is.EqualTo(DoAfterStatus.Cancelled), $"Result was {task.Result}"); -#pragma warning restore RA0004 + Assert.That(data.Cancelled, Is.False); await pairTracker.CleanReturnAsync(); } diff --git a/Content.Server/AirlockPainter/AirlockPainterSystem.cs b/Content.Server/AirlockPainter/AirlockPainterSystem.cs index f208366221..b21ee825e0 100644 --- a/Content.Server/AirlockPainter/AirlockPainterSystem.cs +++ b/Content.Server/AirlockPainter/AirlockPainterSystem.cs @@ -4,12 +4,12 @@ using Content.Server.Popups; using Content.Server.UserInterface; using Content.Shared.AirlockPainter; using Content.Shared.AirlockPainter.Prototypes; +using Content.Shared.DoAfter; using Content.Shared.Database; using Content.Shared.Doors.Components; using Content.Shared.Interaction; using JetBrains.Annotations; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Player; namespace Content.Server.AirlockPainter @@ -24,6 +24,7 @@ namespace Content.Server.AirlockPainter [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; public override void Initialize() @@ -33,27 +34,26 @@ namespace Content.Server.AirlockPainter SubscribeLocalEvent(AfterInteractOn); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnSpritePicked); - SubscribeLocalEvent(OnDoAfterComplete); - SubscribeLocalEvent(OnDoAfterCancelled); + SubscribeLocalEvent>(OnDoAfter); } - private void OnDoAfterComplete(AirlockPainterDoAfterComplete ev) + private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, DoAfterEvent args) { - ev.Component.IsSpraying = false; - if (TryComp(ev.Target, out var appearance) && - TryComp(ev.Target, out PaintableAirlockComponent? _)) + if (args.Handled || args.Cancelled) { - SoundSystem.Play(ev.Component.SpraySound.GetSound(), Filter.Pvs(ev.UsedTool, entityManager:EntityManager), ev.UsedTool); - _appearance.SetData(ev.Target, DoorVisuals.BaseRSI, ev.Sprite, appearance); - - // Log success - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(ev.User):user} painted {ToPrettyString(ev.Target):target}"); + component.IsSpraying = false; + return; } - } - private void OnDoAfterCancelled(AirlockPainterDoAfterCancelled ev) - { - ev.Component.IsSpraying = false; + if (args.Args.Target != null) + { + _audio.Play(component.SpraySound, Filter.Pvs(uid, entityManager:EntityManager), uid, true); + _appearance.SetData(args.Args.Target.Value, DoorVisuals.BaseRSI, args.AdditionalData.Sprite); + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Args.User):user} painted {ToPrettyString(args.Args.Target.Value):target}"); + component.IsSpraying = false; + } + + args.Handled = true; } private void OnActivate(EntityUid uid, AirlockPainterComponent component, ActivateInWorldEvent args) @@ -87,51 +87,22 @@ namespace Content.Server.AirlockPainter return; } component.IsSpraying = true; - var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, default, target) + + var airlockPainterData = new AirlockPainterData(sprite); + var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, target:target, used:uid) { BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, NeedHand = true, - BroadcastFinishedEvent = new AirlockPainterDoAfterComplete(uid, target, sprite, component, args.User), - BroadcastCancelledEvent = new AirlockPainterDoAfterCancelled(component), }; - _doAfterSystem.DoAfter(doAfterEventArgs); + _doAfterSystem.DoAfter(doAfterEventArgs, airlockPainterData); // Log attempt _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(uid):target} to '{style}' at {Transform(uid).Coordinates:targetlocation}"); } - private sealed class AirlockPainterDoAfterComplete : EntityEventArgs - { - public readonly EntityUid User; - public readonly EntityUid UsedTool; - public readonly EntityUid Target; - public readonly string Sprite; - public readonly AirlockPainterComponent Component; - - public AirlockPainterDoAfterComplete(EntityUid usedTool, EntityUid target, string sprite, - AirlockPainterComponent component, EntityUid user) - { - User = user; - UsedTool = usedTool; - Target = target; - Sprite = sprite; - Component = component; - } - } - - private sealed class AirlockPainterDoAfterCancelled : EntityEventArgs - { - public readonly AirlockPainterComponent Component; - - public AirlockPainterDoAfterCancelled(AirlockPainterComponent component) - { - Component = component; - } - } - private void OnSpritePicked(EntityUid uid, AirlockPainterComponent component, AirlockPainterSpritePickedMessage args) { component.Index = args.Index; @@ -147,5 +118,10 @@ namespace Content.Server.AirlockPainter _userInterfaceSystem.TrySetUiState(uid, AirlockPainterUiKey.Key, new AirlockPainterBoundUserInterfaceState(component.Index)); } + + private record struct AirlockPainterData(string Sprite) + { + public string Sprite = Sprite; + } } } diff --git a/Content.Server/Alert/Click/RemoveEnsnare.cs b/Content.Server/Alert/Click/RemoveEnsnare.cs index 7d519a6b73..975734152a 100644 --- a/Content.Server/Alert/Click/RemoveEnsnare.cs +++ b/Content.Server/Alert/Click/RemoveEnsnare.cs @@ -18,7 +18,7 @@ public sealed class RemoveEnsnare : IAlertClick if (!entManager.TryGetComponent(ensnare, out EnsnaringComponent? ensnaringComponent)) return; - entManager.EntitySysManager.GetEntitySystem().TryFree(player, ensnaringComponent); + entManager.EntitySysManager.GetEntitySystem().TryFree(player, ensnare, ensnaringComponent); } } } diff --git a/Content.Server/Animals/Systems/UdderSystem.cs b/Content.Server/Animals/Systems/UdderSystem.cs index 20b5fbdf23..adb8a2bf83 100644 --- a/Content.Server/Animals/Systems/UdderSystem.cs +++ b/Content.Server/Animals/Systems/UdderSystem.cs @@ -4,11 +4,11 @@ using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Server.Nutrition.Components; using Content.Server.Popups; +using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Nutrition.Components; using Content.Shared.Popups; using Content.Shared.Verbs; -using Robust.Shared.Player; namespace Content.Server.Animals.Systems { @@ -26,10 +26,8 @@ namespace Content.Server.Animals.Systems base.Initialize(); SubscribeLocalEvent>(AddMilkVerb); - SubscribeLocalEvent(OnMilkingFinished); - SubscribeLocalEvent(OnMilkingFailed); + SubscribeLocalEvent(OnDoAfter); } - public override void Update(float frameTime) { foreach (var udder in EntityManager.EntityQuery(false)) @@ -74,34 +72,41 @@ namespace Content.Server.Animals.Systems udder.BeingMilked = true; - var doargs = new DoAfterEventArgs(userUid, 5, default, uid) + var doargs = new DoAfterEventArgs(userUid, 5, target:uid, used:containerUid) { BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, - MovementThreshold = 1.0f, - TargetFinishedEvent = new MilkingFinishedEvent(userUid, containerUid), - TargetCancelledEvent = new MilkingFailEvent() + MovementThreshold = 1.0f }; _doAfterSystem.DoAfter(doargs); } - private void OnMilkingFinished(EntityUid uid, UdderComponent udder, MilkingFinishedEvent ev) + private void OnDoAfter(EntityUid uid, UdderComponent component, DoAfterEvent args) { - udder.BeingMilked = false; + if (args.Cancelled) + { + component.BeingMilked = false; + return; + } - if (!_solutionContainerSystem.TryGetSolution(uid, udder.TargetSolutionName, out var solution)) + if (args.Handled || args.Args.Used == null) return; - if (!_solutionContainerSystem.TryGetRefillableSolution(ev.ContainerUid, out var targetSolution)) + component.BeingMilked = false; + + if (!_solutionContainerSystem.TryGetSolution(uid, component.TargetSolutionName, out var solution)) + return; + + if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution)) return; var quantity = solution.Volume; if(quantity == 0) { - _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, ev.UserUid); + _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, args.Args.User); return; } @@ -109,15 +114,12 @@ namespace Content.Server.Animals.Systems quantity = targetSolution.AvailableVolume; var split = _solutionContainerSystem.SplitSolution(uid, solution, quantity); - _solutionContainerSystem.TryAddSolution(ev.ContainerUid, targetSolution, split); + _solutionContainerSystem.TryAddSolution(args.Args.Used.Value, targetSolution, split); - _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(ev.ContainerUid, EntityManager))), uid, - ev.UserUid, PopupType.Medium); - } + _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid, + args.Args.User, PopupType.Medium); - private void OnMilkingFailed(EntityUid uid, UdderComponent component, MilkingFailEvent ev) - { - component.BeingMilked = false; + args.Handled = true; } private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent args) @@ -138,20 +140,5 @@ namespace Content.Server.Animals.Systems }; args.Verbs.Add(verb); } - - private sealed class MilkingFinishedEvent : EntityEventArgs - { - public EntityUid UserUid; - public EntityUid ContainerUid; - - public MilkingFinishedEvent(EntityUid userUid, EntityUid containerUid) - { - UserUid = userUid; - ContainerUid = containerUid; - } - } - - private sealed class MilkingFailEvent : EntityEventArgs - { } } } diff --git a/Content.Server/Anomaly/AnomalySystem.Scanner.cs b/Content.Server/Anomaly/AnomalySystem.Scanner.cs index cdb18d41b7..70a3a10268 100644 --- a/Content.Server/Anomaly/AnomalySystem.Scanner.cs +++ b/Content.Server/Anomaly/AnomalySystem.Scanner.cs @@ -1,7 +1,7 @@ using Content.Server.Anomaly.Components; -using Content.Server.DoAfter; using Content.Shared.Anomaly; using Content.Shared.Anomaly.Components; +using Content.Shared.DoAfter; using Content.Shared.Interaction; using Robust.Server.GameObjects; using Robust.Shared.Utility; @@ -17,8 +17,7 @@ public sealed partial class AnomalySystem { SubscribeLocalEvent(OnScannerUiOpened); SubscribeLocalEvent(OnScannerAfterInteract); - SubscribeLocalEvent(OnScannerDoAfterFinished); - SubscribeLocalEvent(OnScannerDoAfterCancelled); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnScannerAnomalyShutdown); SubscribeLocalEvent(OnScannerAnomalySeverityChanged); @@ -73,38 +72,29 @@ public sealed partial class AnomalySystem private void OnScannerAfterInteract(EntityUid uid, AnomalyScannerComponent component, AfterInteractEvent args) { - if (component.TokenSource != null) - return; - if (args.Target is not { } target) return; if (!HasComp(target)) return; - component.TokenSource = new(); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, component.TokenSource.Token, target, uid) + _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, target:target, used:uid) { - DistanceThreshold = 2f, - UsedFinishedEvent = new AnomalyScanFinishedEvent(target, args.User), - UsedCancelledEvent = new AnomalyScanCancelledEvent() + DistanceThreshold = 2f }); } - private void OnScannerDoAfterFinished(EntityUid uid, AnomalyScannerComponent component, AnomalyScanFinishedEvent args) + private void OnDoAfter(EntityUid uid, AnomalyScannerComponent component, DoAfterEvent args) { - component.TokenSource = null; + if (args.Cancelled || args.Handled || args.Args.Target == null) + return; Audio.PlayPvs(component.CompleteSound, uid); Popup.PopupEntity(Loc.GetString("anomaly-scanner-component-scan-complete"), uid); - UpdateScannerWithNewAnomaly(uid, args.Anomaly, component); + UpdateScannerWithNewAnomaly(uid, args.Args.Target.Value, component); - if (TryComp(args.User, out var actor)) - _ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession); - } + if (TryComp(args.Args.User, out var actor)) _ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession); - private void OnScannerDoAfterCancelled(EntityUid uid, AnomalyScannerComponent component, AnomalyScanCancelledEvent args) - { - component.TokenSource = null; + args.Handled = true; } public void UpdateScannerUi(EntityUid uid, AnomalyScannerComponent? component = null) diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 3de46afb46..3e7122695f 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -7,6 +7,7 @@ using Content.Server.Materials; using Content.Server.Radio.EntitySystems; using Content.Shared.Anomaly; using Content.Shared.Anomaly.Components; +using Content.Shared.DoAfter; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Physics.Events; diff --git a/Content.Server/Anomaly/Components/AnomalyScannerComponent.cs b/Content.Server/Anomaly/Components/AnomalyScannerComponent.cs index 6fd8c1787d..cf40950224 100644 --- a/Content.Server/Anomaly/Components/AnomalyScannerComponent.cs +++ b/Content.Server/Anomaly/Components/AnomalyScannerComponent.cs @@ -22,28 +22,9 @@ public sealed class AnomalyScannerComponent : Component [DataField("scanDoAfterDuration")] public float ScanDoAfterDuration = 5; - public CancellationTokenSource? TokenSource; - /// /// The sound plays when the scan finished /// [DataField("completeSound")] public SoundSpecifier? CompleteSound = new SoundPathSpecifier("/Audio/Items/beep.ogg"); } - -public sealed class AnomalyScanFinishedEvent : EntityEventArgs -{ - public EntityUid Anomaly; - - public EntityUid User; - - public AnomalyScanFinishedEvent(EntityUid anomaly, EntityUid user) - { - Anomaly = anomaly; - User = user; - } -} - -public sealed class AnomalyScanCancelledEvent : EntityEventArgs -{ -} diff --git a/Content.Server/Body/Components/InternalsComponent.cs b/Content.Server/Body/Components/InternalsComponent.cs index abac2fe892..6fa1732127 100644 --- a/Content.Server/Body/Components/InternalsComponent.cs +++ b/Content.Server/Body/Components/InternalsComponent.cs @@ -16,7 +16,5 @@ namespace Content.Server.Body.Components [ViewVariables(VVAccess.ReadWrite)] [DataField("delay")] public float Delay = 3; - - public CancellationTokenSource? CancelToken = null; } } diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index bf5807b0c9..db9aff7e88 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -10,7 +10,7 @@ using Robust.Shared.Prototypes; using Content.Shared.Verbs; using Content.Server.Popups; using Content.Server.DoAfter; -using System.Threading; +using Content.Shared.DoAfter; namespace Content.Server.Body.Systems; @@ -33,8 +33,7 @@ public sealed class InternalsSystem : EntitySystem SubscribeLocalEvent(OnInternalsStartup); SubscribeLocalEvent(OnInternalsShutdown); SubscribeLocalEvent>(OnGetInteractionVerbs); - SubscribeLocalEvent(OnToggleOtherInternalsComplete); - SubscribeLocalEvent(OnToggleOtherInternalCanceled); + SubscribeLocalEvent>(OnDoAfter); } private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent args) @@ -59,9 +58,7 @@ public sealed class InternalsSystem : EntitySystem public void ToggleInternals(EntityUid uid, EntityUid user, bool force, InternalsComponent? internals = null) { if (!Resolve(uid, ref internals, false)) - { return; - } // Toggle off if they're on if (AreInternalsWorking(internals)) @@ -77,7 +74,7 @@ public sealed class InternalsSystem : EntitySystem return; } - var tank = FindBestGasTank(internals); + var tank = FindBestGasTank(uid ,internals); if (tank == null) { @@ -85,24 +82,26 @@ public sealed class InternalsSystem : EntitySystem return; } + var isUser = uid == user; + + var internalsData = new InternalsData(); + if (!force) { // Is the target not you? If yes, use a do-after to give them time to respond. //If no, do a short delay. There's no reason it should be beyond 1 second. - var delay = uid != user ? internals.Delay : 1.0f; + var delay = !isUser ? internals.Delay : 1.0f; - internals.CancelToken?.Cancel(); - internals.CancelToken = new CancellationTokenSource(); - _doAfter.DoAfter(new DoAfterEventArgs(user, delay, internals.CancelToken.Token, uid) + _doAfter.DoAfter(new DoAfterEventArgs(user, delay, target:uid) { BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, MovementThreshold = 0.1f, - TargetFinishedEvent = new ToggleOtherInternalsCompleteEvent(user, tank), - TargetCancelledEvent = new ToggleOtherInternalsCancelledEvent(), - }); + RaiseOnUser = isUser, + RaiseOnTarget = !isUser + }, internalsData); return; } @@ -110,20 +109,19 @@ public sealed class InternalsSystem : EntitySystem _gasTank.ConnectToInternals(tank); } - private void OnToggleOtherInternalsComplete(EntityUid uid, InternalsComponent component, ToggleOtherInternalsCompleteEvent ev) + private void OnDoAfter(EntityUid uid, InternalsComponent component, DoAfterEvent args) { - component.CancelToken = null; - ToggleInternals(uid, ev.User, true, component); - } + if (args.Cancelled || args.Handled) + return; - private static void OnToggleOtherInternalCanceled(EntityUid uid, InternalsComponent component, ToggleOtherInternalsCancelledEvent ev) - { - component.CancelToken = null; + ToggleInternals(uid, args.Args.User, true, component); + + args.Handled = true; } private void OnInternalsStartup(EntityUid uid, InternalsComponent component, ComponentStartup args) { - _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component)); + _alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component)); } private void OnInternalsShutdown(EntityUid uid, InternalsComponent component, ComponentShutdown args) @@ -138,7 +136,7 @@ public sealed class InternalsSystem : EntitySystem var gasTank = Comp(component.GasTankEntity!.Value); args.Gas = _gasTank.RemoveAirVolume(gasTank, Atmospherics.BreathVolume); // TODO: Should listen to gas tank updates instead I guess? - _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component)); + _alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component)); } } public void DisconnectBreathTool(InternalsComponent component) @@ -168,12 +166,11 @@ public sealed class InternalsSystem : EntitySystem public void DisconnectTank(InternalsComponent? component) { - if (component == null) return; + if (component == null) + return; if (TryComp(component.GasTankEntity, out GasTankComponent? tank)) - { _gasTank.DisconnectFromInternals(tank); - } component.GasTankEntity = null; _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component)); @@ -185,9 +182,7 @@ public sealed class InternalsSystem : EntitySystem return false; if (TryComp(component.GasTankEntity, out GasTankComponent? tank)) - { _gasTank.DisconnectFromInternals(tank); - } component.GasTankEntity = tankEntity; _alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component)); @@ -203,7 +198,8 @@ public sealed class InternalsSystem : EntitySystem private short GetSeverity(InternalsComponent component) { - if (component.BreathToolEntity == null || !AreInternalsWorking(component)) return 2; + if (component.BreathToolEntity == null || !AreInternalsWorking(component)) + return 2; // If pressure in the tank is below low pressure threshhold, flash warning on internals UI if (TryComp(component.GasTankEntity, out var gasTank) && gasTank.IsLowPressure) @@ -212,7 +208,7 @@ public sealed class InternalsSystem : EntitySystem return 1; } - public GasTankComponent? FindBestGasTank(InternalsComponent component) + public GasTankComponent? FindBestGasTank(EntityUid internalsOwner, InternalsComponent component) { // Prioritise // 1. back equipped tanks @@ -222,14 +218,14 @@ public sealed class InternalsSystem : EntitySystem InventoryComponent? inventory = null; ContainerManagerComponent? containerManager = null; - if (_inventory.TryGetSlotEntity(component.Owner, "back", out var backEntity, inventory, containerManager) && + if (_inventory.TryGetSlotEntity(internalsOwner, "back", out var backEntity, inventory, containerManager) && TryComp(backEntity, out var backGasTank) && _gasTank.CanConnectToInternals(backGasTank)) { return backGasTank; } - if (_inventory.TryGetSlotEntity(component.Owner, "suitstorage", out var entity, inventory, containerManager) && + if (_inventory.TryGetSlotEntity(internalsOwner, "suitstorage", out var entity, inventory, containerManager) && TryComp(entity, out var gasTank) && _gasTank.CanConnectToInternals(gasTank)) { @@ -238,12 +234,10 @@ public sealed class InternalsSystem : EntitySystem var tanks = new List(); - foreach (var hand in _hands.EnumerateHands(component.Owner)) + foreach (var hand in _hands.EnumerateHands(internalsOwner)) { if (TryComp(hand.HeldEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank)) - { tanks.Add(gasTank); - } } if (tanks.Count > 0) @@ -252,16 +246,14 @@ public sealed class InternalsSystem : EntitySystem return tanks[0]; } - if (Resolve(component.Owner, ref inventory, false)) + if (Resolve(internalsOwner, ref inventory, false)) { - var enumerator = new InventorySystem.ContainerSlotEnumerator(component.Owner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT); + var enumerator = new InventorySystem.ContainerSlotEnumerator(internalsOwner, inventory.TemplateId, _protoManager, _inventory, SlotFlags.POCKET | SlotFlags.BELT); while (enumerator.MoveNext(out var container)) { if (TryComp(container.ContainedEntity, out gasTank) && _gasTank.CanConnectToInternals(gasTank)) - { tanks.Add(gasTank); - } } if (tanks.Count > 0) @@ -273,11 +265,9 @@ public sealed class InternalsSystem : EntitySystem return null; } - private readonly record struct ToggleOtherInternalsCompleteEvent(EntityUid User, GasTankComponent Tank) - { - public readonly EntityUid User = User; - public readonly GasTankComponent Tank = Tank; - } - private readonly record struct ToggleOtherInternalsCancelledEvent; + private record struct InternalsData + { + + } } diff --git a/Content.Server/Botany/Components/BotanySwabComponent.cs b/Content.Server/Botany/Components/BotanySwabComponent.cs index d2b1b7057a..e772d3b492 100644 --- a/Content.Server/Botany/Components/BotanySwabComponent.cs +++ b/Content.Server/Botany/Components/BotanySwabComponent.cs @@ -11,11 +11,6 @@ namespace Content.Server.Botany [DataField("swabDelay")] public float SwabDelay = 2f; - /// - /// Token for interrupting swabbing do after. - /// - public CancellationTokenSource? CancelToken; - /// /// SeedData from the first plant that got swabbed. /// diff --git a/Content.Server/Botany/Systems/BotanySwabSystem.cs b/Content.Server/Botany/Systems/BotanySwabSystem.cs index f4417ed622..09b6c38ccc 100644 --- a/Content.Server/Botany/Systems/BotanySwabSystem.cs +++ b/Content.Server/Botany/Systems/BotanySwabSystem.cs @@ -1,142 +1,84 @@ -using System.Threading; using Content.Server.Botany.Components; using Content.Server.DoAfter; -using Content.Server.Hands.Components; -using Content.Server.Nutrition.EntitySystems; using Content.Server.Popups; +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.Interaction; -using Content.Shared.Inventory; -using Content.Shared.Tools.Components; -using Robust.Shared.Audio; -using Robust.Shared.Player; -using Robust.Shared.Random; -using Robust.Shared.Utility; -namespace Content.Server.Botany.Systems +namespace Content.Server.Botany.Systems; + +public sealed class BotanySwabSystem : EntitySystem { - public sealed class BotanySwabSystem : EntitySystem + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly MutationSystem _mutationSystem = default!; + + public override void Initialize() { - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly MutationSystem _mutationSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnDoAfter); + } - public override void Initialize() + /// + /// This handles swab examination text + /// so you can tell if they are used or not. + /// + private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args) + { + if (args.IsInDetailsRange) { - base.Initialize(); - SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnExamined); - // Private Events - SubscribeLocalEvent(OnTargetSwabSuccessful); - SubscribeLocalEvent(OnSwabCancelled); - } - - /// - /// Handles swabbing a plant. - /// - private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args) - { - if (swab.CancelToken != null) - { - swab.CancelToken.Cancel(); - swab.CancelToken = null; - return; - } - - if (args.Target == null || !args.CanReach) - return; - - if (!TryComp(args.Target, out var plant)) - return; - - swab.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target) - { - BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, plant), - BroadcastCancelledEvent = new SwabCancelledEvent(swab), - BreakOnTargetMove = true, - BreakOnUserMove = true, - BreakOnStun = true, - NeedHand = true - }); - } - - /// - /// This handles swab examination text - /// so you can tell if they are used or not. - /// - private void OnExamined(EntityUid uid, BotanySwabComponent swab, ExaminedEvent args) - { - if (args.IsInDetailsRange) - { - if (swab.SeedData != null) - args.PushMarkup(Loc.GetString("swab-used")); - else - args.PushMarkup(Loc.GetString("swab-unused")); - } - } - - /// - /// Save seed data or cross-pollenate. - /// - private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args) - { - if (args.Target == null) - return; - - if (args.Swab.SeedData == null) - { - // Pick up pollen - args.Swab.SeedData = args.Plant.Seed; - _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Target.Value, args.User); - } + if (swab.SeedData != null) + args.PushMarkup(Loc.GetString("swab-used")); else - { - var old = args.Plant.Seed; // Save old plant pollen - if (old == null) - return; - args.Plant.Seed = _mutationSystem.Cross(args.Swab.SeedData, old); // Cross-pollenate - args.Swab.SeedData = old; // Transfer old plant pollen to swab - _popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Target.Value, args.User); - } - - if (args.Swab.CancelToken != null) - { - args.Swab.CancelToken.Cancel(); - args.Swab.CancelToken = null; - } + args.PushMarkup(Loc.GetString("swab-unused")); } + } - private static void OnSwabCancelled(SwabCancelledEvent args) + /// + /// Handles swabbing a plant. + /// + private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args) + { + if (args.Target == null || !args.CanReach) + return; + + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid) { - args.Swab.CancelToken = null; - } + Broadcast = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnStun = true, + NeedHand = true + }); + } - private sealed class SwabCancelledEvent : EntityEventArgs + /// + /// Save seed data or cross-pollenate. + /// + private void OnDoAfter(DoAfterEvent args) + { + if (args.Cancelled || args.Handled || !TryComp(args.Args.Target, out var plant) || !TryComp(args.Args.Used, out var swab)) + return; + + if (swab.SeedData == null) { - public readonly BotanySwabComponent Swab; - public SwabCancelledEvent(BotanySwabComponent swab) - { - Swab = swab; - } + // Pick up pollen + swab.SeedData = plant.Seed; + _popupSystem.PopupEntity(Loc.GetString("botany-swab-from"), args.Args.Target.Value, args.Args.User); } - - private sealed class TargetSwabSuccessfulEvent : EntityEventArgs + else { - public EntityUid User { get; } - public EntityUid? Target { get; } - public BotanySwabComponent Swab { get; } - - public PlantHolderComponent Plant { get; } - - public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, BotanySwabComponent swab, PlantHolderComponent plant) - { - User = user; - Target = target; - Swab = swab; - Plant = plant; - } + var old = plant.Seed; + if (old == null) + return; + plant.Seed = _mutationSystem.Cross(swab.SeedData, old); // Cross-pollenate + swab.SeedData = old; // Transfer old plant pollen to swab + _popupSystem.PopupEntity(Loc.GetString("botany-swab-to"), args.Args.Target.Value, args.Args.User); } + + args.Handled = true; } } diff --git a/Content.Server/Chemistry/Components/InjectorComponent.cs b/Content.Server/Chemistry/Components/InjectorComponent.cs index d86fe329e6..e9d6b7c42e 100644 --- a/Content.Server/Chemistry/Components/InjectorComponent.cs +++ b/Content.Server/Chemistry/Components/InjectorComponent.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Shared.Chemistry.Components; using Content.Shared.FixedPoint; @@ -63,12 +62,6 @@ namespace Content.Server.Chemistry.Components [DataField("delay")] public float Delay = 5; - /// - /// Token for interrupting a do-after action (e.g., injection another player). If not null, implies - /// component is currently "in use". - /// - public CancellationTokenSource? CancelToken; - [DataField("toggleState")] private InjectorToggleMode _toggleState; /// diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs index b60215893b..b011f39f5d 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs @@ -1,19 +1,15 @@ using Content.Server.Body.Components; using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components.SolutionManager; -using Content.Server.CombatMode; -using Content.Server.DoAfter; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; using Content.Shared.FixedPoint; -using Content.Shared.Hands; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Robust.Shared.GameStates; -using Robust.Shared.Player; -using System.Threading; +using Content.Shared.DoAfter; using Content.Shared.Mobs.Components; using Content.Shared.Verbs; using Robust.Server.GameObjects; @@ -32,14 +28,11 @@ public sealed partial class ChemistrySystem { SubscribeLocalEvent>(AddSetTransferVerbs); SubscribeLocalEvent(OnSolutionChange); - SubscribeLocalEvent(OnInjectorDeselected); + SubscribeLocalEvent(OnInjectDoAfter); SubscribeLocalEvent(OnInjectorStartup); SubscribeLocalEvent(OnInjectorUse); SubscribeLocalEvent(OnInjectorAfterInteract); SubscribeLocalEvent(OnInjectorGetState); - - SubscribeLocalEvent(OnInjectionComplete); - SubscribeLocalEvent(OnInjectionCancelled); } private void AddSetTransferVerbs(EntityUid uid, InjectorComponent component, GetVerbsEvent args) @@ -63,7 +56,7 @@ public sealed partial class ChemistrySystem verb.Act = () => { component.TransferAmount = FixedPoint2.New(amount); - args.User.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount))); + _popup.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), args.User, args.User); }; // we want to sort by size, not alphabetically by the verb text. @@ -74,38 +67,27 @@ public sealed partial class ChemistrySystem } } - private static void OnInjectionCancelled(InjectionCancelledEvent ev) - { - ev.Component.CancelToken = null; - } - - private void OnInjectionComplete(InjectionCompleteEvent ev) - { - ev.Component.CancelToken = null; - UseInjector(ev.Target, ev.User, ev.Component); - } - - private void UseInjector(EntityUid target, EntityUid user, InjectorComponent component) + private void UseInjector(EntityUid target, EntityUid user, EntityUid injector, InjectorComponent component) { // Handle injecting/drawing for solutions if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject) { if (_solutions.TryGetInjectableSolution(target, out var injectableSolution)) { - TryInject(component, target, injectableSolution, user, false); + TryInject(component, injector, target, injectableSolution, user, false); } else if (_solutions.TryGetRefillableSolution(target, out var refillableSolution)) { - TryInject(component, target, refillableSolution, user, true); + TryInject(component, injector, target, refillableSolution, user, true); } else if (TryComp(target, out var bloodstream)) { - TryInjectIntoBloodstream(component, bloodstream, user); + TryInjectIntoBloodstream(component, injector, target, bloodstream, user); } else { _popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message", - ("target", Identity.Entity(target, EntityManager))), component.Owner, user); + ("target", Identity.Entity(target, EntityManager))), injector, user); } } else if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Draw) @@ -113,29 +95,23 @@ public sealed partial class ChemistrySystem // Draw from a bloodstream, if the target has that if (TryComp(target, out var stream)) { - TryDraw(component, target, stream.BloodSolution, user, stream); + TryDraw(component, injector, target, stream.BloodSolution, user, stream); return; } // Draw from an object (food, beaker, etc) if (_solutions.TryGetDrawableSolution(target, out var drawableSolution)) { - TryDraw(component, target, drawableSolution, user); + TryDraw(component, injector, target, drawableSolution, user); } else { _popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message", - ("target", Identity.Entity(target, EntityManager))), component.Owner, user); + ("target", Identity.Entity(target, EntityManager))), injector, user); } } } - private static void OnInjectorDeselected(EntityUid uid, InjectorComponent component, HandDeselectedEvent args) - { - component.CancelToken?.Cancel(); - component.CancelToken = null; - } - private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args) { Dirty(component); @@ -151,38 +127,38 @@ public sealed partial class ChemistrySystem args.State = new SharedInjectorComponent.InjectorComponentState(currentVolume, maxVolume, component.ToggleState); } + private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + UseInjector(args.Args.Target.Value, args.Args.User, uid, component); + + args.Handled = true; + } + private void OnInjectorAfterInteract(EntityUid uid, InjectorComponent component, AfterInteractEvent args) { if (args.Handled || !args.CanReach) return; - if (component.CancelToken != null) - { - args.Handled = true; - return; - } - //Make sure we have the attacking entity - if (args.Target is not { Valid: true } target || - !HasComp(uid)) - { + if (args.Target is not { Valid: true } target || !HasComp(uid)) return; - } // Is the target a mob? If yes, use a do-after to give them time to respond. - if (HasComp(target) || - HasComp(target)) + if (HasComp(target) || HasComp(target)) { // Are use using an injector capible of targeting a mob? if (component.IgnoreMobs) return; - InjectDoAfter(component, args.User, target); + InjectDoAfter(component, args.User, target, uid); args.Handled = true; return; } - UseInjector(target, args.User, component); + UseInjector(target, args.User, uid, component); args.Handled = true; } @@ -197,14 +173,14 @@ public sealed partial class ChemistrySystem if (args.Handled) return; - Toggle(component, args.User); + Toggle(component, args.User, uid); args.Handled = true; } /// /// Toggle between draw/inject state if applicable /// - private void Toggle(InjectorComponent component, EntityUid user) + private void Toggle(InjectorComponent component, EntityUid user, EntityUid injector) { if (component.InjectOnly) { @@ -226,18 +202,18 @@ public sealed partial class ChemistrySystem throw new ArgumentOutOfRangeException(); } - _popup.PopupEntity(Loc.GetString(msg), component.Owner, user); + _popup.PopupEntity(Loc.GetString(msg), injector, user); } /// /// Send informative pop-up messages and wait for a do-after to complete. /// - private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target) + private void InjectDoAfter(InjectorComponent component, EntityUid user, EntityUid target, EntityUid injector) { // Create a pop-up for the user _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user); - if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution)) + if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution)) return; var actualDelay = MathF.Max(component.Delay, 1f); @@ -277,62 +253,48 @@ public sealed partial class ChemistrySystem actualDelay /= 2; if (component.ToggleState == SharedInjectorComponent.InjectorToggleMode.Inject) - _adminLogger.Add(LogType.Ingestion, - $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}."); + _adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}."); } - component.CancelToken = new CancellationTokenSource(); - - _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, component.CancelToken.Token, target) + _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector) { BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, - MovementThreshold = 0.1f, - BroadcastFinishedEvent = new InjectionCompleteEvent() - { - Component = component, - User = user, - Target = target, - }, - BroadcastCancelledEvent = new InjectionCancelledEvent() - { - Component = component, - } + MovementThreshold = 0.1f }); } - private void TryInjectIntoBloodstream(InjectorComponent component, BloodstreamComponent targetBloodstream, EntityUid user) + private void TryInjectIntoBloodstream(InjectorComponent component, EntityUid injector, EntityUid target, BloodstreamComponent targetBloodstream, EntityUid user) { // Get transfer amount. May be smaller than _transferAmount if not enough room var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetBloodstream.ChemicalSolution.AvailableVolume); if (realTransferAmount <= 0) { - _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), - component.Owner, user); + _popup.PopupEntity(Loc.GetString("injector-component-cannot-inject-message", ("target", Identity.Entity(target, EntityManager))), injector, user); return; } // Move units from attackSolution to targetSolution var removedSolution = _solutions.SplitSolution(user, targetBloodstream.ChemicalSolution, realTransferAmount); - _blood.TryAddToChemicals((targetBloodstream).Owner, removedSolution, targetBloodstream); + _blood.TryAddToChemicals(target, removedSolution, targetBloodstream); - removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection); + _reactiveSystem.DoEntityReaction(target, removedSolution, ReactionMethod.Injection); _popup.PopupEntity(Loc.GetString("injector-component-inject-success-message", ("amount", removedSolution.Volume), - ("target", Identity.Entity(targetBloodstream.Owner, EntityManager))), component.Owner, user); + ("target", Identity.Entity(target, EntityManager))), injector, user); Dirty(component); - AfterInject(component); + AfterInject(component, injector); } - private void TryInject(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill) + private void TryInject(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, bool asRefill) { - if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) + if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution) || solution.Volume == 0) { return; @@ -344,14 +306,14 @@ public sealed partial class ChemistrySystem if (realTransferAmount <= 0) { _popup.PopupEntity(Loc.GetString("injector-component-target-already-full-message", ("target", Identity.Entity(targetEntity, EntityManager))), - component.Owner, user); + injector, user); return; } // Move units from attackSolution to targetSolution - var removedSolution = _solutions.SplitSolution(component.Owner, solution, realTransferAmount); + var removedSolution = _solutions.SplitSolution(injector, solution, realTransferAmount); - removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection); + _reactiveSystem.DoEntityReaction(targetEntity, removedSolution, ReactionMethod.Injection); if (!asRefill) { @@ -364,35 +326,35 @@ public sealed partial class ChemistrySystem _popup.PopupEntity(Loc.GetString("injector-component-transfer-success-message", ("amount", removedSolution.Volume), - ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user); + ("target", Identity.Entity(targetEntity, EntityManager))), injector, user); Dirty(component); - AfterInject(component); + AfterInject(component, injector); } - private void AfterInject(InjectorComponent component) + private void AfterInject(InjectorComponent component, EntityUid injector) { // Automatically set syringe to draw after completely draining it. - if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) + if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution) && solution.Volume == 0) { component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Draw; } } - private void AfterDraw(InjectorComponent component) + private void AfterDraw(InjectorComponent component, EntityUid injector) { // Automatically set syringe to inject after completely filling it. - if (_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) + if (_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution) && solution.AvailableVolume == 0) { component.ToggleState = SharedInjectorComponent.InjectorToggleMode.Inject; } } - private void TryDraw(InjectorComponent component, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null) + private void TryDraw(InjectorComponent component, EntityUid injector, EntityUid targetEntity, Solution targetSolution, EntityUid user, BloodstreamComponent? stream = null) { - if (!_solutions.TryGetSolution(component.Owner, InjectorComponent.SolutionName, out var solution) + if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution) || solution.AvailableVolume == 0) { return; @@ -404,34 +366,34 @@ public sealed partial class ChemistrySystem if (realTransferAmount <= 0) { _popup.PopupEntity(Loc.GetString("injector-component-target-is-empty-message", ("target", Identity.Entity(targetEntity, EntityManager))), - component.Owner, user); + injector, user); return; } // We have some snowflaked behavior for streams. if (stream != null) { - DrawFromBlood(user, targetEntity, component, solution, stream, realTransferAmount); + DrawFromBlood(user, injector, targetEntity, component, solution, stream, realTransferAmount); return; } // Move units from attackSolution to targetSolution var removedSolution = _solutions.Draw(targetEntity, targetSolution, realTransferAmount); - if (!_solutions.TryAddSolution(component.Owner, solution, removedSolution)) + if (!_solutions.TryAddSolution(injector, solution, removedSolution)) { return; } _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", ("amount", removedSolution.Volume), - ("target", Identity.Entity(targetEntity, EntityManager))), component.Owner, user); + ("target", Identity.Entity(targetEntity, EntityManager))), injector, user); Dirty(component); - AfterDraw(component); + AfterDraw(component, injector); } - private void DrawFromBlood(EntityUid user, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount) + private void DrawFromBlood(EntityUid user, EntityUid injector, EntityUid target, InjectorComponent component, Solution injectorSolution, BloodstreamComponent stream, FixedPoint2 transferAmount) { var drawAmount = (float) transferAmount; var bloodAmount = drawAmount; @@ -445,25 +407,14 @@ public sealed partial class ChemistrySystem var bloodTemp = stream.BloodSolution.SplitSolution(bloodAmount); var chemTemp = stream.ChemicalSolution.SplitSolution(chemAmount); - _solutions.TryAddSolution(component.Owner, injectorSolution, bloodTemp); - _solutions.TryAddSolution(component.Owner, injectorSolution, chemTemp); + _solutions.TryAddSolution(injector, injectorSolution, bloodTemp); + _solutions.TryAddSolution(injector, injectorSolution, chemTemp); _popup.PopupEntity(Loc.GetString("injector-component-draw-success-message", ("amount", transferAmount), - ("target", Identity.Entity(target, EntityManager))), component.Owner, user); + ("target", Identity.Entity(target, EntityManager))), injector, user); Dirty(component); - AfterDraw(component); - } - private sealed class InjectionCompleteEvent : EntityEventArgs - { - public InjectorComponent Component { get; init; } = default!; - public EntityUid User { get; init; } - public EntityUid Target { get; init; } - } - - private sealed class InjectionCancelledEvent : EntityEventArgs - { - public InjectorComponent Component { get; init; } = default!; + AfterDraw(component, injector); } } diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index 6f11cb0351..1908b2b17f 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Buckle.Components; using Content.Shared.Climbing; using Content.Shared.Climbing.Events; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.GameTicking; using Content.Shared.IdentityManagement; @@ -18,8 +19,6 @@ using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Verbs; using JetBrains.Annotations; -using Robust.Server.GameObjects; -using Robust.Shared.Configuration; using Robust.Shared.GameStates; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; @@ -34,7 +33,6 @@ namespace Content.Server.Climbing; [UsedImplicitly] public sealed class ClimbSystem : SharedClimbSystem { - [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; @@ -43,7 +41,6 @@ public sealed class ClimbSystem : SharedClimbSystem [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly InteractionSystem _interactionSystem = default!; [Dependency] private readonly StunSystem _stunSystem = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly BonkSystem _bonkSystem = default!; @@ -60,7 +57,7 @@ public sealed class ClimbSystem : SharedClimbSystem SubscribeLocalEvent>(AddClimbableVerb); SubscribeLocalEvent(OnClimbableDragDrop); - SubscribeLocalEvent(OnClimbFinished); + SubscribeLocalEvent>(OnDoAfter); SubscribeLocalEvent(OnClimbEndCollide); SubscribeLocalEvent(OnBuckleChange); SubscribeLocalEvent(OnClimbingGetState); @@ -117,19 +114,29 @@ public sealed class ClimbSystem : SharedClimbSystem if (_bonkSystem.TryBonk(entityToMove, climbable)) return; - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ClimbDelay, default, climbable, entityToMove) + var ev = new ClimbExtraEvent(); + + var args = new DoAfterEventArgs(user, component.ClimbDelay, target: climbable, used: entityToMove) { BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, - UsedFinishedEvent = new ClimbFinishedEvent(user, climbable, entityToMove) - }); + RaiseOnUser = user == entityToMove, + RaiseOnTarget = user != entityToMove + }; + + _doAfterSystem.DoAfter(args, ev); } - private void OnClimbFinished(EntityUid uid, ClimbingComponent climbing, ClimbFinishedEvent args) + private void OnDoAfter(EntityUid uid, ClimbingComponent component, DoAfterEvent args) { - Climb(uid, args.User, args.Instigator, args.Climbable, climbing: climbing); + if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null) + return; + + Climb(uid, args.Args.User, args.Args.Used.Value, args.Args.Target.Value, climbing: component); + + args.Handled = true; } private void Climb(EntityUid uid, EntityUid user, EntityUid instigator, EntityUid climbable, bool silent = false, ClimbingComponent? climbing = null, @@ -428,20 +435,11 @@ public sealed class ClimbSystem : SharedClimbSystem { _fixtureRemoveQueue.Clear(); } -} -internal sealed class ClimbFinishedEvent : EntityEventArgs -{ - public ClimbFinishedEvent(EntityUid user, EntityUid climbable, EntityUid instigator) + private sealed class ClimbExtraEvent : EntityEventArgs { - User = user; - Climbable = climbable; - Instigator = instigator; + //Honestly this is only here because otherwise this activates on every single doafter on a human } - - public EntityUid User { get; } - public EntityUid Climbable { get; } - public EntityUid Instigator { get; } } /// diff --git a/Content.Server/Construction/AnchorableSystem.cs b/Content.Server/Construction/AnchorableSystem.cs index 0a2ab04fcb..571d09e2a3 100644 --- a/Content.Server/Construction/AnchorableSystem.cs +++ b/Content.Server/Construction/AnchorableSystem.cs @@ -1,19 +1,16 @@ -using System.Threading; using Content.Server.Administration.Logs; using Content.Server.Coordinates.Helpers; using Content.Server.Popups; using Content.Server.Pulling; -using Content.Server.Tools; using Content.Shared.Construction.Components; using Content.Shared.Construction.EntitySystems; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Pulling.Components; +using Content.Shared.Tools; using Content.Shared.Tools.Components; using Robust.Shared.Map; -using Robust.Shared.Physics; using Robust.Shared.Physics.Components; -using Robust.Shared.Player; namespace Content.Server.Construction { @@ -22,16 +19,14 @@ namespace Content.Server.Construction [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly ToolSystem _tool = default!; + [Dependency] private readonly SharedToolSystem _tool = default!; [Dependency] private readonly PullingSystem _pulling = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAnchorComplete); - SubscribeLocalEvent(OnAnchorCancelled); SubscribeLocalEvent(OnUnanchorComplete); - SubscribeLocalEvent(OnUnanchorCancelled); SubscribeLocalEvent(OnAnchoredExamine); } @@ -42,14 +37,8 @@ namespace Content.Server.Construction args.PushMarkup(Loc.GetString(messageId, ("target", uid))); } - private void OnUnanchorCancelled(EntityUid uid, AnchorableComponent component, TryUnanchorCancelledEvent args) - { - component.CancelToken = null; - } - private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args) { - component.CancelToken = null; var xform = Transform(uid); RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, args.Using)); @@ -65,14 +54,8 @@ namespace Content.Server.Construction ); } - private void OnAnchorCancelled(EntityUid uid, AnchorableComponent component, TryAnchorCancelledEvent args) - { - component.CancelToken = null; - } - private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args) { - component.CancelToken = null; var xform = Transform(uid); if (TryComp(uid, out var anchorBody) && !TileFree(xform.Coordinates, anchorBody)) @@ -144,8 +127,7 @@ namespace Content.Server.Construction /// true if it is valid, false otherwise private bool Valid(EntityUid uid, EntityUid userUid, EntityUid usingUid, bool anchoring, AnchorableComponent? anchorable = null, ToolComponent? usingTool = null) { - if (!Resolve(uid, ref anchorable) || - anchorable.CancelToken != null) + if (!Resolve(uid, ref anchorable)) return false; if (!Resolve(usingUid, ref usingTool)) @@ -194,10 +176,8 @@ namespace Content.Server.Construction return; } - anchorable.CancelToken = new CancellationTokenSource(); - - _tool.UseTool(usingUid, userUid, uid, 0f, anchorable.Delay, usingTool.Qualities, - new TryAnchorCompletedEvent(userUid, usingUid), new TryAnchorCancelledEvent(userUid, usingUid), uid, cancelToken: anchorable.CancelToken.Token); + var toolEvData = new ToolEventData(new TryAnchorCompletedEvent(userUid, usingUid), targetEntity:uid); + _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData); } /// @@ -209,18 +189,17 @@ namespace Content.Server.Construction TransformComponent? transform = null, ToolComponent? usingTool = null) { - if (!Resolve(uid, ref anchorable, ref transform) || - anchorable.CancelToken != null) + if (!Resolve(uid, ref anchorable, ref transform)) return; - if (!Resolve(usingUid, ref usingTool)) return; + if (!Resolve(usingUid, ref usingTool)) + return; - if (!Valid(uid, userUid, usingUid, false)) return; + if (!Valid(uid, userUid, usingUid, false)) + return; - anchorable.CancelToken = new CancellationTokenSource(); - - _tool.UseTool(usingUid, userUid, uid, 0f, anchorable.Delay, usingTool.Qualities, - new TryUnanchorCompletedEvent(userUid, usingUid), new TryUnanchorCancelledEvent(userUid, usingUid), uid, cancelToken: anchorable.CancelToken.Token); + var toolEvData = new ToolEventData(new TryUnanchorCompletedEvent(userUid, usingUid), targetEntity:uid); + _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData); } /// @@ -271,12 +250,6 @@ namespace Content.Server.Construction } } - private sealed class TryUnanchorCancelledEvent : AnchorEvent - { - public TryUnanchorCancelledEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid) - { - } - } private sealed class TryAnchorCompletedEvent : AnchorEvent { @@ -284,12 +257,5 @@ namespace Content.Server.Construction { } } - - private sealed class TryAnchorCancelledEvent : AnchorEvent - { - public TryAnchorCancelledEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid) - { - } - } } } diff --git a/Content.Server/Construction/Components/PartExchangerComponent.cs b/Content.Server/Construction/Components/PartExchangerComponent.cs index d51a6609c0..fb7d3a89c6 100644 --- a/Content.Server/Construction/Components/PartExchangerComponent.cs +++ b/Content.Server/Construction/Components/PartExchangerComponent.cs @@ -27,6 +27,4 @@ public sealed class PartExchangerComponent : Component public SoundSpecifier ExchangeSound = new SoundPathSpecifier("/Audio/Items/rped.ogg"); public IPlayingAudioStream? AudioStream; - - public CancellationTokenSource? Token; } diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 721dd11fc7..e72848031a 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -11,6 +11,7 @@ using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Steps; using Content.Shared.Coordinates; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 503303bf4b..7634bdf00c 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -1,15 +1,15 @@ using Content.Server.Administration.Logs; using Content.Server.Construction.Components; -using Content.Server.DoAfter; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Shared.Construction; -using Content.Shared.Construction.EntitySystems; using Content.Shared.Construction.Steps; -using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Interaction; +using Content.Shared.Tools.Components; using Robust.Shared.Containers; #if EXCEPTION_TOLERANCE +// ReSharper disable once RedundantUsingDirective using Robust.Shared.Exceptions; #endif @@ -36,6 +36,7 @@ namespace Content.Server.Construction SubscribeLocalEvent(OnDoAfterCancelled); SubscribeLocalEvent(EnqueueEvent); SubscribeLocalEvent(EnqueueEvent); + SubscribeLocalEvent>(OnDoAfter); #endregion @@ -300,20 +301,19 @@ namespace Content.Server.Construction // If we still haven't completed this step's DoAfter... if (doAfterState == DoAfterState.None && insertStep.DoAfter > 0) { - _doAfterSystem.DoAfter( - new DoAfterEventArgs(interactUsing.User, step.DoAfter, default, interactUsing.Target) - { - BreakOnDamage = false, - BreakOnStun = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - NeedHand = true, + // These events will be broadcast and handled by this very same system, that will + // raise them directed to the target. These events wrap the original event. + var constructionData = new ConstructionData(new ConstructionDoAfterComplete(uid, ev), new ConstructionDoAfterCancelled(uid, ev)); + var doAfterEventArgs = new DoAfterEventArgs(interactUsing.User, step.DoAfter, target: interactUsing.Target) + { + BreakOnDamage = false, + BreakOnStun = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true + }; - // These events will be broadcast and handled by this very same system, that will - // raise them directed to the target. These events wrap the original event. - BroadcastFinishedEvent = new ConstructionDoAfterComplete(uid, ev), - BroadcastCancelledEvent = new ConstructionDoAfterCancelled(uid, ev) - }); + _doAfterSystem.DoAfter(doAfterEventArgs, constructionData); // To properly signal that we're waiting for a DoAfter, we have to set the flag on the component // and then also return the DoAfter HandleResult. @@ -375,9 +375,9 @@ namespace Content.Server.Construction if (doAfterState != DoAfterState.None) return doAfterState == DoAfterState.Completed ? HandleResult.True : HandleResult.False; - if (!_toolSystem.UseTool(interactUsing.Used, interactUsing.User, - uid, toolInsertStep.Fuel, toolInsertStep.DoAfter, toolInsertStep.Tool, - new ConstructionDoAfterComplete(uid, ev), new ConstructionDoAfterCancelled(uid, ev))) + var toolEvData = new ToolEventData(new ConstructionDoAfterComplete(uid, ev), toolInsertStep.Fuel, new ConstructionDoAfterCancelled(uid, ev)); + + if(!_toolSystem.UseTool(interactUsing.Used, interactUsing.User, uid, toolInsertStep.DoAfter, new [] {toolInsertStep.Tool}, toolEvData)) return HandleResult.False; // In the case we're not waiting for a doAfter, then this step is complete! @@ -546,6 +546,21 @@ namespace Content.Server.Construction _constructionUpdateQueue.Add(uid); } + private void OnDoAfter(EntityUid uid, ConstructionComponent component, DoAfterEvent args) + { + if (!Exists(args.Args.Target) || args.Handled) + return; + + if (args.Cancelled) + { + RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CancelEvent); + args.Handled = true; + } + + RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CompleteEvent); + args.Handled = true; + } + private void OnDoAfterComplete(ConstructionDoAfterComplete ev) { // Make extra sure the target entity exists... @@ -570,6 +585,18 @@ namespace Content.Server.Construction #region Event Definitions + private sealed class ConstructionData + { + public readonly object CompleteEvent; + public readonly object CancelEvent; + + public ConstructionData(object completeEvent, object cancelEvent) + { + CompleteEvent = completeEvent; + CancelEvent = cancelEvent; + } + } + /// /// This event signals that a construction interaction's DoAfter has completed successfully. /// This wraps the original event and also keeps some custom data that event handlers might need. diff --git a/Content.Server/Construction/ConstructionSystem.cs b/Content.Server/Construction/ConstructionSystem.cs index 2de4145829..45b8f232df 100644 --- a/Content.Server/Construction/ConstructionSystem.cs +++ b/Content.Server/Construction/ConstructionSystem.cs @@ -1,8 +1,8 @@ using Content.Server.Construction.Components; using Content.Server.DoAfter; using Content.Server.Stack; -using Content.Server.Tools; using Content.Shared.Construction; +using Content.Shared.Tools; using JetBrains.Annotations; using Robust.Server.Containers; using Robust.Shared.Prototypes; @@ -22,7 +22,7 @@ namespace Content.Server.Construction [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly StackSystem _stackSystem = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; private const string SawmillName = "Construction"; private ISawmill _sawmill = default!; diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs index 7389353a27..837bf808db 100644 --- a/Content.Server/Construction/PartExchangerSystem.cs +++ b/Content.Server/Construction/PartExchangerSystem.cs @@ -1,15 +1,14 @@ using System.Linq; -using System.Threading; using Content.Server.Construction.Components; using Content.Server.DoAfter; using Content.Server.Storage.Components; using Content.Server.Storage.EntitySystems; using Content.Server.Wires; +using Content.Shared.DoAfter; using Content.Shared.Construction.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Robust.Shared.Containers; -using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Server.Construction; @@ -27,16 +26,20 @@ public sealed class PartExchangerSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnFinished); - SubscribeLocalEvent(OnCancelled); + SubscribeLocalEvent(OnDoAfter); } - private void OnFinished(EntityUid uid, PartExchangerComponent component, RpedExchangeFinishedEvent args) + private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args) { - component.Token = null; + if (args.Cancelled || args.Handled || args.Args.Target == null) + { + component.AudioStream?.Stop(); + return; + } + component.AudioStream?.Stop(); - if (!TryComp(args.Target, out var machine)) + if (!TryComp(args.Args.Target.Value, out var machine)) return; if (!TryComp(uid, out var storage) || storage.Storage == null) @@ -59,7 +62,7 @@ public sealed class PartExchangerSystem : EntitySystem if (TryComp(ent, out var part)) { machineParts.Add(part); - _container.RemoveEntity(machine.Owner, ent); + _container.RemoveEntity(args.Args.Target.Value, ent); } } @@ -85,19 +88,12 @@ public sealed class PartExchangerSystem : EntitySystem _storage.Insert(uid, unused.Owner, null, false); } _construction.RefreshParts(machine); - } - private void OnCancelled(EntityUid uid, PartExchangerComponent component, RpedExchangeCancelledEvent args) - { - component.Token = null; - component.AudioStream?.Stop(); + args.Handled = true; } private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args) { - if (component.Token != null) - return; - if (component.DoDistanceCheck && !args.CanReach) return; @@ -116,28 +112,11 @@ public sealed class PartExchangerSystem : EntitySystem component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid); - component.Token = new CancellationTokenSource(); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, component.Token.Token, args.Target, args.Used) + _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, target:args.Target, used:args.Used) { BreakOnDamage = true, BreakOnStun = true, - BreakOnUserMove = true, - UsedFinishedEvent = new RpedExchangeFinishedEvent(args.Target.Value), - UsedCancelledEvent = new RpedExchangeCancelledEvent() + BreakOnUserMove = true }); } } - -public sealed class RpedExchangeFinishedEvent : EntityEventArgs -{ - public readonly EntityUid Target; - - public RpedExchangeFinishedEvent(EntityUid target) - { - Target = target; - } -} - -public readonly struct RpedExchangeCancelledEvent -{ -} diff --git a/Content.Server/Construction/RefiningSystem.cs b/Content.Server/Construction/RefiningSystem.cs index 644b8fec7a..73b4f8d760 100644 --- a/Content.Server/Construction/RefiningSystem.cs +++ b/Content.Server/Construction/RefiningSystem.cs @@ -1,15 +1,15 @@ using Content.Server.Construction.Components; -using Content.Server.Tools; using Content.Server.Stack; using Content.Shared.Interaction; using Content.Shared.Stacks; +using Content.Shared.Tools; using Content.Shared.Tools.Components; namespace Content.Server.Construction { public sealed class RefiningSystem : EntitySystem { - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly StackSystem _stackSystem = default!; public override void Initialize() { @@ -29,7 +29,9 @@ namespace Content.Server.Construction component.BeingWelded = true; - if (!await _toolSystem.UseTool(args.Used, args.User, uid, component.RefineFuel, component.RefineTime, component.QualityNeeded)) + var toolEvData = new ToolEventData(null); + + if (!_toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, toolEvData, component.RefineFuel)) { // failed to veld - abort refine component.BeingWelded = false; diff --git a/Content.Server/Cuffs/Components/CuffableComponent.cs b/Content.Server/Cuffs/Components/CuffableComponent.cs index 2e6194ca12..8d36b33f84 100644 --- a/Content.Server/Cuffs/Components/CuffableComponent.cs +++ b/Content.Server/Cuffs/Components/CuffableComponent.cs @@ -18,6 +18,7 @@ using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; using Content.Server.Recycling.Components; +using Content.Shared.DoAfter; using Robust.Shared.Map; namespace Content.Server.Cuffs.Components diff --git a/Content.Server/Cuffs/Components/HandcuffComponent.cs b/Content.Server/Cuffs/Components/HandcuffComponent.cs index 793595d941..e8dda6cd55 100644 --- a/Content.Server/Cuffs/Components/HandcuffComponent.cs +++ b/Content.Server/Cuffs/Components/HandcuffComponent.cs @@ -3,6 +3,7 @@ using Content.Server.Administration.Logs; using Content.Server.DoAfter; using Content.Shared.Cuffs.Components; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Popups; using Content.Shared.Stunnable; using Robust.Shared.Audio; diff --git a/Content.Server/Disease/Components/DiseaseSwabComponent.cs b/Content.Server/Disease/Components/DiseaseSwabComponent.cs index e55417f1c2..a4e6c0aa77 100644 --- a/Content.Server/Disease/Components/DiseaseSwabComponent.cs +++ b/Content.Server/Disease/Components/DiseaseSwabComponent.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Shared.Disease; namespace Content.Server.Disease.Components @@ -20,10 +19,6 @@ namespace Content.Server.Disease.Components /// public bool Used = false; /// - /// Token for interrupting swabbing do after. - /// - public CancellationTokenSource? CancelToken; - /// /// The disease prototype currently on the swab /// [ViewVariables] diff --git a/Content.Server/Disease/Components/DiseaseVaccineComponent.cs b/Content.Server/Disease/Components/DiseaseVaccineComponent.cs index fc2349729f..b949f6b580 100644 --- a/Content.Server/Disease/Components/DiseaseVaccineComponent.cs +++ b/Content.Server/Disease/Components/DiseaseVaccineComponent.cs @@ -18,10 +18,6 @@ namespace Content.Server.Disease.Components /// If this vaccine has been used /// public bool Used = false; - /// - /// Token for interrupting injection do after. - /// - public CancellationTokenSource? CancelToken; /// /// The disease prototype currently on the vaccine diff --git a/Content.Server/Disease/DiseaseDiagnosisSystem.cs b/Content.Server/Disease/DiseaseDiagnosisSystem.cs index 2cabef4e63..28b4dcd7c6 100644 --- a/Content.Server/Disease/DiseaseDiagnosisSystem.cs +++ b/Content.Server/Disease/DiseaseDiagnosisSystem.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Disease.Components; using Content.Shared.Disease; using Content.Shared.Interaction; @@ -17,6 +16,7 @@ using Robust.Shared.Audio; using Robust.Shared.Utility; using Content.Shared.Tools.Components; using Content.Server.Station.Systems; +using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Robust.Server.GameObjects; @@ -47,8 +47,7 @@ namespace Content.Server.Disease // Private Events SubscribeLocalEvent(OnDiagnoserFinished); SubscribeLocalEvent(OnVaccinatorFinished); - SubscribeLocalEvent(OnTargetSwabSuccessful); - SubscribeLocalEvent(OnSwabCancelled); + SubscribeLocalEvent(OnSwabDoAfter); } private Queue AddQueue = new(); @@ -100,18 +99,9 @@ namespace Content.Server.Disease /// private void OnAfterInteract(EntityUid uid, DiseaseSwabComponent swab, AfterInteractEvent args) { - if (swab.CancelToken != null) - { - swab.CancelToken.Cancel(); - swab.CancelToken = null; - return; - } if (args.Target == null || !args.CanReach) return; - if (!TryComp(args.Target, out var carrier)) - return; - if (swab.Used) { _popupSystem.PopupEntity(Loc.GetString("swab-already-used"), args.User, args.User); @@ -126,11 +116,8 @@ namespace Content.Server.Disease return; } - swab.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid) { - BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, carrier), - BroadcastCancelledEvent = new SwabCancelledEvent(swab), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -309,35 +296,26 @@ namespace Content.Server.Disease { UpdateAppearance(uid, args.Powered, false); } - /// - /// Private events - /// /// /// Copies a disease prototype to the swab /// after the doafter completes. /// - private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args) + private void OnSwabDoAfter(DoAfterEvent args) { - if (args.Target == null) + if (args.Handled || args.Cancelled || !TryComp(args.Args.Target, out var carrier) || !TryComp(args.Args.Target, out var swab)) return; - args.Swab.Used = true; - _popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User); + swab.Used = true; + _popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", Identity.Entity(args.Args.Target.Value, EntityManager))), args.Args.Target.Value, args.Args.User); - if (args.Swab.Disease != null || args.Carrier.Diseases.Count == 0) + if (swab.Disease != null || carrier.Diseases.Count == 0) return; - args.Swab.Disease = _random.Pick(args.Carrier.Diseases); + swab.Disease = _random.Pick(carrier.Diseases); + } - /// - /// Cancels the swab doafter if needed. - /// - private static void OnSwabCancelled(SwabCancelledEvent args) - { - args.Swab.CancelToken = null; - } /// /// Prints a diagnostic report with its findings. @@ -410,38 +388,6 @@ namespace Content.Server.Disease vaxxComp.Disease = args.Machine.Disease; } - /// - /// Cancels the mouth-swabbing doafter - /// - private sealed class SwabCancelledEvent : EntityEventArgs - { - public readonly DiseaseSwabComponent Swab; - public SwabCancelledEvent(DiseaseSwabComponent swab) - { - Swab = swab; - } - } - - /// - /// Fires if the doafter for swabbing someone's mouth succeeds - /// - private sealed class TargetSwabSuccessfulEvent : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid? Target { get; } - public DiseaseSwabComponent Swab { get; } - - public DiseaseCarrierComponent Carrier { get; } - - public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseSwabComponent swab, DiseaseCarrierComponent carrier) - { - User = user; - Target = target; - Swab = swab; - Carrier = carrier; - } - } - /// /// Fires when a disease machine is done /// with its production delay and ready to diff --git a/Content.Server/Disease/DiseaseSystem.cs b/Content.Server/Disease/DiseaseSystem.cs index f057f890b6..db6f0dc6a5 100644 --- a/Content.Server/Disease/DiseaseSystem.cs +++ b/Content.Server/Disease/DiseaseSystem.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Body.Systems; using Content.Server.Chat.Systems; using Content.Server.Disease.Components; @@ -9,6 +8,7 @@ using Content.Shared.Clothing.Components; using Content.Shared.Disease; using Content.Shared.Disease.Components; using Content.Shared.Disease.Events; +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; @@ -20,7 +20,6 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Rejuvenate; using Robust.Shared.Audio; using Robust.Server.GameObjects; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Serialization.Manager; @@ -59,8 +58,7 @@ namespace Content.Server.Disease // Handling stuff from other systems SubscribeLocalEvent(OnApplyMetabolicMultiplier); // Private events stuff - SubscribeLocalEvent(OnTargetVaxxSuccessful); - SubscribeLocalEvent(OnVaxxCancelled); + SubscribeLocalEvent(OnDoAfter); } private Queue AddQueue = new(); @@ -278,22 +276,7 @@ namespace Content.Server.Disease /// private void OnAfterInteract(EntityUid uid, DiseaseVaccineComponent vaxx, AfterInteractEvent args) { - if (vaxx.CancelToken != null) - { - vaxx.CancelToken.Cancel(); - vaxx.CancelToken = null; - return; - } - if (args.Target == null) - return; - - if (!args.CanReach) - return; - - if (vaxx.CancelToken != null) - return; - - if (!TryComp(args.Target, out var carrier)) + if (args.Target == null || !args.CanReach) return; if (vaxx.Used) @@ -302,11 +285,8 @@ namespace Content.Server.Disease return; } - vaxx.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, vaxx.CancelToken.Token, target: args.Target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, target: args.Target, used:uid) { - BroadcastFinishedEvent = new TargetVaxxSuccessfulEvent(args.User, args.Target, vaxx, carrier), - BroadcastCancelledEvent = new VaxxCancelledEvent(vaxx), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -350,7 +330,6 @@ namespace Content.Server.Disease } } - /// /// Helper functions /// @@ -490,60 +469,23 @@ namespace Content.Server.Disease carrier.PastDiseases.Add(disease); } - /// - /// Private Events Stuff - /// - - /// - /// Injects the vaccine into the target - /// if the doafter is completed - /// - private void OnTargetVaxxSuccessful(TargetVaxxSuccessfulEvent args) + private void OnDoAfter(EntityUid uid, DiseaseVaccineComponent component, DoAfterEvent args) { - if (args.Vaxx.Disease == null) + if (args.Handled || args.Cancelled || !TryComp(args.Args.Target, out var carrier) || component.Disease == null) return; - Vaccinate(args.Carrier, args.Vaxx.Disease); - EntityManager.DeleteEntity(args.Vaxx.Owner); - } - /// - /// Cancels the vaccine doafter - /// - private static void OnVaxxCancelled(VaxxCancelledEvent args) - { - args.Vaxx.CancelToken = null; - } - /// These two are standard doafter stuff you can ignore - private sealed class VaxxCancelledEvent : EntityEventArgs - { - public readonly DiseaseVaccineComponent Vaxx; - public VaxxCancelledEvent(DiseaseVaccineComponent vaxx) - { - Vaxx = vaxx; - } - } - private sealed class TargetVaxxSuccessfulEvent : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid? Target { get; } - public DiseaseVaccineComponent Vaxx { get; } - public DiseaseCarrierComponent Carrier { get; } - public TargetVaxxSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseVaccineComponent vaxx, DiseaseCarrierComponent carrier) - { - User = user; - Target = target; - Vaxx = vaxx; - Carrier = carrier; - } + Vaccinate(carrier, component.Disease); + EntityManager.DeleteEntity(uid); + args.Handled = true; } } - /// - /// This event is fired by chems - /// and other brute-force rather than - /// specific cures. It will roll the dice to attempt - /// to cure each disease on the target - /// + /// + /// This event is fired by chems + /// and other brute-force rather than + /// specific cures. It will roll the dice to attempt + /// to cure each disease on the target + /// public sealed class CureDiseaseAttemptEvent : EntityEventArgs { public float CureChance { get; } diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index 0e7ed86331..9afc18faa3 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -16,6 +16,7 @@ using Content.Shared.Database; using Content.Shared.Destructible; using Content.Shared.Disposal; using Content.Shared.Disposal.Components; +using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -28,7 +29,6 @@ using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; -using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Map.Components; @@ -78,7 +78,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems SubscribeLocalEvent>(AddClimbInsideVerb); // Units - SubscribeLocalEvent(DoInsertDisposalUnit); + SubscribeLocalEvent(OnDoAfter); //UI SubscribeLocalEvent(OnUiButtonPressed); @@ -155,28 +155,21 @@ namespace Content.Server.Disposal.Unit.EntitySystems { _handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands); _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}"); - AfterInsert(uid, component, args.Using.Value); + AfterInsert(uid, component, args.Using.Value, args.User); } }; args.Verbs.Add(insertVerb); } - private void DoInsertDisposalUnit(DoInsertDisposalUnitEvent ev) + private void OnDoAfter(EntityUid uid, DisposalUnitComponent component, DoAfterEvent args) { - var toInsert = ev.ToInsert; - - if (!TryComp(ev.Unit, out DisposalUnitComponent? unit)) - { + if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null) return; - } - if (!unit.Container.Insert(toInsert)) - return; - if (ev.User != null) - _adminLogger.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(ev.User.Value):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(ev.Unit)}"); - AfterInsert(ev.Unit, unit, toInsert); + AfterInsert(uid, component, args.Args.Target.Value, args.Args.User); + + args.Handled = true; } public void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, DisposalUnitComponent? disposal = null) @@ -188,7 +181,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems return; _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user):player} inserted {ToPrettyString(toInsert)} into {ToPrettyString(uid)}"); - AfterInsert(uid, disposal, toInsert); + AfterInsert(uid, disposal, toInsert, user); } public override void Update(float frameTime) @@ -273,7 +266,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems } _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Used)} into {ToPrettyString(uid)}"); - AfterInsert(uid, component, args.Used); + AfterInsert(uid, component, args.Used, args.User); args.Handled = true; } @@ -454,6 +447,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems TryComp(ejectedId, out PhysicsComponent? body)) { // TODO: We need to use a specific collision method (which sloth hasn't coded yet) for actual bounds overlaps. + // TODO: Come do this sloth :^) // Check for itemcomp as we won't just block the disposal unit "sleeping" for something it can't collide with anyway. if (!HasComp(ejectedId) && _lookup.GetWorldAABB(ejectedId).Intersects(disposalsBounds!.Value)) @@ -485,24 +479,22 @@ namespace Content.Server.Disposal.Unit.EntitySystems return false; var delay = userId == toInsertId ? unit.EntryDelay : unit.DraggedEntryDelay; - var ev = new DoInsertDisposalUnitEvent(userId, toInsertId, unitId); if (delay <= 0 || userId == null) { - DoInsertDisposalUnit(ev); + AfterInsert(unitId, unit, toInsertId, userId); return true; } // Can't check if our target AND disposals moves currently so we'll just check target. // if you really want to check if disposals moves then add a predicate. - var doAfterArgs = new DoAfterEventArgs(userId.Value, delay, default, toInsertId) + var doAfterArgs = new DoAfterEventArgs(userId.Value, delay, target:toInsertId, used:unitId) { BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, BreakOnUserMove = true, NeedHand = false, - BroadcastFinishedEvent = ev }; _doAfterSystem.DoAfter(doAfterArgs); @@ -727,8 +719,14 @@ namespace Content.Server.Disposal.Unit.EntitySystems }, component.AutomaticEngageToken.Token); } - public void AfterInsert(EntityUid uid, DisposalUnitComponent component, EntityUid inserted) + public void AfterInsert(EntityUid uid, DisposalUnitComponent component, EntityUid inserted, EntityUid? user = null) { + if (!component.Container.Insert(inserted)) + return; + + if (user != inserted && user != null) + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(user.Value):player} inserted {ToPrettyString(inserted)} into {ToPrettyString(uid)}"); + TryQueueEngage(uid, component); if (TryComp(inserted, out ActorComponent? actor)) diff --git a/Content.Server/DoAfter/DoAfter.cs b/Content.Server/DoAfter/DoAfter.cs deleted file mode 100644 index 61f4e009ae..0000000000 --- a/Content.Server/DoAfter/DoAfter.cs +++ /dev/null @@ -1,209 +0,0 @@ -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; } - -#pragma warning disable RA0004 - public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running; -#pragma warning restore RA0004 - - // 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; - } - } - } - - if (EventArgs.DistanceThreshold != null) - { - var xformQuery = entityManager.GetEntityQuery(); - TransformComponent? userXform = null; - - // Check user distance to target AND used entities. - if (EventArgs.Target != null && !EventArgs.User.Equals(EventArgs.Target)) - { - //recalculate Target location in case Target has also moved - var targetCoordinates = xformQuery.GetComponent(EventArgs.Target.Value).Coordinates; - userXform ??= xformQuery.GetComponent(EventArgs.User); - if (!userXform.Coordinates.InRange(entityManager, targetCoordinates, EventArgs.DistanceThreshold.Value)) - return true; - } - - if (EventArgs.Used != null) - { - var targetCoordinates = xformQuery.GetComponent(EventArgs.Used.Value).Coordinates; - userXform ??= xformQuery.GetComponent(EventArgs.User); - if (!userXform.Coordinates.InRange(entityManager, targetCoordinates, EventArgs.DistanceThreshold.Value)) - return true; - } - } - - return false; - } - - private bool TryPostCheck() - { - return EventArgs.PostCheck?.Invoke() != false; - } - - private bool IsFinished() - { - if (Elapsed <= EventArgs.Delay) - { - return false; - } - - return true; - } - } -} diff --git a/Content.Server/DoAfter/DoAfterComponent.cs b/Content.Server/DoAfter/DoAfterComponent.cs deleted file mode 100644 index adf5e382a9..0000000000 --- a/Content.Server/DoAfter/DoAfterComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.DoAfter; - -namespace Content.Server.DoAfter -{ - [RegisterComponent, Access(typeof(DoAfterSystem))] - public sealed class DoAfterComponent : SharedDoAfterComponent - { - public readonly Dictionary DoAfters = new(); - - // So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates) - // we'll just send them the index. Doesn't matter if it wraps around. - public byte RunningIndex; - } - - /// - /// Added to entities that are currently performing any doafters. - /// - [RegisterComponent] - public sealed class ActiveDoAfterComponent : Component {} -} diff --git a/Content.Server/DoAfter/DoAfterEventArgs.cs b/Content.Server/DoAfter/DoAfterEventArgs.cs deleted file mode 100644 index 7ba46c64ea..0000000000 --- a/Content.Server/DoAfter/DoAfterEventArgs.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Threading; -using Content.Shared.FixedPoint; -using Robust.Shared.Utility; - -namespace Content.Server.DoAfter -{ - public sealed class DoAfterEventArgs - { - /// - /// The entity invoking do_after - /// - public EntityUid User { get; } - - /// - /// How long does the do_after require to complete - /// - public float Delay { get; } - - /// - /// Applicable target (if relevant) - /// - public EntityUid? Target { get; } - - /// - /// Entity used by the User on the Target. - /// - public EntityUid? Used { get; set; } - - /// - /// Manually cancel the do_after so it no longer runs - /// - public CancellationToken CancelToken { get; } - - // Break the chains - /// - /// Whether we need to keep our active hand as is (i.e. can't change hand or change item). - /// This also covers requiring the hand to be free (if applicable). - /// - public bool NeedHand { get; set; } - - /// - /// If do_after stops when the user moves - /// - public bool BreakOnUserMove { get; set; } - - /// - /// If do_after stops when the target moves (if there is a target) - /// - public bool BreakOnTargetMove { get; set; } - - /// - /// Threshold for user and target movement - /// - public float MovementThreshold { get; set; } - - public bool BreakOnDamage { get; set; } - - /// - /// Threshold for user damage - /// - public FixedPoint2 DamageThreshold { get; set; } - public bool BreakOnStun { get; set; } - - /// - /// Threshold for distance user from the used OR target entities. - /// - public float? DistanceThreshold { get; set; } - - /// - /// Requires a function call once at the end (like InRangeUnobstructed). - /// - /// - /// Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client. - /// - public Func? PostCheck { get; set; } = null; - - /// - /// Additional conditions that need to be met. Return false to cancel. - /// - public Func? ExtraCheck { get; set; } - - /// - /// Event to be raised directed to the entity when the DoAfter is cancelled. - /// - public object? UserCancelledEvent { get; set; } - - /// - /// Event to be raised directed to the entity when the DoAfter is finished successfully. - /// - public object? UserFinishedEvent { get; set; } - - /// - /// Event to be raised directed to the entity when the DoAfter is cancelled. - /// - public object? UsedCancelledEvent { get; set; } - - /// - /// Event to be raised directed to the entity when the DoAfter is finished successfully. - /// - public object? UsedFinishedEvent { get; set; } - - /// - /// Event to be raised directed to the entity when the DoAfter is cancelled. - /// - public object? TargetCancelledEvent { get; set; } - - /// - /// Event to be raised directed to the entity when the DoAfter is finished successfully. - /// - public object? TargetFinishedEvent { get; set; } - - /// - /// Event to be broadcast when the DoAfter is cancelled. - /// - public object? BroadcastCancelledEvent { get; set; } - - /// - /// Event to be broadcast when the DoAfter is finished successfully. - /// - public object? BroadcastFinishedEvent { get; set; } - - public DoAfterEventArgs( - EntityUid user, - float delay, - CancellationToken cancelToken = default, - EntityUid? target = null, - EntityUid? used = null) - { - User = user; - Delay = delay; - CancelToken = cancelToken; - Target = target; - Used = used; - MovementThreshold = 0.1f; - DamageThreshold = 1.0; - - if (Target == null) - { - DebugTools.Assert(!BreakOnTargetMove); - BreakOnTargetMove = false; - } - } - } -} diff --git a/Content.Server/DoAfter/DoAfterSystem.cs b/Content.Server/DoAfter/DoAfterSystem.cs index 84ad75092a..ad0d8d27d8 100644 --- a/Content.Server/DoAfter/DoAfterSystem.cs +++ b/Content.Server/DoAfter/DoAfterSystem.cs @@ -1,227 +1,11 @@ -using System.Linq; -using System.Threading.Tasks; -using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Mobs; using JetBrains.Annotations; -using Robust.Shared.GameStates; -namespace Content.Server.DoAfter +namespace Content.Server.DoAfter; + +[UsedImplicitly] +public sealed class DoAfterSystem : SharedDoAfterSystem { - [UsedImplicitly] - public sealed class DoAfterSystem : EntitySystem - { - // We cache these lists as to not allocate them every update tick... - private readonly Queue _cancelled = new(); - private readonly Queue _finished = new(); - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnDamage); - SubscribeLocalEvent(OnStateChanged); - SubscribeLocalEvent(OnDoAfterGetState); - } - - public void Add(DoAfterComponent component, DoAfter doAfter) - { - component.DoAfters.Add(doAfter, component.RunningIndex); - EnsureComp(component.Owner); - component.RunningIndex++; - Dirty(component); - } - - public void Cancelled(DoAfterComponent component, DoAfter doAfter) - { - if (!component.DoAfters.TryGetValue(doAfter, out var index)) - return; - - component.DoAfters.Remove(doAfter); - - if (component.DoAfters.Count == 0) - { - RemComp(component.Owner); - } - - RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index)); - } - - /// - /// Call when the particular DoAfter is finished. - /// Client should be tracking this independently. - /// - public void Finished(DoAfterComponent component, DoAfter doAfter) - { - if (!component.DoAfters.ContainsKey(doAfter)) - return; - - component.DoAfters.Remove(doAfter); - - if (component.DoAfters.Count == 0) - { - RemComp(component.Owner); - } - } - - private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args) - { - var toAdd = new List(component.DoAfters.Count); - - foreach (var (doAfter, _) in component.DoAfters) - { - // THE ALMIGHTY PYRAMID - var clientDoAfter = new ClientDoAfter( - component.DoAfters[doAfter], - doAfter.UserGrid, - doAfter.TargetGrid, - doAfter.StartTime, - doAfter.EventArgs.Delay, - doAfter.EventArgs.BreakOnUserMove, - doAfter.EventArgs.BreakOnTargetMove, - doAfter.EventArgs.MovementThreshold, - doAfter.EventArgs.DamageThreshold, - doAfter.EventArgs.Target); - - toAdd.Add(clientDoAfter); - } - - args.State = new DoAfterComponentState(toAdd); - } - - private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args) - { - if (args.NewMobState == MobState.Alive) - return; - - foreach (var (doAfter, _) in component.DoAfters) - { - doAfter.Cancel(); - } - } - - /// - /// Cancels DoAfter if it breaks on damage and it meets the threshold - /// - /// - /// The EntityUID of the user - /// - /// - /// - public void OnDamage(EntityUid _, DoAfterComponent component, DamageChangedEvent args) - { - if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null) - return; - - foreach (var (doAfter, _) in component.DoAfters) - { - if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold) - { - doAfter.Cancel(); - } - } - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - foreach (var (_, comp) in EntityManager.EntityQuery()) - { - foreach (var (doAfter, _) in comp.DoAfters.ToArray()) - { - doAfter.Run(frameTime, EntityManager); - - switch (doAfter.Status) - { - case DoAfterStatus.Running: - break; - case DoAfterStatus.Cancelled: - _cancelled.Enqueue(doAfter); - break; - case DoAfterStatus.Finished: - _finished.Enqueue(doAfter); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - while (_cancelled.TryDequeue(out var doAfter)) - { - Cancelled(comp, doAfter); - - if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserCancelledEvent != null) - RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserCancelledEvent, false); - - if (doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedCancelledEvent != null) - RaiseLocalEvent(used, doAfter.EventArgs.UsedCancelledEvent); - - if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetCancelledEvent != null) - RaiseLocalEvent(target, doAfter.EventArgs.TargetCancelledEvent, false); - - if(doAfter.EventArgs.BroadcastCancelledEvent != null) - RaiseLocalEvent(doAfter.EventArgs.BroadcastCancelledEvent); - } - - while (_finished.TryDequeue(out var doAfter)) - { - Finished(comp, doAfter); - - if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserFinishedEvent != null) - RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserFinishedEvent, false); - - if(doAfter.EventArgs.Used is {} used && EntityManager.EntityExists(used) && doAfter.EventArgs.UsedFinishedEvent != null) - RaiseLocalEvent(used, doAfter.EventArgs.UsedFinishedEvent); - - if(doAfter.EventArgs.Target is {} target && EntityManager.EntityExists(target) && doAfter.EventArgs.TargetFinishedEvent != null) - RaiseLocalEvent(target, doAfter.EventArgs.TargetFinishedEvent, false); - - if(doAfter.EventArgs.BroadcastFinishedEvent != null) - RaiseLocalEvent(doAfter.EventArgs.BroadcastFinishedEvent); - } - } - } - - /// - /// Tasks that are delayed until the specified time has passed - /// These can be potentially cancelled by the user moving or when other things happen. - /// - /// - /// - public async Task WaitDoAfter(DoAfterEventArgs eventArgs) - { - var doAfter = CreateDoAfter(eventArgs); - - await doAfter.AsTask; - - return doAfter.Status; - } - - /// - /// Creates a DoAfter without waiting for it to finish. You can use events with this. - /// These can be potentially cancelled by the user moving or when other things happen. - /// - /// - public void DoAfter(DoAfterEventArgs eventArgs) - { - CreateDoAfter(eventArgs); - } - - private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs) - { - // Setup - var doAfter = new DoAfter(eventArgs, EntityManager); - // Caller's gonna be responsible for this I guess - var doAfterComponent = Comp(eventArgs.User); - Add(doAfterComponent, doAfter); - return doAfter; - } - } - - public enum DoAfterStatus : byte - { - Running, - Cancelled, - Finished, - } } diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index cac9876d27..bb01665b8d 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -2,8 +2,6 @@ using Content.Server.Access; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Construction; -using Content.Server.Doors.Components; -using Content.Server.Tools; using Content.Server.Tools.Systems; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; @@ -17,9 +15,9 @@ using Content.Shared.Tools.Components; using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.Player; using System.Linq; using Content.Server.Power.EntitySystems; +using Content.Shared.Tools; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -31,7 +29,7 @@ public sealed class DoorSystem : SharedDoorSystem [Dependency] private readonly AirlockSystem _airlock = default!; [Dependency] private readonly AirtightSystem _airtightSystem = default!; [Dependency] private readonly ConstructionSystem _constructionSystem = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; public override void Initialize() @@ -197,9 +195,8 @@ public sealed class DoorSystem : SharedDoorSystem RaiseLocalEvent(target, modEv, false); door.BeingPried = true; - _toolSystem.UseTool(tool, user, target, 0f, modEv.PryTimeModifier * door.PryTime, door.PryingQuality, - new PryFinishedEvent(), new PryCancelledEvent(), target); - + var toolEvData = new ToolEventData(new PryFinishedEvent(), cancelledEv: new PryCancelledEvent(),targetEntity: target); + _toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, new[] { door.PryingQuality }, toolEvData); return true; // we might not actually succeeded, but a do-after has started } diff --git a/Content.Server/Dragon/Components/DragonComponent.cs b/Content.Server/Dragon/Components/DragonComponent.cs index 490d132a08..2222b64a15 100644 --- a/Content.Server/Dragon/Components/DragonComponent.cs +++ b/Content.Server/Dragon/Components/DragonComponent.cs @@ -102,8 +102,6 @@ namespace Content.Server.Dragon Params = AudioParams.Default.WithVolume(3f), }; - public CancellationTokenSource? CancelToken; - [ViewVariables(VVAccess.ReadWrite), DataField("devourWhitelist")] public EntityWhitelist? DevourWhitelist = new() { diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index adc6ab92da..f4d3809b85 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -9,7 +9,6 @@ using System.Threading; using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; -using Content.Server.Humanoid; using Content.Server.NPC; using Content.Shared.Damage; using Content.Shared.Dragon; @@ -20,6 +19,7 @@ using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Random; using Content.Server.NPC.Systems; +using Content.Shared.DoAfter; using Content.Shared.Humanoid; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; @@ -59,13 +59,12 @@ namespace Content.Server.Dragon SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnDragonDevourComplete); SubscribeLocalEvent(OnDevourAction); SubscribeLocalEvent(OnDragonRift); SubscribeLocalEvent(OnDragonMove); - SubscribeLocalEvent(OnDragonStructureDevourComplete); - SubscribeLocalEvent(OnDragonDevourCancelled); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnRiftShutdown); @@ -76,6 +75,30 @@ namespace Content.Server.Dragon SubscribeLocalEvent(OnRiftRoundEnd); } + private void OnDoAfter(EntityUid uid, DragonComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate); + + //Humanoid devours allow dragon to get eggs, corpses included + if (HasComp(args.Args.Target)) + { + ichorInjection.ScaleSolution(0.5f); + component.DragonStomach.Insert(args.Args.Target.Value); + _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection); + } + + //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow. + //If it's not human, it must be a structure + else if (args.Args.Target != null) + EntityManager.QueueDeleteEntity(args.Args.Target.Value); + + if (component.SoundDevour != null) + _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params); + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -292,39 +315,6 @@ namespace Content.Server.Dragon } } - private void OnDragonDevourCancelled(EntityUid uid, DragonComponent component, DragonDevourCancelledEvent args) - { - component.CancelToken = null; - } - - private void OnDragonDevourComplete(EntityUid uid, DragonComponent component, DragonDevourComplete args) - { - component.CancelToken = null; - var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate); - - //Humanoid devours allow dragon to get eggs, corpses included - if (!EntityManager.HasComponent(args.Target)) - { - ichorInjection.ScaleSolution(0.5f); - } - - _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection); - component.DragonStomach.Insert(args.Target); - - if (component.SoundDevour != null) - _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params); - } - - private void OnDragonStructureDevourComplete(EntityUid uid, DragonComponent component, DragonStructureDevourComplete args) - { - component.CancelToken = null; - //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow. - EntityManager.QueueDeleteEntity(args.Target); - - if (component.SoundDevour != null) - _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params); - } - private void Roar(DragonComponent component) { if (component.SoundRoar != null) @@ -351,12 +341,8 @@ namespace Content.Server.Dragon /// private void OnDevourAction(EntityUid uid, DragonComponent component, DragonDevourActionEvent args) { - if (component.CancelToken != null || - args.Handled || - component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true) - { + if (args.Handled || component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true) return; - } args.Handled = true; var target = args.Target; @@ -368,12 +354,9 @@ namespace Content.Server.Dragon { case MobState.Critical: case MobState.Dead: - component.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, component.CancelToken.Token, target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, target:target) { - UserFinishedEvent = new DragonDevourComplete(uid, target), - UserCancelledEvent = new DragonDevourCancelledEvent(), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -392,42 +375,12 @@ namespace Content.Server.Dragon if (component.SoundStructureDevour != null) _audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params); - component.CancelToken = new CancellationTokenSource(); - - _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, component.CancelToken.Token, target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, target:target) { - UserFinishedEvent = new DragonStructureDevourComplete(uid, target), - UserCancelledEvent = new DragonDevourCancelledEvent(), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, }); } - - private sealed class DragonDevourComplete : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid Target { get; } - - public DragonDevourComplete(EntityUid user, EntityUid target) - { - User = user; - Target = target; - } - } - - private sealed class DragonStructureDevourComplete : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid Target { get; } - - public DragonStructureDevourComplete(EntityUid user, EntityUid target) - { - User = user; - Target = target; - } - } - - private sealed class DragonDevourCancelledEvent : EntityEventArgs {} } } diff --git a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs index b4fd425103..79d25f3520 100644 --- a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs +++ b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs @@ -1,5 +1,6 @@ using Content.Server.DoAfter; using Content.Server.Engineering.Components; +using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Verbs; using JetBrains.Annotations; diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index 3fc4a40d0f..988b72ae88 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Coordinates.Helpers; using Content.Server.DoAfter; using Content.Server.Engineering.Components; using Content.Server.Stack; +using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Maps; using Content.Shared.Stacks; diff --git a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs index 0bfde38c78..dbb45f1f9b 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs @@ -1,6 +1,7 @@ using System.Threading; using Content.Server.DoAfter; using Content.Shared.Alert; +using Content.Shared.DoAfter; using Content.Shared.Ensnaring.Components; using Content.Shared.IdentityManagement; using Content.Shared.StepTrigger.Systems; @@ -27,7 +28,7 @@ public sealed partial class EnsnareableSystem return; if (ensnared.IsEnsnared) - ForceFree(component); + ForceFree(uid, component); } private void AttemptStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggerAttemptEvent args) @@ -37,7 +38,7 @@ public sealed partial class EnsnareableSystem private void OnStepTrigger(EntityUid uid, EnsnaringComponent component, ref StepTriggeredEvent args) { - TryEnsnare(args.Tripper, component); + TryEnsnare(args.Tripper, uid, component); } private void OnThrowHit(EntityUid uid, EnsnaringComponent component, ThrowDoHitEvent args) @@ -45,26 +46,27 @@ public sealed partial class EnsnareableSystem if (!component.CanThrowTrigger) return; - TryEnsnare(args.Target, component); + TryEnsnare(args.Target, uid, component); } /// /// Used where you want to try to ensnare an entity with the /// /// The entity that will be ensnared + /// The entity that is used to ensnare /// The ensnaring component - public void TryEnsnare(EntityUid target, EnsnaringComponent component) + public void TryEnsnare(EntityUid target, EntityUid ensnare, EnsnaringComponent component) { //Don't do anything if they don't have the ensnareable component. if (!TryComp(target, out var ensnareable)) return; component.Ensnared = target; - ensnareable.Container.Insert(component.Owner); + ensnareable.Container.Insert(ensnare); ensnareable.IsEnsnared = true; Dirty(ensnareable); - UpdateAlert(ensnareable); + UpdateAlert(ensnare, ensnareable); var ev = new EnsnareEvent(component.WalkSpeed, component.SprintSpeed); RaiseLocalEvent(target, ev); } @@ -73,18 +75,14 @@ public sealed partial class EnsnareableSystem /// Used where you want to try to free an entity with the /// /// The entity that will be free + /// The entity used to ensnare /// The ensnaring component - public void TryFree(EntityUid target, EnsnaringComponent component, EntityUid? user = null) + public void TryFree(EntityUid target, EntityUid ensnare, EnsnaringComponent component, EntityUid? user = null) { //Don't do anything if they don't have the ensnareable component. if (!HasComp(target)) return; - if (component.CancelToken != null) - return; - - component.CancelToken = new CancellationTokenSource(); - var isOwner = !(user != null && target != user); var freeTime = isOwner ? component.BreakoutTime : component.FreeTime; bool breakOnMove; @@ -94,55 +92,47 @@ public sealed partial class EnsnareableSystem else breakOnMove = true; - var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, component.CancelToken.Token, target) + var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, target: target, used:ensnare) { BreakOnUserMove = breakOnMove, BreakOnTargetMove = breakOnMove, BreakOnDamage = false, BreakOnStun = true, - NeedHand = true, - TargetFinishedEvent = new FreeEnsnareDoAfterComplete(component.Owner), - TargetCancelledEvent = new FreeEnsnareDoAfterCancel(component.Owner), + NeedHand = true }; _doAfter.DoAfter(doAfterEventArgs); if (isOwner) - _popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", component.Owner)), target, target); + _popup.PopupEntity(Loc.GetString("ensnare-component-try-free", ("ensnare", ensnare)), target, target); if (!isOwner && user != null) - { - _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", component.Owner), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value); - } + _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value); } /// /// Used to force free someone for things like if the is removed /// - public void ForceFree(EnsnaringComponent component) + public void ForceFree(EntityUid ensnare, EnsnaringComponent component) { if (!TryComp(component.Ensnared, out var ensnareable)) return; - ensnareable.Container.ForceRemove(component.Owner); + ensnareable.Container.ForceRemove(ensnare); ensnareable.IsEnsnared = false; Dirty(ensnareable); component.Ensnared = null; - UpdateAlert(ensnareable); + UpdateAlert(ensnare, ensnareable); var ev = new EnsnareRemoveEvent(); - RaiseLocalEvent(component.Owner, ev); + RaiseLocalEvent(ensnare, ev); } - public void UpdateAlert(EnsnareableComponent component) + public void UpdateAlert(EntityUid ensnare, EnsnareableComponent component) { if (!component.IsEnsnared) - { - _alerts.ClearAlert(component.Owner, AlertType.Ensnared); - } + _alerts.ClearAlert(ensnare, AlertType.Ensnared); else - { - _alerts.ShowAlert(component.Owner, AlertType.Ensnared); - } + _alerts.ShowAlert(ensnare, AlertType.Ensnared); } } diff --git a/Content.Server/Ensnaring/EnsnareableSystem.cs b/Content.Server/Ensnaring/EnsnareableSystem.cs index cbb025459f..d883cdb418 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Popups; +using Content.Shared.DoAfter; using Content.Shared.Ensnaring; using Content.Shared.Ensnaring.Components; using Content.Shared.Popups; @@ -19,43 +20,36 @@ public sealed partial class EnsnareableSystem : SharedEnsnareableSystem InitializeEnsnaring(); SubscribeLocalEvent(OnEnsnareableInit); - SubscribeLocalEvent(OnFreeComplete); - SubscribeLocalEvent(OnFreeFail); + SubscribeLocalEvent(OnDoAfter); } private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args) { - component.Container = _container.EnsureContainer(component.Owner, "ensnare"); + component.Container = _container.EnsureContainer(uid, "ensnare"); } - private void OnFreeComplete(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterComplete args) + private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEvent args) { - if (!TryComp(args.EnsnaringEntity, out var ensnaring)) + if (args.Handled || !TryComp(args.Args.Used, out var ensnaring)) return; - component.Container.Remove(args.EnsnaringEntity); + if (args.Cancelled) + { + _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.Args.Used)), uid, uid, PopupType.Large); + return; + } + + component.Container.Remove(args.Args.Used.Value); component.IsEnsnared = false; Dirty(component); ensnaring.Ensnared = null; - _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.EnsnaringEntity)), - uid, uid, PopupType.Large); + _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, uid, PopupType.Large); - UpdateAlert(component); + UpdateAlert(args.Args.Used.Value, component); var ev = new EnsnareRemoveEvent(); RaiseLocalEvent(uid, ev); - ensnaring.CancelToken = null; - } - - private void OnFreeFail(EntityUid uid, EnsnareableComponent component, FreeEnsnareDoAfterCancel args) - { - if (!TryComp(args.EnsnaringEntity, out var ensnaring)) - return; - - ensnaring.CancelToken = null; - - _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-fail", ("ensnare", args.EnsnaringEntity)), - uid, uid, PopupType.Large); + args.Handled = true; } } diff --git a/Content.Server/Eye/Blinding/EyeProtection/EyeProtectionSystem.cs b/Content.Server/Eye/Blinding/EyeProtection/EyeProtectionSystem.cs index 80e1de68a8..4872cd359a 100644 --- a/Content.Server/Eye/Blinding/EyeProtection/EyeProtectionSystem.cs +++ b/Content.Server/Eye/Blinding/EyeProtection/EyeProtectionSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Clothing.Components; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Server.Tools; +using Content.Shared.Tools.Components; namespace Content.Server.Eye.Blinding.EyeProtection { diff --git a/Content.Server/Fluids/Components/SpillableComponent.cs b/Content.Server/Fluids/Components/SpillableComponent.cs index 2d1f23d5c8..eb12121566 100644 --- a/Content.Server/Fluids/Components/SpillableComponent.cs +++ b/Content.Server/Fluids/Components/SpillableComponent.cs @@ -1,5 +1,3 @@ -using System.Threading; - namespace Content.Server.Fluids.Components; [RegisterComponent] @@ -17,6 +15,4 @@ public sealed class SpillableComponent : Component [DataField("spillDelay")] public float? SpillDelay; - - public CancellationTokenSource? CancelToken; } diff --git a/Content.Server/Fluids/EntitySystems/MoppingSystem.cs b/Content.Server/Fluids/EntitySystems/MoppingSystem.cs index 44939ad969..6272279205 100644 --- a/Content.Server/Fluids/EntitySystems/MoppingSystem.cs +++ b/Content.Server/Fluids/EntitySystems/MoppingSystem.cs @@ -5,6 +5,7 @@ using Content.Server.DoAfter; using Content.Server.Fluids.Components; using Content.Server.Popups; using Content.Shared.Chemistry.Components; +using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Fluids; using Content.Shared.Interaction; @@ -34,9 +35,7 @@ public sealed class MoppingSystem : SharedMoppingSystem base.Initialize(); SubscribeLocalEvent(OnAbsorbentInit); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnAbsorbentSolutionChange); - SubscribeLocalEvent(OnTransferCancelled); - SubscribeLocalEvent(OnTransferComplete); + SubscribeLocalEvent>(OnDoAfter); } private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args) @@ -256,63 +255,38 @@ public sealed class MoppingSystem : SharedMoppingSystem if (!component.InteractingEntities.Add(target)) return; - var doAfterArgs = new DoAfterEventArgs(user, delay, target: target) + var aborbantData = new AbsorbantData(targetSolution, msg, sfx, transferAmount); + + var doAfterArgs = new DoAfterEventArgs(user, delay, target: target, used:used) { BreakOnUserMove = true, BreakOnStun = true, BreakOnDamage = true, - MovementThreshold = 0.2f, - BroadcastCancelledEvent = new TransferCancelledEvent(target, component), - BroadcastFinishedEvent = new TransferCompleteEvent(used, target, component, targetSolution, msg, sfx, transferAmount) + MovementThreshold = 0.2f }; - _doAfterSystem.DoAfter(doAfterArgs); + _doAfterSystem.DoAfter(doAfterArgs, aborbantData); } - private void OnTransferComplete(TransferCompleteEvent ev) + private void OnDoAfter(EntityUid uid, AbsorbentComponent component, DoAfterEvent args) { - _audio.PlayPvs(ev.Sound, ev.Tool); - _popups.PopupEntity(ev.Message, ev.Tool); - _solutionSystem.TryTransferSolution(ev.Target, ev.Tool, ev.TargetSolution, AbsorbentComponent.SolutionName, ev.TransferAmount); - ev.Component.InteractingEntities.Remove(ev.Target); + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + _audio.PlayPvs(args.AdditionalData.Sound, uid); + _popups.PopupEntity(Loc.GetString(args.AdditionalData.Message, ("target", args.Args.Target.Value), ("used", uid)), uid); + _solutionSystem.TryTransferSolution(args.Args.Target.Value, uid, args.AdditionalData.TargetSolution, + AbsorbentComponent.SolutionName, args.AdditionalData.TransferAmount); + component.InteractingEntities.Remove(args.Args.Target.Value); + + args.Handled = true; } - private void OnTransferCancelled(TransferCancelledEvent ev) + private record struct AbsorbantData(string TargetSolution, string Message, SoundSpecifier Sound, FixedPoint2 TransferAmount) { - ev.Component.InteractingEntities.Remove(ev.Target); - } -} - -public sealed class TransferCompleteEvent : EntityEventArgs -{ - public readonly EntityUid Tool; - public readonly EntityUid Target; - public readonly AbsorbentComponent Component; - public readonly string TargetSolution; - public readonly string Message; - public readonly SoundSpecifier Sound; - public readonly FixedPoint2 TransferAmount; - - public TransferCompleteEvent(EntityUid tool, EntityUid target, AbsorbentComponent component, string targetSolution, string message, SoundSpecifier sound, FixedPoint2 transferAmount) - { - Tool = tool; - Target = target; - Component = component; - TargetSolution = targetSolution; - Message = Loc.GetString(message, ("target", target), ("used", tool)); - Sound = sound; - TransferAmount = transferAmount; - } -} - -public sealed class TransferCancelledEvent : EntityEventArgs -{ - public readonly EntityUid Target; - public readonly AbsorbentComponent Component; - - public TransferCancelledEvent(EntityUid target, AbsorbentComponent component) - { - Target = target; - Component = component; + public readonly string TargetSolution = TargetSolution; + public readonly string Message = Message; + public readonly SoundSpecifier Sound = Sound; + public readonly FixedPoint2 TransferAmount = TransferAmount; } } diff --git a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs index 32fbe0e769..32e6dcbaa4 100644 --- a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs @@ -16,7 +16,7 @@ using Robust.Shared.Map; using Robust.Shared.Prototypes; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading; +using Content.Shared.DoAfter; namespace Content.Server.Fluids.EntitySystems; @@ -38,8 +38,7 @@ public sealed class SpillableSystem : EntitySystem SubscribeLocalEvent>(AddSpillVerb); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnSpikeOverflow); - SubscribeLocalEvent(OnSpillFinished); - SubscribeLocalEvent(OnSpillCancelled); + SubscribeLocalEvent(OnDoAfter); } private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args) @@ -142,20 +141,14 @@ public sealed class SpillableSystem : EntitySystem { verb.Act = () => { - if (component.CancelToken == null) + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, target:uid) { - component.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, component.CancelToken.Token, component.Owner) - { - BreakOnTargetMove = true, - BreakOnUserMove = true, - BreakOnDamage = true, - BreakOnStun = true, - NeedHand = true, - TargetFinishedEvent = new SpillFinishedEvent(args.User, component.Owner, solution), - TargetCancelledEvent = new SpillCancelledEvent(component.Owner) - }); - } + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = true + }); }; } verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately. @@ -263,46 +256,19 @@ public sealed class SpillableSystem : EntitySystem return puddleComponent; } - private void OnSpillFinished(EntityUid uid, SpillableComponent component, SpillFinishedEvent ev) + private void OnDoAfter(EntityUid uid, SpillableComponent component, DoAfterEvent args) { - component.CancelToken = null; - - //solution gone by other means before doafter completes - if (ev.Solution == null || ev.Solution.Volume == 0) + if (args.Handled || args.Cancelled || args.Args.Target == null) return; - var puddleSolution = _solutionContainerSystem.SplitSolution(uid, - ev.Solution, ev.Solution.Volume); + //solution gone by other means before doafter completes + if (!_solutionContainerSystem.TryGetDrainableSolution(uid, out var solution) || solution.Volume == 0) + return; - SpillAt(puddleSolution, Transform(component.Owner).Coordinates, "PuddleSmear"); - } + var puddleSolution = _solutionContainerSystem.SplitSolution(uid, solution, solution.Volume); - private void OnSpillCancelled(EntityUid uid, SpillableComponent component, SpillCancelledEvent ev) - { - component.CancelToken = null; - } + SpillAt(puddleSolution, Transform(uid).Coordinates, "PuddleSmear"); - internal sealed class SpillFinishedEvent : EntityEventArgs - { - public SpillFinishedEvent(EntityUid user, EntityUid spillable, Solution solution) - { - User = user; - Spillable = spillable; - Solution = solution; - } - - public EntityUid User { get; } - public EntityUid Spillable { get; } - public Solution Solution { get; } - } - - private sealed class SpillCancelledEvent : EntityEventArgs - { - public EntityUid Spillable; - - public SpillCancelledEvent(EntityUid spillable) - { - Spillable = spillable; - } + args.Handled = true; } } diff --git a/Content.Server/Forensics/Components/ForensicPadComponent.cs b/Content.Server/Forensics/Components/ForensicPadComponent.cs index ffdf7b7683..8476d17a0d 100644 --- a/Content.Server/Forensics/Components/ForensicPadComponent.cs +++ b/Content.Server/Forensics/Components/ForensicPadComponent.cs @@ -1,5 +1,3 @@ -using System.Threading; - namespace Content.Server.Forensics { /// @@ -8,8 +6,6 @@ namespace Content.Server.Forensics [RegisterComponent] public sealed class ForensicPadComponent : Component { - public CancellationTokenSource? CancelToken; - [DataField("scanDelay")] public float ScanDelay = 3.0f; diff --git a/Content.Server/Forensics/Systems/ForensicPadSystem.cs b/Content.Server/Forensics/Systems/ForensicPadSystem.cs index 1ac262c05f..e7f2355056 100644 --- a/Content.Server/Forensics/Systems/ForensicPadSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicPadSystem.cs @@ -1,11 +1,11 @@ -using System.Threading; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Server.DoAfter; using Content.Server.Popups; +using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; -using Robust.Shared.Player; +using Robust.Shared.Serialization; namespace Content.Server.Forensics { @@ -16,7 +16,6 @@ namespace Content.Server.Forensics { [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; public override void Initialize() @@ -24,8 +23,7 @@ namespace Content.Server.Forensics base.Initialize(); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnTargetPadSuccessful); - SubscribeLocalEvent(OnPadCancelled); + SubscribeLocalEvent>(OnDoAfter); } private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args) @@ -44,7 +42,7 @@ namespace Content.Server.Forensics private void OnAfterInteract(EntityUid uid, ForensicPadComponent component, AfterInteractEvent args) { - if (component.CancelToken != null || !args.CanReach || args.Target == null) + if (!args.CanReach || args.Target == null) return; if (HasComp(args.Target)) @@ -71,74 +69,58 @@ namespace Content.Server.Forensics _popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.Target.Value, args.User); _popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", Identity.Entity(args.User, EntityManager))), args.Target.Value, args.Target.Value); } - StartScan(args.User, args.Target.Value, component, fingerprint.Fingerprint); + StartScan(uid, args.User, args.Target.Value, component, fingerprint.Fingerprint); return; } if (TryComp(args.Target, out var fiber)) - StartScan(args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial))); + StartScan(uid, args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial))); } - private void StartScan(EntityUid user, EntityUid target, ForensicPadComponent pad, string sample) + private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample) { - pad.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, pad.ScanDelay, pad.CancelToken.Token, target: target) + var padData = new ForensicPadData(sample); + + var doAfterEventArgs = new DoAfterEventArgs(user, pad.ScanDelay, target: target, used: used) { - BroadcastFinishedEvent = new TargetPadSuccessfulEvent(user, target, pad.Owner, sample), - BroadcastCancelledEvent = new PadCancelledEvent(pad.Owner), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, NeedHand = true - }); + }; + + _doAfterSystem.DoAfter(doAfterEventArgs, padData); } - /// - /// When the forensic pad is successfully used, take their fingerprint sample and flag the pad as used. - /// - private void OnTargetPadSuccessful(TargetPadSuccessfulEvent ev) + private void OnDoAfter(EntityUid uid, ForensicPadComponent component, DoAfterEvent args) { - if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component)) - return; - - if (HasComp(ev.Target)) - MetaData(component.Owner).EntityName = Loc.GetString("forensic-pad-fingerprint-name", ("entity", ev.Target)); - else - MetaData(component.Owner).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", ev.Target)); - - component.CancelToken = null; - component.Sample = ev.Sample; - component.Used = true; - } - private void OnPadCancelled(PadCancelledEvent ev) - { - if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component)) - return; - component.CancelToken = null; - } - - private sealed class PadCancelledEvent : EntityEventArgs - { - public EntityUid Pad; - - public PadCancelledEvent(EntityUid pad) + if (args.Handled + || args.Cancelled + || !EntityManager.TryGetComponent(args.Args.Used, out ForensicPadComponent? padComponent)) { - Pad = pad; + return; } + + if (args.Args.Target != null) + { + if (HasComp(args.Args.Target)) + MetaData(uid).EntityName = Loc.GetString("forensic-pad-fingerprint-name", ("entity", args.Args.Target)); + else + MetaData(uid).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", args.Args.Target)); + } + + padComponent.Sample = args.AdditionalData.Sample; + padComponent.Used = true; + + args.Handled = true; } - private sealed class TargetPadSuccessfulEvent : EntityEventArgs + private sealed class ForensicPadData { - public EntityUid User; - public EntityUid Target; - public EntityUid Pad; - public string Sample = string.Empty; + public string Sample; - public TargetPadSuccessfulEvent(EntityUid user, EntityUid target, EntityUid pad, string sample) + public ForensicPadData(string sample) { - User = user; - Target = target; - Pad = pad; Sample = sample; } } diff --git a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs index 146414b8d3..1f7df3d82f 100644 --- a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs @@ -1,14 +1,13 @@ using System.Linq; using System.Text; // todo: remove this stinky LINQy -using System.Threading; using Robust.Server.GameObjects; using Robust.Shared.Audio; -using Robust.Shared.Player; using Robust.Shared.Timing; using Content.Server.DoAfter; using Content.Server.Paper; using Content.Server.Popups; using Content.Server.UserInterface; +using Content.Shared.DoAfter; using Content.Shared.Forensics; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; @@ -40,8 +39,7 @@ namespace Content.Server.Forensics SubscribeLocalEvent>(OnUtilityVerb); SubscribeLocalEvent(OnPrint); SubscribeLocalEvent(OnClear); - SubscribeLocalEvent(OnTargetScanSuccessful); - SubscribeLocalEvent(OnScanCancelled); + SubscribeLocalEvent(OnDoAfter); } private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component) @@ -54,40 +52,35 @@ namespace Content.Server.Forensics component.PrintReadyAt); if (!_uiSystem.TrySetUiState(uid, ForensicScannerUiKey.Key, state)) - { _sawmill.Warning($"{ToPrettyString(uid)} was unable to set UI state."); - } } - private void OnScanCancelled(ScanCancelledEvent ev) + private void OnDoAfter(EntityUid uid, ForensicScannerComponent component, DoAfterEvent args) { - if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner)) + if (args.Handled || args.Cancelled) return; - scanner.CancelToken = null; - } - - private void OnTargetScanSuccessful(TargetScanSuccessfulEvent ev) - { - if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner)) + if (!EntityManager.TryGetComponent(uid, out ForensicScannerComponent? scanner)) return; - scanner.CancelToken = null; + if (args.Args.Target != null) + { + if (!TryComp(args.Args.Target, out var forensics)) + { + scanner.Fingerprints = new(); + scanner.Fibers = new(); + } - if (!TryComp(ev.Target, out var forensics)) - { - scanner.Fingerprints = new(); - scanner.Fibers = new(); - } - else - { - scanner.Fingerprints = forensics.Fingerprints.ToList(); - scanner.Fibers = forensics.Fibers.ToList(); + else + { + scanner.Fingerprints = forensics.Fingerprints.ToList(); + scanner.Fibers = forensics.Fibers.ToList(); + } + + scanner.LastScannedName = MetaData(args.Args.Target.Value).EntityName; } - scanner.LastScannedName = MetaData(ev.Target).EntityName; - - OpenUserInterface(ev.User, scanner); + OpenUserInterface(args.Args.User, scanner); } /// @@ -95,11 +88,8 @@ namespace Content.Server.Forensics /// private void StartScan(EntityUid uid, ForensicScannerComponent component, EntityUid user, EntityUid target) { - component.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, component.CancelToken.Token, target: target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, target: target, used: uid) { - BroadcastFinishedEvent = new TargetScanSuccessfulEvent(user, target, component.Owner), - BroadcastCancelledEvent = new ScanCancelledEvent(component.Owner), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -244,28 +234,5 @@ namespace Content.Server.Forensics UpdateUserInterface(uid, component); } - - private sealed class ScanCancelledEvent : EntityEventArgs - { - public EntityUid Scanner; - - public ScanCancelledEvent(EntityUid scanner) - { - Scanner = scanner; - } - } - - private sealed class TargetScanSuccessfulEvent : EntityEventArgs - { - public EntityUid User; - public EntityUid Target; - public EntityUid Scanner; - public TargetScanSuccessfulEvent(EntityUid user, EntityUid target, EntityUid scanner) - { - User = user; - Target = target; - Scanner = scanner; - } - } } } diff --git a/Content.Server/Gatherable/Components/GatheringToolComponent.cs b/Content.Server/Gatherable/Components/GatheringToolComponent.cs index c4e506e538..9bf5bb2018 100644 --- a/Content.Server/Gatherable/Components/GatheringToolComponent.cs +++ b/Content.Server/Gatherable/Components/GatheringToolComponent.cs @@ -32,6 +32,6 @@ namespace Content.Server.Gatherable.Components public int MaxGatheringEntities = 1; [ViewVariables] - public readonly Dictionary GatheringEntities = new(); + public readonly List GatheringEntities = new(); } } diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs index f574bd5dd9..e82a5130d3 100644 --- a/Content.Server/Gatherable/GatherableSystem.cs +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Destructible; using Content.Server.DoAfter; using Content.Server.Gatherable.Components; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.Destructible; using Content.Shared.EntityList; using Content.Shared.Interaction; @@ -27,15 +28,12 @@ public sealed class GatherableSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnDoafterCancel); - SubscribeLocalEvent(OnDoafterSuccess); + SubscribeLocalEvent(OnDoAfter); } private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args) { - if (!TryComp(args.Used, out var tool) || - component.ToolWhitelist?.IsValid(args.Used) == false || - tool.GatheringEntities.TryGetValue(uid, out var cancelToken)) + if (!TryComp(args.Used, out var tool) || component.ToolWhitelist?.IsValid(args.Used) == false) return; // Can't gather too many entities at once. @@ -46,38 +44,39 @@ public sealed class GatherableSystem : EntitySystem var damageTime = (damageRequired / tool.Damage.Total).Float(); damageTime = Math.Max(1f, damageTime); - cancelToken = new CancellationTokenSource(); - tool.GatheringEntities[uid] = cancelToken; - - var doAfter = new DoAfterEventArgs(args.User, damageTime, cancelToken.Token, uid) + var doAfter = new DoAfterEventArgs(args.User, damageTime, target: uid, used: args.Used) { BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, BreakOnUserMove = true, MovementThreshold = 0.25f, - BroadcastCancelledEvent = new GatheringDoafterCancel { Tool = args.Used, Resource = uid }, - TargetFinishedEvent = new GatheringDoafterSuccess { Tool = args.Used, Resource = uid, Player = args.User } }; _doAfterSystem.DoAfter(doAfter); } - private void OnDoafterSuccess(EntityUid uid, GatherableComponent component, GatheringDoafterSuccess ev) + private void OnDoAfter(EntityUid uid, GatherableComponent component, DoAfterEvent args) { - if (!TryComp(ev.Tool, out GatheringToolComponent? tool)) + if(!TryComp(args.Args.Used, out var tool) || args.Args.Target == null) return; + if (args.Handled || args.Cancelled) + { + tool.GatheringEntities.Remove(args.Args.Target.Value); + return; + } + // Complete the gathering process - _destructible.DestroyEntity(uid); - _audio.PlayPvs(tool.GatheringSound, ev.Resource); - tool.GatheringEntities.Remove(ev.Resource); + _destructible.DestroyEntity(args.Args.Target.Value); + _audio.PlayPvs(tool.GatheringSound, args.Args.Target.Value); + tool.GatheringEntities.Remove(args.Args.Target.Value); // Spawn the loot! if (component.MappedLoot == null) return; - var playerPos = Transform(ev.Player).MapPosition; + var playerPos = Transform(args.Args.User).MapPosition; foreach (var (tag, table) in component.MappedLoot) { @@ -91,27 +90,7 @@ public sealed class GatherableSystem : EntitySystem var spawnPos = playerPos.Offset(_random.NextVector2(0.3f)); Spawn(spawnLoot[0], spawnPos); } - } - - private void OnDoafterCancel(GatheringDoafterCancel ev) - { - if (!TryComp(ev.Tool, out var tool)) - return; - - tool.GatheringEntities.Remove(ev.Resource); - } - - private sealed class GatheringDoafterCancel : EntityEventArgs - { - public EntityUid Tool; - public EntityUid Resource; - } - - private sealed class GatheringDoafterSuccess : EntityEventArgs - { - public EntityUid Tool; - public EntityUid Resource; - public EntityUid Player; + args.Handled = true; } } diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs index bad11e1da8..b1ca676e04 100644 --- a/Content.Server/Guardian/GuardianSystem.cs +++ b/Content.Server/Guardian/GuardianSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Popups; using Content.Shared.Actions; using Content.Shared.Audio; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; @@ -10,7 +11,6 @@ using Content.Shared.Interaction.Events; using Content.Shared.Mobs; using Content.Shared.Popups; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -27,6 +27,7 @@ namespace Content.Server.Guardian [Dependency] private readonly DamageableSystem _damageSystem = default!; [Dependency] private readonly SharedActionsSystem _actionSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { @@ -34,8 +35,7 @@ namespace Content.Server.Guardian SubscribeLocalEvent(OnCreatorUse); SubscribeLocalEvent(OnCreatorInteract); SubscribeLocalEvent(OnCreatorExamine); - SubscribeLocalEvent(OnCreatorInject); - SubscribeLocalEvent(OnCreatorCancelled); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnGuardianMove); SubscribeLocalEvent(OnGuardianDamaged); @@ -58,7 +58,7 @@ namespace Content.Server.Guardian return; if (component.HostedGuardian != null) - ToggleGuardian(component); + ToggleGuardian(uid, component); args.Handled = true; } @@ -67,19 +67,18 @@ namespace Content.Server.Guardian { var host = component.Host; - if (!TryComp(host, out var hostComponent)) return; - - if (LifeStage(host) >= EntityLifeStage.MapInitialized) + if (!TryComp(host, out var hostComponent) || LifeStage(host) >= EntityLifeStage.MapInitialized) return; - RetractGuardian(hostComponent, component); + RetractGuardian(host, hostComponent, uid, component); } private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args) { var host = component.Host; - if (!HasComp(host)) return; + if (!HasComp(host)) + return; _popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, host); } @@ -92,7 +91,9 @@ namespace Content.Server.Guardian private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args) { - if (component.HostedGuardian == null) return; + if (component.HostedGuardian == null) + return; + EntityManager.QueueDeleteEntity(component.HostedGuardian.Value); _actionSystem.RemoveAction(uid, component.Action); } @@ -107,19 +108,15 @@ namespace Content.Server.Guardian args.Cancel(); } - public void ToggleGuardian(GuardianHostComponent hostComponent) + public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent) { - if (hostComponent.HostedGuardian == null || - !TryComp(hostComponent.HostedGuardian, out GuardianComponent? guardianComponent)) return; + if (hostComponent.HostedGuardian == null || !TryComp(hostComponent.HostedGuardian, out var guardianComponent)) + return; if (guardianComponent.GuardianLoose) - { - RetractGuardian(hostComponent, guardianComponent); - } + RetractGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent); else - { - ReleaseGuardian(hostComponent, guardianComponent); - } + ReleaseGuardian(user, hostComponent, hostComponent.HostedGuardian.Value, guardianComponent); } /// @@ -127,24 +124,22 @@ namespace Content.Server.Guardian /// private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args) { - if (args.Handled) return; - args.Handled = true; - UseCreator(args.User, args.User, component); + if (args.Handled) + return; + + //args.Handled = true; + UseCreator(args.User, args.User, uid, component); } private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args) { - if (args.Handled || args.Target == null || !args.CanReach) return; - args.Handled = true; - UseCreator(args.User, args.Target.Value, component); - } + if (args.Handled || args.Target == null || !args.CanReach) + return; - private void OnCreatorCancelled(GuardianCreatorInjectCancelledEvent ev) - { - ev.Component.Injecting = false; + //args.Handled = true; + UseCreator(args.User, args.Target.Value, uid, component); } - - private void UseCreator(EntityUid user, EntityUid target, GuardianCreatorComponent component) + private void UseCreator(EntityUid user, EntityUid target, EntityUid injector, GuardianCreatorComponent component) { if (component.Used) { @@ -166,55 +161,52 @@ namespace Content.Server.Guardian return; } - if (component.Injecting) return; + if (component.Injecting) + return; component.Injecting = true; - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target, used: injector) { - BroadcastFinishedEvent = new GuardianCreatorInjectedEvent(user, target, component), - BroadcastCancelledEvent = new GuardianCreatorInjectCancelledEvent(target, component), BreakOnTargetMove = true, - BreakOnUserMove = true, + BreakOnUserMove = true }); } - private void OnCreatorInject(GuardianCreatorInjectedEvent ev) + private void OnDoAfter(EntityUid uid, GuardianCreatorComponent component, DoAfterEvent args) { - var comp = ev.Component; + if (args.Handled || args.Args.Target == null) + return; - if (comp.Deleted || - comp.Used || - !_handsSystem.IsHolding(ev.User, comp.Owner, out _) || - HasComp(ev.Target)) + if (args.Cancelled || component.Deleted || component.Used || !_handsSystem.IsHolding(args.Args.User, uid, out _) || HasComp(args.Args.Target)) { - comp.Injecting = false; + component.Injecting = false; return; } - var hostXform = EntityManager.GetComponent(ev.Target); - var host = EntityManager.EnsureComponent(ev.Target); + var hostXform = Transform(args.Args.Target.Value); + var host = EnsureComp(args.Args.Target.Value); // Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess. - var guardian = EntityManager.SpawnEntity(comp.GuardianProto, hostXform.MapPosition); + var guardian = Spawn(component.GuardianProto, hostXform.MapPosition); host.GuardianContainer.Insert(guardian); host.HostedGuardian = guardian; - if (TryComp(guardian, out GuardianComponent? guardianComponent)) + if (TryComp(guardian, out var guardianComp)) { - guardianComponent.Host = ev.Target; - - SoundSystem.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(ev.Target), ev.Target); - - _popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, ev.Target); + guardianComp.Host = args.Args.Target.Value; + _audio.Play("/Audio/Effects/guardian_inject.ogg", Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true); + _popupSystem.PopupEntity(Loc.GetString("guardian-created"), args.Args.Target.Value, args.Args.Target.Value); // Exhaust the activator - comp.Used = true; + component.Used = true; } else { Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}"); EntityManager.QueueDeleteEntity(guardian); } + + args.Handled = true; } /// @@ -222,16 +214,18 @@ namespace Content.Server.Guardian /// private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args) { - if (component.HostedGuardian == null) return; + if (component.HostedGuardian == null) + return; if (args.NewMobState == MobState.Critical) { _popupSystem.PopupEntity(Loc.GetString("guardian-critical-warn"), component.HostedGuardian.Value, component.HostedGuardian.Value); - SoundSystem.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value); + _audio.Play("/Audio/Effects/guardian_warn.ogg", Filter.Pvs(component.HostedGuardian.Value), component.HostedGuardian.Value, true); } else if (args.NewMobState == MobState.Dead) { - SoundSystem.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.20f)); + //TODO: Replace WithVariation with datafield + _audio.Play("/Audio/Voice/Human/malescream_guardian.ogg", Filter.Pvs(uid), uid, true, AudioHelpers.WithVariation(0.20f)); EntityManager.RemoveComponent(uid); } } @@ -241,7 +235,8 @@ namespace Content.Server.Guardian /// private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args) { - if (args.DamageDelta == null) return; + if (args.DamageDelta == null) + return; _damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare, origin: args.Origin); _popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, component.Host); @@ -254,9 +249,7 @@ namespace Content.Server.Guardian private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args) { if (component.Used) - { args.PushMarkup(Loc.GetString("guardian-activator-empty-examine")); - } } /// @@ -266,7 +259,10 @@ namespace Content.Server.Guardian { if (component.HostedGuardian == null || !TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) || - !guardianComponent.GuardianLoose) return; + !guardianComponent.GuardianLoose) + { + return; + } CheckGuardianMove(uid, component.HostedGuardian.Value, component); } @@ -276,7 +272,8 @@ namespace Content.Server.Guardian /// private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args) { - if (!component.GuardianLoose) return; + if (!component.GuardianLoose) + return; CheckGuardianMove(component.Host, uid, guardianComponent: component); } @@ -298,78 +295,51 @@ namespace Content.Server.Guardian return; } - if (!guardianComponent.GuardianLoose) return; + if (!guardianComponent.GuardianLoose) + return; if (!guardianXform.Coordinates.InRange(EntityManager, hostXform.Coordinates, guardianComponent.DistanceAllowed)) - { - RetractGuardian(hostComponent, guardianComponent); - } + RetractGuardian(hostUid, hostComponent, guardianUid, guardianComponent); } - private bool CanRelease(GuardianHostComponent host, GuardianComponent guardian) + private bool CanRelease(EntityUid guardian) { - return HasComp(guardian.Owner); + return HasComp(guardian); } - private void ReleaseGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent) + private void ReleaseGuardian(EntityUid host, GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent) { if (guardianComponent.GuardianLoose) { - DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner)); + DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian)); return; } - if (!CanRelease(hostComponent, guardianComponent)) + if (!CanRelease(guardian)) { - _popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), hostComponent.Owner, hostComponent.Owner); + _popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), host, host); return; } - DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner)); - hostComponent.GuardianContainer.Remove(guardianComponent.Owner); - DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner)); + DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian)); + hostComponent.GuardianContainer.Remove(guardian); + DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardian)); guardianComponent.GuardianLoose = true; } - private void RetractGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent) + private void RetractGuardian(EntityUid host,GuardianHostComponent hostComponent, EntityUid guardian, GuardianComponent guardianComponent) { if (!guardianComponent.GuardianLoose) { - DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner)); + DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian)); return; } - hostComponent.GuardianContainer.Insert(guardianComponent.Owner); - DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner)); - _popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), hostComponent.Owner); + hostComponent.GuardianContainer.Insert(guardian); + DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardian)); + _popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), host); guardianComponent.GuardianLoose = false; } - - private sealed class GuardianCreatorInjectedEvent : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid Target { get; } - public GuardianCreatorComponent Component { get; } - - public GuardianCreatorInjectedEvent(EntityUid user, EntityUid target, GuardianCreatorComponent component) - { - User = user; - Target = target; - Component = component; - } - } - - private sealed class GuardianCreatorInjectCancelledEvent : EntityEventArgs - { - public EntityUid Target { get; } - public GuardianCreatorComponent Component { get; } - - public GuardianCreatorInjectCancelledEvent(EntityUid target, GuardianCreatorComponent component) - { - Target = target; - Component = component; - } - } } } diff --git a/Content.Server/Implants/ImplanterSystem.cs b/Content.Server/Implants/ImplanterSystem.cs index 369070041a..451f4d074e 100644 --- a/Content.Server/Implants/ImplanterSystem.cs +++ b/Content.Server/Implants/ImplanterSystem.cs @@ -2,6 +2,7 @@ using System.Threading; using Content.Server.DoAfter; using Content.Server.Guardian; using Content.Server.Popups; +using Content.Shared.DoAfter; using Content.Shared.Hands; using Content.Shared.IdentityManagement; using Content.Shared.Implants; @@ -11,7 +12,6 @@ using Content.Shared.Mobs.Components; using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.GameStates; -using Robust.Shared.Player; namespace Content.Server.Implants; @@ -30,9 +30,8 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem SubscribeLocalEvent(OnImplanterAfterInteract); SubscribeLocalEvent(OnImplanterGetState); - SubscribeLocalEvent(OnImplantAttemptSuccess); - SubscribeLocalEvent(OnDrawAttemptSuccess); - SubscribeLocalEvent(OnImplantAttemptFail); + SubscribeLocalEvent>(OnImplant); + SubscribeLocalEvent>(OnDraw); } private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args) @@ -40,12 +39,6 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem if (args.Target == null || !args.CanReach || args.Handled) return; - if (component.CancelToken != null) - { - args.Handled = true; - return; - } - //Simplemobs and regular mobs should be injectable, but only regular mobs have mind. //So just don't implant/draw anything that isn't living or is a guardian //TODO: Rework a bit when surgery is in to work with implant cases @@ -84,6 +77,9 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem /// The implanter being used public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter) { + if (component.CancelToken != null) + return; + _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user); var userName = Identity.Entity(user, EntityManager); @@ -92,15 +88,16 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem component.CancelToken?.Cancel(); component.CancelToken = new CancellationTokenSource(); - _doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token, target, implanter) + var implantEvent = new ImplantEvent(); + + _doAfter.DoAfter(new DoAfterEventArgs(user, component.ImplantTime, component.CancelToken.Token,target:target, used:implanter) { BreakOnUserMove = true, BreakOnTargetMove = true, BreakOnDamage = true, BreakOnStun = true, - UsedFinishedEvent = new ImplanterImplantCompleteEvent(implanter, target), - UserCancelledEvent = new ImplanterCancelledEvent() - }); + NeedHand = true + }, implantEvent); } /// @@ -118,15 +115,16 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem component.CancelToken?.Cancel(); component.CancelToken = new CancellationTokenSource(); - _doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, component.CancelToken.Token, target ,implanter) + var drawEvent = new DrawEvent(); + + _doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, target:target,used:implanter) { BreakOnUserMove = true, BreakOnTargetMove = true, BreakOnDamage = true, BreakOnStun = true, - UsedFinishedEvent = new ImplanterDrawCompleteEvent(implanter, user, target), - UsedCancelledEvent = new ImplanterCancelledEvent() - }); + NeedHand = true + }, drawEvent); } private void OnImplanterGetState(EntityUid uid, ImplanterComponent component, ref ComponentGetState args) @@ -134,55 +132,47 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem args.State = new ImplanterComponentState(component.CurrentMode, component.ImplantOnly); } - private void OnImplantAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterImplantCompleteEvent args) + private void OnImplant(EntityUid uid, ImplanterComponent component, DoAfterEvent args) { - component.CancelToken?.Cancel(); - component.CancelToken = null; - Implant(args.Implanter, args.Target, component); - } - - private void OnDrawAttemptSuccess(EntityUid uid, ImplanterComponent component, ImplanterDrawCompleteEvent args) - { - component.CancelToken?.Cancel(); - component.CancelToken = null; - Draw(args.Implanter, args.User, args.Target, component); - } - - private void OnImplantAttemptFail(EntityUid uid, ImplanterComponent component, ImplanterCancelledEvent args) - { - component.CancelToken?.Cancel(); - component.CancelToken = null; - } - - private sealed class ImplanterImplantCompleteEvent : EntityEventArgs - { - public EntityUid Implanter; - public EntityUid Target; - - public ImplanterImplantCompleteEvent(EntityUid implanter, EntityUid target) + if (args.Cancelled) { - Implanter = implanter; - Target = target; + component.CancelToken = null; + return; } + + if (args.Handled || args.Args.Target == null || args.Args.Used == null) + return; + + Implant(args.Args.Used.Value, args.Args.Target.Value, component); + + args.Handled = true; + component.CancelToken = null; } - private sealed class ImplanterCancelledEvent : EntityEventArgs + private void OnDraw(EntityUid uid, ImplanterComponent component, DoAfterEvent args) { - - } - - private sealed class ImplanterDrawCompleteEvent : EntityEventArgs - { - public EntityUid Implanter; - public EntityUid User; - public EntityUid Target; - - public ImplanterDrawCompleteEvent(EntityUid implanter, EntityUid user, EntityUid target) + if (args.Cancelled) { - Implanter = implanter; - User = user; - Target = target; + component.CancelToken = null; + return; } + + if (args.Handled || args.Args.Used == null || args.Args.Target == null) + return; + + Draw(args.Args.Used.Value, args.Args.User, args.Args.Target.Value, component); + + args.Handled = true; + component.CancelToken = null; } + private sealed class ImplantEvent : EntityEventArgs + { + + } + + private sealed class DrawEvent : EntityEventArgs + { + + } } diff --git a/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs b/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs index 78d4a01deb..19514310ba 100644 --- a/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs @@ -1,14 +1,13 @@ using Content.Server.Administration.Logs; using Content.Server.DoAfter; using Content.Server.Kitchen.Components; -using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Nutrition.Components; -using Robust.Shared.Audio; using Robust.Shared.Player; using Content.Shared.Storage; using Robust.Shared.Random; @@ -18,7 +17,6 @@ using Content.Shared.Kitchen; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; -using Robust.Server.GameObjects; namespace Content.Server.Kitchen.EntitySystems { @@ -30,6 +28,7 @@ namespace Content.Server.Kitchen.EntitySystems [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { @@ -40,8 +39,7 @@ namespace Content.Server.Kitchen.EntitySystems SubscribeLocalEvent(OnDragDrop); //DoAfter - SubscribeLocalEvent(OnSpikingFinished); - SubscribeLocalEvent(OnSpikingFail); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnSuicide); @@ -56,35 +54,32 @@ namespace Content.Server.Kitchen.EntitySystems private void OnSuicide(EntityUid uid, KitchenSpikeComponent component, SuicideEvent args) { - if (args.Handled) return; + if (args.Handled) + return; args.SetHandled(SuicideKind.Piercing); var victim = args.Victim; var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other", ("victim", victim)); - victim.PopupMessageOtherClients(othersMessage); + _popupSystem.PopupEntity(othersMessage, victim); var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self"); - victim.PopupMessage(selfMessage); + _popupSystem.PopupEntity(selfMessage, victim, victim); } - private void OnSpikingFail(EntityUid uid, KitchenSpikeComponent component, SpikingFailEvent args) + private void OnDoAfter(EntityUid uid, KitchenSpikeComponent component, DoAfterEvent args) { - component.InUse = false; + if (args.Args.Target == null) + return; - if (EntityManager.TryGetComponent(args.VictimUid, out var butcherable)) - butcherable.BeingButchered = false; - } - - private void OnSpikingFinished(EntityUid uid, KitchenSpikeComponent component, SpikingFinishedEvent args) - { - component.InUse = false; - - if (EntityManager.TryGetComponent(args.VictimUid, out var butcherable)) + if (TryComp(args.Args.Target.Value, out var butcherable)) butcherable.BeingButchered = false; - if (Spikeable(uid, args.UserUid, args.VictimUid, component, butcherable)) - { - Spike(uid, args.UserUid, args.VictimUid, component); - } + if (args.Handled || args.Cancelled) + return; + + if (Spikeable(uid, args.Args.User, args.Args.Target.Value, component, butcherable)) + Spike(uid, args.Args.User, args.Args.Target.Value, component); + + args.Handled = true; } private void OnDragDrop(EntityUid uid, KitchenSpikeComponent component, ref DragDropTargetEvent args) @@ -96,8 +91,8 @@ namespace Content.Server.Kitchen.EntitySystems if (Spikeable(uid, args.User, args.Dragged, component)) TrySpike(uid, args.User, args.Dragged, component); - } + private void OnInteractHand(EntityUid uid, KitchenSpikeComponent component, InteractHandEvent args) { if (args.Handled) @@ -142,7 +137,7 @@ namespace Content.Server.Kitchen.EntitySystems // TODO: Need to be able to leave them on the spike to do DoT, see ss13. EntityManager.QueueDeleteEntity(victimUid); - SoundSystem.Play(component.SpikeSound.GetSound(), Filter.Pvs(uid), uid); + _audio.Play(component.SpikeSound, Filter.Pvs(uid), uid, true); } private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used, @@ -164,9 +159,7 @@ namespace Content.Server.Kitchen.EntitySystems Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim)); if (component.PrototypesToSpawn.Count != 0) - { _popupSystem.PopupEntity(component.MeatSource1p, uid, user, PopupType.MediumCaution); - } else { UpdateAppearance(uid, null, component); @@ -243,42 +236,18 @@ namespace Content.Server.Kitchen.EntitySystems butcherable.BeingButchered = true; component.InUse = true; - var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, default, uid) + var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, target:victimUid, used:uid) { BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, - NeedHand = true, - TargetFinishedEvent = new SpikingFinishedEvent(userUid, victimUid), - TargetCancelledEvent = new SpikingFailEvent(victimUid) + NeedHand = true }; _doAfter.DoAfter(doAfterArgs); return true; } - - private sealed class SpikingFinishedEvent : EntityEventArgs - { - public EntityUid VictimUid; - public EntityUid UserUid; - - public SpikingFinishedEvent(EntityUid userUid, EntityUid victimUid) - { - UserUid = userUid; - VictimUid = victimUid; - } - } - - private sealed class SpikingFailEvent : EntityEventArgs - { - public EntityUid VictimUid; - - public SpikingFailEvent(EntityUid victimUid) - { - VictimUid = victimUid; - } - } } } diff --git a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs index 4aceecd419..05894146fc 100644 --- a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Systems; +using Content.Server.Body.Systems; using Content.Server.DoAfter; using Content.Server.Kitchen.Components; using Content.Shared.Body.Components; @@ -8,10 +8,10 @@ using Content.Shared.Popups; using Content.Shared.Storage; using Content.Shared.Verbs; using Content.Shared.Destructible; +using Content.Shared.DoAfter; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Robust.Server.Containers; -using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server.Kitchen.EntitySystems; @@ -31,8 +31,7 @@ public sealed class SharpSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnDoafterComplete); - SubscribeLocalEvent(OnDoafterCancelled); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent>(OnGetInteractionVerbs); } @@ -63,64 +62,56 @@ public sealed class SharpSystem : EntitySystem return; var doAfter = - new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, default, target) + new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, target: target, used: knife) { BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, - NeedHand = true, - BroadcastFinishedEvent = new SharpButcherDoafterComplete { User = user, Entity = target, Sharp = knife }, - BroadcastCancelledEvent = new SharpButcherDoafterCancelled { Entity = target, Sharp = knife } + NeedHand = true }; _doAfterSystem.DoAfter(doAfter); } - private void OnDoafterComplete(SharpButcherDoafterComplete ev) + private void OnDoAfter(EntityUid uid, SharpComponent component, DoAfterEvent args) { - if (!TryComp(ev.Entity, out var butcher)) + if (args.Handled || args.Cancelled || !TryComp(args.Args.Target, out var butcher)) return; - if (!TryComp(ev.Sharp, out var sharp)) - return; + component.Butchering.Remove(args.Args.Target.Value); - sharp.Butchering.Remove(ev.Entity); - - if (_containerSystem.IsEntityInContainer(ev.Entity)) + if (_containerSystem.IsEntityInContainer(args.Args.Target.Value)) + { + args.Handled = true; return; + } var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom); - var coords = Transform(ev.Entity).MapPosition; - EntityUid popupEnt = default; + var coords = Transform(args.Args.Target.Value).MapPosition; + EntityUid popupEnt = default!; foreach (var proto in spawnEntities) { // distribute the spawned items randomly in a small radius around the origin popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f))); } - var hasBody = TryComp(ev.Entity, out var body); + var hasBody = TryComp(args.Args.Target.Value, out var body); // only show a big popup when butchering living things. var popupType = PopupType.Small; if (hasBody) popupType = PopupType.LargeCaution; - _popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", ev.Entity), ("knife", ev.Sharp)), - popupEnt, ev.User, popupType); + _popupSystem.PopupEntity(Loc.GetString("butcherable-knife-butchered-success", ("target", args.Args.Target.Value), ("knife", uid)), + popupEnt, args.Args.User, popupType); if (hasBody) - _bodySystem.GibBody(body!.Owner, body: body); + _bodySystem.GibBody(args.Args.Target.Value, body: body); - _destructibleSystem.DestroyEntity(ev.Entity); - } + _destructibleSystem.DestroyEntity(args.Args.Target.Value); - private void OnDoafterCancelled(SharpButcherDoafterCancelled ev) - { - if (!TryComp(ev.Sharp, out var sharp)) - return; - - sharp.Butchering.Remove(ev.Entity); + args.Handled = true; } private void OnGetInteractionVerbs(EntityUid uid, ButcherableComponent component, GetVerbsEvent args) @@ -165,16 +156,3 @@ public sealed class SharpSystem : EntitySystem args.Verbs.Add(verb); } } - -public sealed class SharpButcherDoafterComplete : EntityEventArgs -{ - public EntityUid Entity; - public EntityUid Sharp; - public EntityUid User; -} - -public sealed class SharpButcherDoafterCancelled : EntityEventArgs -{ - public EntityUid Entity; - public EntityUid Sharp; -} diff --git a/Content.Server/Light/Components/PoweredLightComponent.cs b/Content.Server/Light/Components/PoweredLightComponent.cs index 0088aad335..be44d20f54 100644 --- a/Content.Server/Light/Components/PoweredLightComponent.cs +++ b/Content.Server/Light/Components/PoweredLightComponent.cs @@ -64,8 +64,6 @@ namespace Content.Server.Light.Components [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] public string TogglePort = "Toggle"; - public CancellationTokenSource? CancelToken; - /// /// How long it takes to eject a bulb from this /// diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index cfcbf97bc1..bef74772a7 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -21,7 +21,7 @@ using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Timing; -using System.Threading; +using Content.Shared.DoAfter; namespace Content.Server.Light.EntitySystems { @@ -40,6 +40,7 @@ namespace Content.Server.Light.EntitySystems [Dependency] private readonly SignalLinkerSystem _signalSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2); @@ -61,8 +62,7 @@ namespace Content.Server.Light.EntitySystems SubscribeLocalEvent(OnPowerChanged); - SubscribeLocalEvent(OnEjectBulbComplete); - SubscribeLocalEvent(OnEjectBulbCancelled); + SubscribeLocalEvent(OnDoAfter); } private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) @@ -95,9 +95,6 @@ namespace Content.Server.Light.EntitySystems if (args.Handled) return; - if (light.CancelToken != null) - return; - // check if light has bulb to eject var bulbUid = GetBulb(uid, light); if (bulbUid == null) @@ -122,10 +119,9 @@ namespace Content.Server.Light.EntitySystems var damage = _damageableSystem.TryChangeDamage(userUid, light.Damage, origin: userUid); if (damage != null) - _adminLogger.Add(LogType.Damaged, - $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage"); + _adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.Total:damage} damage"); - SoundSystem.Play(light.BurnHandSound.GetSound(), Filter.Pvs(uid), uid); + _audio.Play(light.BurnHandSound, Filter.Pvs(uid), uid, true); args.Handled = true; return; @@ -141,22 +137,11 @@ namespace Content.Server.Light.EntitySystems } // removing a working bulb, so require a delay - light.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, light.CancelToken.Token, uid) + _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, target:uid) { BreakOnUserMove = true, BreakOnDamage = true, - BreakOnStun = true, - TargetFinishedEvent = new EjectBulbCompleteEvent() - { - Component = light, - User = userUid, - Target = uid, - }, - TargetCancelledEvent = new EjectBulbCancelledEvent() - { - Component = light, - } + BreakOnStun = true }); args.Handled = true; @@ -287,7 +272,7 @@ namespace Content.Server.Light.EntitySystems if (time > light.LastThunk + ThunkDelay) { light.LastThunk = time; - SoundSystem.Play(light.TurnOnSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-10f)); + _audio.Play(light.TurnOnSound, Filter.Pvs(uid), uid, true, AudioParams.Default.WithVolume(-10f)); } } else @@ -358,7 +343,7 @@ namespace Content.Server.Light.EntitySystems light.IsBlinking = isNowBlinking; - if (!EntityManager.TryGetComponent(light.Owner, out AppearanceComponent? appearance)) + if (!EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) return; _appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance); @@ -427,27 +412,14 @@ namespace Content.Server.Light.EntitySystems UpdateLight(uid, light); } - private void OnEjectBulbComplete(EntityUid uid, PoweredLightComponent component, EjectBulbCompleteEvent args) + private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args) { - args.Component.CancelToken = null; - EjectBulb(args.Target, args.User, args.Component); - } + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; - private static void OnEjectBulbCancelled(EntityUid uid, PoweredLightComponent component, EjectBulbCancelledEvent args) - { - args.Component.CancelToken = null; - } + EjectBulb(args.Args.Target.Value, args.Args.User, component); - private sealed class EjectBulbCompleteEvent : EntityEventArgs - { - public PoweredLightComponent Component { get; init; } = default!; - public EntityUid User { get; init; } - public EntityUid Target { get; init; } - } - - private sealed class EjectBulbCancelledEvent : EntityEventArgs - { - public PoweredLightComponent Component { get; init; } = default!; + args.Handled = true; } } } diff --git a/Content.Server/Magic/Components/SpellbookComponent.cs b/Content.Server/Magic/Components/SpellbookComponent.cs index 334921dc29..0432336dfd 100644 --- a/Content.Server/Magic/Components/SpellbookComponent.cs +++ b/Content.Server/Magic/Components/SpellbookComponent.cs @@ -1,5 +1,4 @@ -using System.Threading; -using Content.Shared.Actions.ActionTypes; +using Content.Shared.Actions.ActionTypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; namespace Content.Server.Magic; @@ -30,6 +29,4 @@ public sealed class SpellbookComponent : Component [DataField("learnTime")] public float LearnTime = .75f; - - public CancellationTokenSource? CancelToken; } diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index 79fbfaf1d5..5cdb549b7a 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -9,6 +9,7 @@ using Content.Server.Weapons.Ranged.Systems; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using Content.Shared.Body.Components; +using Content.Shared.DoAfter; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; using Content.Shared.Interaction.Events; @@ -53,8 +54,7 @@ public sealed class MagicSystem : EntitySystem SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(OnLearnComplete); - SubscribeLocalEvent(OnLearnCancel); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnInstantSpawn); SubscribeLocalEvent(OnTeleportSpell); @@ -65,6 +65,15 @@ public sealed class MagicSystem : EntitySystem SubscribeLocalEvent(OnChangeComponentsSpell); } + private void OnDoAfter(EntityUid uid, SpellbookComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + _actionsSystem.AddActions(args.Args.User, component.Spells, uid); + args.Handled = true; + } + private void OnInit(EntityUid uid, SpellbookComponent component, ComponentInit args) { //Negative charges means the spell can be used without it running out. @@ -102,35 +111,18 @@ public sealed class MagicSystem : EntitySystem private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args) { - if (component.CancelToken != null) return; - - component.CancelToken = new CancellationTokenSource(); - - var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, component.CancelToken.Token, uid) + var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, target:uid) { BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, - NeedHand = true, //What, are you going to read with your eyes only?? - TargetFinishedEvent = new LearnDoAfterComplete(args.User), - TargetCancelledEvent = new LearnDoAfterCancel(), + NeedHand = true //What, are you going to read with your eyes only?? }; _doAfter.DoAfter(doAfterEventArgs); } - private void OnLearnComplete(EntityUid uid, SpellbookComponent component, LearnDoAfterComplete ev) - { - component.CancelToken = null; - _actionsSystem.AddActions(ev.User, component.Spells, uid); - } - - private void OnLearnCancel(EntityUid uid, SpellbookComponent component, LearnDoAfterCancel args) - { - component.CancelToken = null; - } - #region Spells /// @@ -383,20 +375,4 @@ public sealed class MagicSystem : EntitySystem } #endregion - - #region DoAfterClasses - - private sealed class LearnDoAfterComplete : EntityEventArgs - { - public readonly EntityUid User; - - public LearnDoAfterComplete(EntityUid uid) - { - User = uid; - } - } - - private sealed class LearnDoAfterCancel : EntityEventArgs { } - - #endregion } diff --git a/Content.Server/Mech/Components/MechComponent.cs b/Content.Server/Mech/Components/MechComponent.cs index 8a9f1c7c2c..b233d0309e 100644 --- a/Content.Server/Mech/Components/MechComponent.cs +++ b/Content.Server/Mech/Components/MechComponent.cs @@ -1,5 +1,4 @@ -using System.Threading; -using Content.Server.Atmos; +using Content.Server.Atmos; using Content.Shared.Mech.Components; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -32,8 +31,6 @@ public sealed class MechComponent : SharedMechComponent [DataField("batteryRemovalDelay")] public float BatteryRemovalDelay = 2; - public CancellationTokenSource? EntryTokenSource; - /// /// Whether or not the mech is airtight. /// diff --git a/Content.Server/Mech/Equipment/Components/MechGrabberComponent.cs b/Content.Server/Mech/Equipment/Components/MechGrabberComponent.cs index 77881de7cb..c93e69e48d 100644 --- a/Content.Server/Mech/Equipment/Components/MechGrabberComponent.cs +++ b/Content.Server/Mech/Equipment/Components/MechGrabberComponent.cs @@ -46,29 +46,4 @@ public sealed class MechGrabberComponent : Component [ViewVariables(VVAccess.ReadWrite)] public Container ItemContainer = default!; - public CancellationTokenSource? Token; -} - -/// -/// Event raised on the user when the grab is complete. -/// -public sealed class MechGrabberGrabFinishedEvent : EntityEventArgs -{ - /// - /// The thing that was grabbed. - /// - public EntityUid Grabbed; - - public MechGrabberGrabFinishedEvent(EntityUid grabbed) - { - Grabbed = grabbed; - } -} - -/// -/// Event raised on the user when the grab fails -/// -public sealed class MechGrabberGrabCancelledEvent : EntityEventArgs -{ - } diff --git a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs index e0a59ed8ce..baf4eafd21 100644 --- a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs +++ b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs @@ -4,6 +4,8 @@ using Content.Server.Interaction; using Content.Server.Mech.Components; using Content.Server.Mech.Equipment.Components; using Content.Server.Mech.Systems; +using Content.Shared.DoAfter; +using Content.Shared.Construction.Components; using Content.Shared.Interaction; using Content.Shared.Mech; using Content.Shared.Mech.Equipment.Components; @@ -39,8 +41,7 @@ public sealed class MechGrabberSystem : EntitySystem SubscribeLocalEvent(OnAttemptRemove); SubscribeLocalEvent(OnInteract); - SubscribeLocalEvent(OnGrabFinished); - SubscribeLocalEvent(OnGrabCancelled); + SubscribeLocalEvent(OnMechGrab); } private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args) @@ -144,40 +145,37 @@ public sealed class MechGrabberSystem : EntitySystem if (mech.Energy + component.GrabEnergyDelta < 0) return; - if (component.Token != null) - return; - if (!_interaction.InRangeUnobstructed(args.User, target)) return; args.Handled = true; - component.Token = new(); component.AudioStream = _audio.PlayPvs(component.GrabSound, uid); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, component.Token.Token, target, uid) + _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, target:target, used:uid) { BreakOnTargetMove = true, - BreakOnUserMove = true, - UsedFinishedEvent = new MechGrabberGrabFinishedEvent(target), - UsedCancelledEvent = new MechGrabberGrabCancelledEvent() + BreakOnUserMove = true }); } - private void OnGrabFinished(EntityUid uid, MechGrabberComponent component, MechGrabberGrabFinishedEvent args) + private void OnMechGrab(EntityUid uid, MechGrabberComponent component, DoAfterEvent args) { - component.Token = null; + if (args.Cancelled) + { + component.AudioStream?.Stop(); + return; + } + + if (args.Handled || args.Args.Target == null) + return; if (!TryComp(uid, out var equipmentComponent) || equipmentComponent.EquipmentOwner == null) return; if (!_mech.TryChangeEnergy(equipmentComponent.EquipmentOwner.Value, component.GrabEnergyDelta)) return; - component.ItemContainer.Insert(args.Grabbed); + component.ItemContainer.Insert(args.Args.Target.Value); _mech.UpdateUserInterface(equipmentComponent.EquipmentOwner.Value); - } - private void OnGrabCancelled(EntityUid uid, MechGrabberComponent component, MechGrabberGrabCancelledEvent args) - { - component.AudioStream?.Stop(); - component.Token = null; + args.Handled = true; } } diff --git a/Content.Server/Mech/Systems/MechEquipmentSystem.cs b/Content.Server/Mech/Systems/MechEquipmentSystem.cs index 087c2e3aa3..8a9032a21d 100644 --- a/Content.Server/Mech/Systems/MechEquipmentSystem.cs +++ b/Content.Server/Mech/Systems/MechEquipmentSystem.cs @@ -1,9 +1,9 @@ using Content.Server.DoAfter; using Content.Server.Mech.Components; using Content.Server.Popups; +using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Mech.Equipment.Components; -using Robust.Shared.Player; namespace Content.Server.Mech.Systems; @@ -20,15 +20,11 @@ public sealed class MechEquipmentSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnUsed); - SubscribeLocalEvent(OnFinished); - SubscribeLocalEvent(OnCancelled); + SubscribeLocalEvent>(OnInsertEquipment); } private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterInteractEvent args) { - if (component.TokenSource != null) - return; - if (args.Handled || !args.CanReach || args.Target == null) return; @@ -50,26 +46,30 @@ public sealed class MechEquipmentSystem : EntitySystem _popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech); - component.TokenSource = new(); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.InstallDuration, component.TokenSource.Token, mech, uid) + var insertEquipment = new InsertEquipmentEvent(); + var doAfterEventArgs = new DoAfterEventArgs(args.User, component.InstallDuration, target: mech, used: uid) { BreakOnStun = true, BreakOnTargetMove = true, - BreakOnUserMove = true, - UsedFinishedEvent = new MechEquipmentInstallFinished(mech), - UsedCancelledEvent = new MechEquipmentInstallCancelled() - }); + BreakOnUserMove = true + }; + + _doAfter.DoAfter(doAfterEventArgs, insertEquipment); } - private void OnFinished(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallFinished args) + private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, DoAfterEvent args) { - component.TokenSource = null; - _popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Mech); - _mech.InsertEquipment(args.Mech, uid); + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + _popup.PopupEntity(Loc.GetString("mech-equipment-finish-install", ("item", uid)), args.Args.Target.Value); + _mech.InsertEquipment(args.Args.Target.Value, uid); + + args.Handled = true; } - private void OnCancelled(EntityUid uid, MechEquipmentComponent component, MechEquipmentInstallCancelled args) + private sealed class InsertEquipmentEvent : EntityEventArgs { - component.TokenSource = null; + } } diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 1a9b8ff4e0..b23fb22106 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -1,19 +1,17 @@ using System.Linq; -using System.Threading; using Content.Server.Atmos.EntitySystems; using Content.Server.DoAfter; using Content.Server.Mech.Components; using Content.Server.Power.Components; -using Content.Server.Tools; using Content.Server.Wires; using Content.Shared.ActionBlocker; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Mech; using Content.Shared.Mech.Components; using Content.Shared.Mech.EntitySystems; -using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Tools.Components; using Content.Shared.Verbs; @@ -47,12 +45,9 @@ public sealed class MechSystem : SharedMechSystem SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent>(OnAlternativeVerb); SubscribeLocalEvent(OnOpenUi); - SubscribeLocalEvent(OnEntryFinished); - SubscribeLocalEvent(OnEntryExitCancelled); - SubscribeLocalEvent(OnExitFinished); - SubscribeLocalEvent(OnEntryExitCancelled); - SubscribeLocalEvent(OnRemoveBatteryFinished); - SubscribeLocalEvent(OnRemoveBatteryCancelled); + SubscribeLocalEvent>(OnRemoveBattery); + SubscribeLocalEvent>(OnMechEntry); + SubscribeLocalEvent>(OnMechExit); SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnRemoveEquipmentMessage); @@ -87,32 +82,29 @@ public sealed class MechSystem : SharedMechSystem return; } - if (component.EntryTokenSource == null && - TryComp(args.Used, out var tool) && - tool.Qualities.Contains("Prying") && - component.BatterySlot.ContainedEntity != null) + if (TryComp(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null) { - component.EntryTokenSource = new(); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, component.EntryTokenSource.Token, uid, args.Target) + var removeBattery = new RemoveBatteryEvent(); + + var doAfterEventArgs = new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, target: uid, used: args.Target) { BreakOnTargetMove = true, - BreakOnUserMove = true, - TargetFinishedEvent = new MechRemoveBatteryFinishedEvent(), - TargetCancelledEvent = new MechRemoveBatteryCancelledEvent() - }); + BreakOnUserMove = true + }; + + _doAfter.DoAfter(doAfterEventArgs, removeBattery); } } - private void OnRemoveBatteryFinished(EntityUid uid, MechComponent component, MechRemoveBatteryFinishedEvent args) + private void OnRemoveBattery(EntityUid uid, MechComponent component, DoAfterEvent args) { - component.EntryTokenSource = null; + if (args.Cancelled || args.Handled) + return; + RemoveBattery(uid, component); _actionBlocker.UpdateCanMove(uid); - } - private void OnRemoveBatteryCancelled(EntityUid uid, MechComponent component, MechRemoveBatteryCancelledEvent args) - { - component.EntryTokenSource = null; + args.Handled = true; } private void OnMapInit(EntityUid uid, MechComponent component, MapInitEvent args) @@ -171,16 +163,14 @@ public sealed class MechSystem : SharedMechSystem Text = Loc.GetString("mech-verb-enter"), Act = () => { - if (component.EntryTokenSource != null) - return; - component.EntryTokenSource = new CancellationTokenSource(); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.EntryDelay, component.EntryTokenSource.Token, uid) + var mechEntryEvent = new MechEntryEvent(); + var doAfterEventArgs = new DoAfterEventArgs(args.User, component.EntryDelay, target: uid) { BreakOnUserMove = true, BreakOnStun = true, - TargetFinishedEvent = new MechEntryFinishedEvent(args.User), - TargetCancelledEvent = new MechEntryCanclledEvent() - }); + }; + + _doAfter.DoAfter(doAfterEventArgs, mechEntryEvent); } }; var openUiVerb = new AlternativeVerb //can't hijack someone else's mech @@ -199,45 +189,46 @@ public sealed class MechSystem : SharedMechSystem Priority = 1, // Promote to top to make ejecting the ALT-click action Act = () => { - if (component.EntryTokenSource != null) - return; if (args.User == component.PilotSlot.ContainedEntity) { TryEject(uid, component); return; } - component.EntryTokenSource = new CancellationTokenSource(); - _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExitDelay, component.EntryTokenSource.Token, uid) + var mechExitEvent = new MechExitEvent(); + var doAfterEventArgs = new DoAfterEventArgs(args.User, component.ExitDelay, target: uid) { BreakOnUserMove = true, BreakOnTargetMove = true, - BreakOnStun = true, - TargetFinishedEvent = new MechExitFinishedEvent(), - TargetCancelledEvent = new MechExitCanclledEvent() - }); + BreakOnStun = true + }; + + _doAfter.DoAfter(doAfterEventArgs, mechExitEvent); } }; args.Verbs.Add(ejectVerb); } } - private void OnEntryFinished(EntityUid uid, MechComponent component, MechEntryFinishedEvent args) + private void OnMechEntry(EntityUid uid, MechComponent component, DoAfterEvent args) { - component.EntryTokenSource = null; - TryInsert(uid, args.User, component); + if (args.Cancelled || args.Handled) + return; + + TryInsert(uid, args.Args.User, component); _actionBlocker.UpdateCanMove(uid); + + args.Handled = true; } - private void OnExitFinished(EntityUid uid, MechComponent component, MechExitFinishedEvent args) + private void OnMechExit(EntityUid uid, MechComponent component, DoAfterEvent args) { - component.EntryTokenSource = null; + if (args.Cancelled || args.Handled) + return; + TryEject(uid, component); - } - private void OnEntryExitCancelled(EntityUid uid, MechComponent component, EntityEventArgs args) - { - component.EntryTokenSource = null; + args.Handled = true; } private void OnDamageChanged(EntityUid uid, SharedMechComponent component, DamageChangedEvent args) @@ -454,4 +445,27 @@ public sealed class MechSystem : SharedMechSystem args.Handled = true; } #endregion + + /// + /// Event raised when the battery is successfully removed from the mech, + /// on both success and failure + /// + private sealed class RemoveBatteryEvent : EntityEventArgs + { + } + + /// + /// Event raised when a person enters a mech, on both success and failure + /// + private sealed class MechEntryEvent : EntityEventArgs + { + } + + /// + /// Event raised when a person removes someone from a mech, + /// on both success and failure + /// + private sealed class MechExitEvent : EntityEventArgs + { + } } diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs index 5f2c6139d2..4022c25d14 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs @@ -9,8 +9,6 @@ namespace Content.Server.Medical.BiomassReclaimer [RegisterComponent] public sealed class BiomassReclaimerComponent : Component { - public CancellationTokenSource? CancelToken; - /// /// This gets set for each mob it processes. /// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item. diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index 7d35105930..b418e6a6e3 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -17,6 +17,7 @@ using Content.Server.Construction; using Content.Server.DoAfter; using Content.Server.Materials; using Content.Server.Mind.Components; +using Content.Shared.DoAfter; using Content.Shared.Humanoid; using Content.Shared.Interaction.Events; using Content.Shared.Mobs.Components; @@ -96,8 +97,7 @@ namespace Content.Server.Medical.BiomassReclaimer SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnSuicide); - SubscribeLocalEvent(OnReclaimSuccessful); - SubscribeLocalEvent(OnReclaimCancelled); + SubscribeLocalEvent(OnDoAfter); } private void OnSuicide(EntityUid uid, BiomassReclaimerComponent component, SuicideEvent args) @@ -146,20 +146,14 @@ namespace Content.Server.Medical.BiomassReclaimer } private void OnAfterInteractUsing(EntityUid uid, BiomassReclaimerComponent component, AfterInteractUsingEvent args) { - if (!args.CanReach) - return; - - if (component.CancelToken != null || args.Target == null) + if (!args.CanReach || args.Target == null) return; if (!HasComp(args.Used) || !CanGib(uid, args.Used, component)) return; - component.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, component.CancelToken.Token, args.Target, args.Used) + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, target:args.Target, used:args.Used) { - BroadcastFinishedEvent = new ReclaimSuccessfulEvent(args.User, args.Used, uid), - BroadcastCancelledEvent = new ReclaimCancelledEvent(uid), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -200,21 +194,15 @@ namespace Content.Server.Medical.BiomassReclaimer args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-biomass-yield", component.YieldPerUnitMass / component.BaseYieldPerUnitMass); } - private void OnReclaimSuccessful(ReclaimSuccessfulEvent args) + private void OnDoAfter(EntityUid uid, BiomassReclaimerComponent component, DoAfterEvent args) { - if (!TryComp(args.Reclaimer, out var reclaimer)) + if (args.Handled || args.Cancelled || args.Args.Target == null || HasComp(args.Args.Target.Value)) return; - _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Target):target} in {ToPrettyString(args.Reclaimer):reclaimer}"); - reclaimer.CancelToken = null; - StartProcessing(args.Target, reclaimer); - } + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Args.Target.Value):target} in {ToPrettyString(uid):reclaimer}"); + StartProcessing(args.Args.Target.Value, component); - private void OnReclaimCancelled(ReclaimCancelledEvent args) - { - if (!TryComp(args.Reclaimer, out var reclaimer)) - return; - reclaimer.CancelToken = null; + args.Handled = true; } private void StartProcessing(EntityUid toProcess, BiomassReclaimerComponent component, PhysicsComponent? physics = null) @@ -266,28 +254,5 @@ namespace Content.Server.Medical.BiomassReclaimer return true; } - - private readonly struct ReclaimCancelledEvent - { - public readonly EntityUid Reclaimer; - - public ReclaimCancelledEvent(EntityUid reclaimer) - { - Reclaimer = reclaimer; - } - } - - private readonly struct ReclaimSuccessfulEvent - { - public readonly EntityUid User; - public readonly EntityUid Target; - public readonly EntityUid Reclaimer; - public ReclaimSuccessfulEvent(EntityUid user, EntityUid target, EntityUid reclaimer) - { - User = user; - Target = target; - Reclaimer = reclaimer; - } - } } } diff --git a/Content.Server/Medical/Components/HealingComponent.cs b/Content.Server/Medical/Components/HealingComponent.cs index a48b372dd6..edebf7697a 100644 --- a/Content.Server/Medical/Components/HealingComponent.cs +++ b/Content.Server/Medical/Components/HealingComponent.cs @@ -45,8 +45,6 @@ namespace Content.Server.Medical.Components [DataField("selfHealPenaltyMultiplier")] public float SelfHealPenaltyMultiplier = 3f; - public CancellationTokenSource? CancelToken = null; - /// /// Sound played on healing begin /// diff --git a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs index 237cd4b4ff..793b6e628d 100644 --- a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs +++ b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs @@ -20,10 +20,7 @@ namespace Content.Server.Medical.Components /// [DataField("scanDelay")] public float ScanDelay = 0.8f; - /// - /// Token for interrupting scanning do after. - /// - public CancellationTokenSource? CancelToken; + public BoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key); /// diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index b789031044..a778322caf 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; @@ -14,18 +13,19 @@ using Content.Server.NodeContainer; using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; -using Content.Server.Tools; using Content.Server.UserInterface; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Containers.ItemSlots; +using Content.Shared.DoAfter; using Content.Shared.DragDrop; using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Medical.Cryogenics; using Content.Shared.MedicalScanner; +using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Verbs; using Robust.Server.GameObjects; @@ -44,7 +44,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; [Dependency] private readonly ReactiveSystem _reactiveSystem = default!; @@ -57,8 +57,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent>(AddAlternativeVerbs); SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(DoInsertCryoPod); - SubscribeLocalEvent(DoInsertCancelCryoPod); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnCryoPodPryFinished); SubscribeLocalEvent(OnCryoPodPryInterrupted); @@ -130,32 +129,30 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem private void HandleDragDropOn(EntityUid uid, CryoPodComponent cryoPodComponent, ref DragDropTargetEvent args) { if (cryoPodComponent.BodyContainer.ContainedEntity != null) - { return; - } - if (cryoPodComponent.DragDropCancelToken != null) - { - cryoPodComponent.DragDropCancelToken.Cancel(); - cryoPodComponent.DragDropCancelToken = null; - return; - } - - cryoPodComponent.DragDropCancelToken = new CancellationTokenSource(); - var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, cryoPodComponent.DragDropCancelToken.Token, uid, args.Dragged) + var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, target:args.Dragged, used:uid) { BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, BreakOnUserMove = true, NeedHand = false, - TargetFinishedEvent = new DoInsertCryoPodEvent(args.Dragged), - TargetCancelledEvent = new DoInsertCancelledCryoPodEvent() }; _doAfterSystem.DoAfter(doAfterArgs); args.Handled = true; } + private void OnDoAfter(EntityUid uid, CryoPodComponent component, DoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Args.Target == null) + return; + + InsertBody(uid, args.Args.Target.Value, component); + + args.Handled = true; + } + private void OnActivateUIAttempt(EntityUid uid, CryoPodComponent cryoPodComponent, ActivatableUIOpenAttemptEvent args) { if (args.Cancelled) @@ -190,9 +187,8 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem return; cryoPodComponent.IsPrying = true; - _toolSystem.UseTool(args.Used, args.User, uid, 0f, - cryoPodComponent.PryDelay, "Prying", - new CryoPodPryFinished(), new CryoPodPryInterrupted(), uid); + var toolEvData = new ToolEventData(new CryoPodPryFinished(), targetEntity:uid); + _toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, new [] {"Prying"}, toolEvData); args.Handled = true; } @@ -200,7 +196,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem private void OnExamined(EntityUid uid, CryoPodComponent component, ExaminedEvent args) { - var container = _itemSlotsSystem.GetItemOrNull(component.Owner, component.SolutionContainerName); + var container = _itemSlotsSystem.GetItemOrNull(uid, component.SolutionContainerName); if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out var containerSolution)) { args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value)))); diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index 4fbcfb0833..a908801605 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Administration.Logs; using Content.Server.Body.Systems; using Content.Server.DoAfter; @@ -7,6 +6,7 @@ using Content.Server.Stack; using Content.Shared.Audio; using Content.Shared.Damage; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Mobs; @@ -35,51 +35,39 @@ public sealed class HealingSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnHealingUse); SubscribeLocalEvent(OnHealingAfterInteract); - SubscribeLocalEvent(OnHealingCancelled); - SubscribeLocalEvent(OnHealingComplete); + SubscribeLocalEvent>(OnDoAfter); } - private void OnHealingComplete(EntityUid uid, DamageableComponent component, HealingCompleteEvent args) + private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent args) { - if (_mobStateSystem.IsDead(uid)) + if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null) return; - if (TryComp(args.Component.Owner, out var stack) && stack.Count < 1) + if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID)) return; - if (component.DamageContainerID is not null && - !component.DamageContainerID.Equals(component.DamageContainerID)) - return; + // Heal some bloodloss damage. + if (args.AdditionalData.HealingComponent.BloodlossModifier != 0) + _bloodstreamSystem.TryModifyBleedAmount(uid, args.AdditionalData.HealingComponent.BloodlossModifier); - if (args.Component.BloodlossModifier != 0) - { - // Heal some bloodloss damage. - _bloodstreamSystem.TryModifyBleedAmount(uid, args.Component.BloodlossModifier); - } + var healed = _damageable.TryChangeDamage(uid, args.AdditionalData.HealingComponent.Damage, true, origin: args.Args.User); - var healed = _damageable.TryChangeDamage(uid, args.Component.Damage, true, origin: args.User); - - // Reverify that we can heal the damage. if (healed == null) return; - _stacks.Use(args.Component.Owner, 1, stack); + // Reverify that we can heal the damage. + _stacks.Use(args.Args.Used.Value, 1, args.AdditionalData.Stack); + + if (uid != args.Args.User) + _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage"); - if (uid != args.User) - _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage"); else - _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.User):user} healed themselves for {healed.Total:damage} damage"); + _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed themselves for {healed.Total:damage} damage"); - if (args.Component.HealingEndSound != null) - { - _audio.PlayPvs(args.Component.HealingEndSound, uid, - AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f)); - } - } + if (args.AdditionalData.HealingComponent.HealingEndSound != null) + _audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f)); - private static void OnHealingCancelled(HealingCancelledEvent ev) - { - ev.Component.CancelToken = null; + args.Handled = true; } private void OnHealingUse(EntityUid uid, HealingComponent component, UseInHandEvent args) @@ -102,15 +90,7 @@ public sealed class HealingSystem : EntitySystem private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component) { - if (component.CancelToken != null) - { - return false; - } - - if (_mobStateSystem.IsDead(target)) - return false; - - if (!TryComp(target, out var targetDamage)) + if (_mobStateSystem.IsDead(target) || !TryComp(target, out var targetDamage)) return false; if (targetDamage.TotalDamage == 0) @@ -119,28 +99,22 @@ public sealed class HealingSystem : EntitySystem if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(targetDamage.DamageContainerID)) return false; - if (user != target && - !_interactionSystem.InRangeUnobstructed(user, target, popup: true)) - { - return false; - } - - if (TryComp(component.Owner, out var stack) && stack.Count < 1) + if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true)) return false; - component.CancelToken = new CancellationTokenSource(); + if (!TryComp(uid, out var stack) || stack.Count < 1) + return false; if (component.HealingBeginSound != null) - { - _audio.PlayPvs(component.HealingBeginSound, uid, - AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f)); - } + _audio.PlayPvs(component.HealingBeginSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f)); var delay = user != target ? component.Delay : component.Delay * GetScaledHealingPenalty(user, component); - _doAfter.DoAfter(new DoAfterEventArgs(user, delay, component.CancelToken.Token, target) + var healingData = new HealingData(component, stack); + + var doAfterEventArgs = new DoAfterEventArgs(user, delay, target: target, used: uid) { BreakOnUserMove = true, BreakOnTargetMove = true, @@ -148,22 +122,11 @@ public sealed class HealingSystem : EntitySystem // not being able to heal your own ticking damage would be frustrating. BreakOnStun = true, NeedHand = true, - TargetFinishedEvent = new HealingCompleteEvent - { - User = user, - Component = component, - }, - BroadcastCancelledEvent = new HealingCancelledEvent - { - Component = component, - }, // Juusstt in case damageble gets removed it avoids having to re-cancel the token. Won't need this when DoAfterEvent gets added. - PostCheck = () => - { - component.CancelToken = null; - return true; - }, - }); + PostCheck = () => true + }; + + _doAfter.DoAfter(doAfterEventArgs, healingData); return true; } @@ -179,27 +142,18 @@ public sealed class HealingSystem : EntitySystem var output = component.Delay; if (!TryComp(uid, out var mobThreshold) || !TryComp(uid, out var damageable)) return output; - - - if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, - mobThreshold)) - { + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold)) return 1; - } + var percentDamage = (float) (damageable.TotalDamage / amount); //basically make it scale from 1 to the multiplier. var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1; return Math.Max(modifier, 1); } - private sealed class HealingCompleteEvent : EntityEventArgs + private record struct HealingData(HealingComponent HealingComponent, StackComponent Stack) { - public EntityUid User; - public HealingComponent Component = default!; - } - - private sealed class HealingCancelledEvent : EntityEventArgs - { - public HealingComponent Component = default!; + public HealingComponent HealingComponent = HealingComponent; + public StackComponent Stack = Stack; } } diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index 0f5f1d0959..0ba0770036 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -1,15 +1,13 @@ -using System.Threading; using Content.Server.DoAfter; using Content.Server.Medical.Components; using Content.Server.Disease; using Content.Server.Popups; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Mobs.Components; -using Content.Shared.Audio; using Robust.Server.GameObjects; -using Robust.Shared.Player; using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent; namespace Content.Server.Medical @@ -20,14 +18,14 @@ namespace Content.Server.Medical [Dependency] private readonly DiseaseSystem _disease = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(HandleActivateInWorld); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnTargetScanSuccessful); - SubscribeLocalEvent(OnScanCancelled); + SubscribeLocalEvent(OnDoAfter); } private void HandleActivateInWorld(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, ActivateInWorldEvent args) @@ -37,33 +35,13 @@ namespace Content.Server.Medical private void OnAfterInteract(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, AfterInteractEvent args) { - if (healthAnalyzer.CancelToken != null) - { - healthAnalyzer.CancelToken.Cancel(); - healthAnalyzer.CancelToken = null; + if (args.Target == null || !args.CanReach || !HasComp(args.Target)) return; - } - - if (args.Target == null) - return; - - if (!args.CanReach) - return; - - if (healthAnalyzer.CancelToken != null) - return; - - if (!HasComp(args.Target)) - return; - - healthAnalyzer.CancelToken = new CancellationTokenSource(); _audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid); - _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, healthAnalyzer.CancelToken.Token, target: args.Target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, target: args.Target, used:uid) { - BroadcastFinishedEvent = new TargetScanSuccessfulEvent(args.User, args.Target, healthAnalyzer), - BroadcastCancelledEvent = new ScanCancelledEvent(healthAnalyzer), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -71,37 +49,47 @@ namespace Content.Server.Medical }); } - private void OnTargetScanSuccessful(TargetScanSuccessfulEvent args) + private void OnDoAfter(EntityUid uid, HealthAnalyzerComponent component, DoAfterEvent args) { - args.Component.CancelToken = null; + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; - _audio.PlayPvs(args.Component.ScanningEndSound, args.User); + _audio.PlayPvs(component.ScanningEndSound, args.Args.User); - UpdateScannedUser(args.Component.Owner, args.User, args.Target, args.Component); + UpdateScannedUser(uid, args.Args.User, args.Args.Target.Value, component); // Below is for the traitor item // Piggybacking off another component's doafter is complete CBT so I gave up // and put it on the same component - if (string.IsNullOrEmpty(args.Component.Disease) || args.Target == null) - return; - - _disease.TryAddDisease(args.Target.Value, args.Component.Disease); - - if (args.User == args.Target) + if (string.IsNullOrEmpty(component.Disease)) { - _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", args.Component.Disease)), - args.User, args.User); + args.Handled = true; return; } - _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Target.Value, EntityManager)), ("disease", args.Component.Disease)), - args.User, args.User); + + _disease.TryAddDisease(args.Args.Target.Value, component.Disease); + + if (args.Args.User == args.Args.Target) + { + _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", component.Disease)), + args.Args.User, args.Args.User); + } + + + else + { + _popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", Identity.Entity(args.Args.Target.Value, EntityManager)), + ("disease", component.Disease)), args.Args.User, args.Args.User); + } + + args.Handled = true; } private void OpenUserInterface(EntityUid user, HealthAnalyzerComponent healthAnalyzer) { - if (!TryComp(user, out var actor)) + if (!TryComp(user, out var actor) || healthAnalyzer.UserInterface == null) return; - healthAnalyzer.UserInterface?.Open(actor.PlayerSession); + _uiSystem.OpenUi(healthAnalyzer.UserInterface ,actor.PlayerSession); } public void UpdateScannedUser(EntityUid uid, EntityUid user, EntityUid? target, HealthAnalyzerComponent? healthAnalyzer) @@ -116,35 +104,7 @@ namespace Content.Server.Medical return; OpenUserInterface(user, healthAnalyzer); - healthAnalyzer.UserInterface?.SendMessage(new HealthAnalyzerScannedUserMessage(target)); - } - - private static void OnScanCancelled(ScanCancelledEvent args) - { - args.HealthAnalyzer.CancelToken = null; - } - - private sealed class ScanCancelledEvent : EntityEventArgs - { - public readonly HealthAnalyzerComponent HealthAnalyzer; - public ScanCancelledEvent(HealthAnalyzerComponent healthAnalyzer) - { - HealthAnalyzer = healthAnalyzer; - } - } - - private sealed class TargetScanSuccessfulEvent : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid? Target { get; } - public HealthAnalyzerComponent Component { get; } - - public TargetScanSuccessfulEvent(EntityUid user, EntityUid? target, HealthAnalyzerComponent component) - { - User = user; - Target = target; - Component = component; - } + _uiSystem.SendUiMessage(healthAnalyzer.UserInterface, new HealthAnalyzerScannedUserMessage(target)); } } } diff --git a/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs b/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs index e0af81530b..6e27ecc6b1 100644 --- a/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs +++ b/Content.Server/Medical/Stethoscope/Components/StethoscopeComponent.cs @@ -12,8 +12,6 @@ namespace Content.Server.Medical.Components { public bool IsActive = false; - public CancellationTokenSource? CancelToken; - [DataField("delay")] public float Delay = 2.5f; diff --git a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs index a78e5bbe2b..1ce71c8c71 100644 --- a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs +++ b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs @@ -8,10 +8,9 @@ using Content.Shared.Damage; using Content.Shared.FixedPoint; using Content.Shared.Inventory.Events; using Content.Shared.Verbs; -using Robust.Shared.Player; -using System.Threading; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.DoAfter; namespace Content.Server.Medical { @@ -29,8 +28,7 @@ namespace Content.Server.Medical SubscribeLocalEvent>(AddStethoscopeVerb); SubscribeLocalEvent(OnGetActions); SubscribeLocalEvent(OnStethoscopeAction); - SubscribeLocalEvent(OnListenSuccess); - SubscribeLocalEvent(OnListenCancelled); + SubscribeLocalEvent(OnDoAfter); } /// @@ -81,7 +79,7 @@ namespace Content.Server.Medical { Act = () => { - StartListening(uid, args.Target, stetho); // start doafter + StartListening(component.Stethoscope, uid, args.Target, stetho); // start doafter }, Text = Loc.GetString("stethoscope-verb"), IconTexture = "Clothing/Neck/Misc/stethoscope.rsi/icon.png", @@ -93,7 +91,7 @@ namespace Content.Server.Medical private void OnStethoscopeAction(EntityUid uid, StethoscopeComponent component, StethoscopeActionEvent args) { - StartListening(args.Performer, args.Target, component); + StartListening(uid, args.Performer, args.Target, component); } private void OnGetActions(EntityUid uid, StethoscopeComponent component, GetItemActionsEvent args) @@ -101,27 +99,11 @@ namespace Content.Server.Medical args.Actions.Add(component.Action); } - // doafter succeeded / failed - private void OnListenSuccess(ListenSuccessfulEvent ev) - { - ev.Component.CancelToken = null; - ExamineWithStethoscope(ev.User, ev.Target); - } - - private void OnListenCancelled(ListenCancelledEvent ev) - { - if (ev.Component == null) - return; - ev.Component.CancelToken = null; - } // construct the doafter and start it - private void StartListening(EntityUid user, EntityUid target, StethoscopeComponent comp) + private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp) { - comp.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, comp.CancelToken.Token, target: target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, target: target, used:scope) { - BroadcastFinishedEvent = new ListenSuccessfulEvent(user, target, comp), - BroadcastCancelledEvent = new ListenCancelledEvent(user, comp), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -129,6 +111,14 @@ namespace Content.Server.Medical }); } + private void OnDoAfter(EntityUid uid, StethoscopeComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + ExamineWithStethoscope(args.Args.User, args.Args.Target.Value); + } + /// /// Return a value based on the total oxyloss of the target. /// Could be expanded in the future with reagent effects etc. @@ -165,34 +155,6 @@ namespace Content.Server.Medical }; return msg; } - - // events for the doafter - private sealed class ListenSuccessfulEvent : EntityEventArgs - { - public EntityUid User; - public EntityUid Target; - public StethoscopeComponent Component; - - public ListenSuccessfulEvent(EntityUid user, EntityUid target, StethoscopeComponent component) - { - User = user; - Target = target; - Component = component; - } - } - - private sealed class ListenCancelledEvent : EntityEventArgs - { - public EntityUid Uid; - public StethoscopeComponent Component; - - public ListenCancelledEvent(EntityUid uid, StethoscopeComponent component) - { - Uid = uid; - Component = component; - } - } - } public sealed class StethoscopeActionEvent : EntityTargetActionEvent {} diff --git a/Content.Server/Nuke/NukeComponent.cs b/Content.Server/Nuke/NukeComponent.cs index 16cfc3919d..2d91af1f6e 100644 --- a/Content.Server/Nuke/NukeComponent.cs +++ b/Content.Server/Nuke/NukeComponent.cs @@ -174,8 +174,6 @@ namespace Content.Server.Nuke /// public bool PlayedAlertSound = false; - public CancellationToken? DisarmCancelToken = null; - public IPlayingAudioStream? AlertAudioStream = default; } } diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 2c870f504b..47e10929a3 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -6,13 +6,13 @@ using Content.Server.DoAfter; using Content.Server.Explosion.EntitySystems; using Content.Server.Popups; using Content.Server.Station.Systems; -using Content.Server.UserInterface; using Content.Shared.Audio; using Content.Shared.Construction.Components; using Content.Shared.Containers.ItemSlots; +using Content.Shared.DoAfter; using Content.Shared.Nuke; using Content.Shared.Popups; -using Robust.Shared.Audio; +using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Random; @@ -30,6 +30,8 @@ namespace Content.Server.Nuke [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; /// /// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx @@ -65,8 +67,7 @@ namespace Content.Server.Nuke SubscribeLocalEvent(OnEnterButtonPressed); // Doafter events - SubscribeLocalEvent(OnDisarmSuccess); - SubscribeLocalEvent(OnDisarmCancelled); + SubscribeLocalEvent(OnDoAfter); } private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args) @@ -211,7 +212,7 @@ namespace Content.Server.Nuke private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args) { - PlaySound(uid, component.KeypadPressSound, 0f, component); + _audio.Play(component.KeypadPressSound, Filter.Pvs(uid), uid, true); if (component.Status != NukeStatus.AWAIT_CODE) return; @@ -241,17 +242,18 @@ namespace Content.Server.Nuke #region Doafter Events - private void OnDisarmSuccess(EntityUid uid, NukeComponent component, NukeDisarmSuccessEvent args) + private void OnDoAfter(EntityUid uid, NukeComponent component, DoAfterEvent args) { - component.DisarmCancelToken = null; + if(args.Handled || args.Cancelled) + return; + DisarmBomb(uid, component); - } - private void OnDisarmCancelled(EntityUid uid, NukeComponent component, NukeDisarmCancelledEvent args) - { - component.DisarmCancelToken = null; - } + var ev = new NukeDisarmSuccessEvent(); + RaiseLocalEvent(ev); + args.Handled = true; + } #endregion private void TickCooldown(EntityUid uid, float frameTime, NukeComponent? nuke = null) @@ -268,7 +270,7 @@ namespace Content.Server.Nuke UpdateStatus(uid, nuke); } - UpdateUserInterface(nuke.Owner, nuke); + UpdateUserInterface(uid, nuke); } private void TickTimer(EntityUid uid, float frameTime, NukeComponent? nuke = null) @@ -289,7 +291,7 @@ namespace Content.Server.Nuke // play alert sound if time is running out if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound) { - nuke.AlertAudioStream = SoundSystem.Play(nuke.AlertSound.GetSound(), Filter.Broadcast()); + nuke.AlertAudioStream = _audio.Play(nuke.AlertSound, Filter.Broadcast(), uid, true); _soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke); nuke.PlayedAlertSound = true; } @@ -329,12 +331,12 @@ namespace Content.Server.Nuke { component.Status = NukeStatus.AWAIT_ARM; component.RemainingTime = component.Timer; - PlaySound(uid, component.AccessGrantedSound, 0, component); + _audio.Play(component.AccessGrantedSound, Filter.Pvs(uid), uid, true); } else { component.EnteredCode = ""; - PlaySound(uid, component.AccessDeniedSound, 0, component); + _audio.Play(component.AccessDeniedSound, Filter.Pvs(uid), uid, true); } break; @@ -353,7 +355,7 @@ namespace Content.Server.Nuke if (!Resolve(uid, ref component)) return; - var ui = component.Owner.GetUIOrNull(NukeUiKey.Key); + var ui = _ui.GetUiOrNull(uid, NukeUiKey.Key); if (ui == null) return; @@ -365,7 +367,7 @@ namespace Content.Server.Nuke (component.Status == NukeStatus.AWAIT_ARM || component.Status == NukeStatus.ARMED); - var state = new NukeUiState() + var state = new NukeUiState { Status = component.Status, RemainingTime = (int) component.RemainingTime, @@ -377,7 +379,7 @@ namespace Content.Server.Nuke CooldownTime = (int) component.CooldownTime }; - ui.SetState(state); + _ui.SetUiState(ui, state); } private void PlayNukeKeypadSound(EntityUid uid, int number, NukeComponent? component = null) @@ -407,18 +409,7 @@ namespace Content.Server.Nuke // Don't double-dip on the octave shifting component.LastPlayedKeypadSemitones = number == 0 ? component.LastPlayedKeypadSemitones : semitoneShift; - SoundSystem.Play(component.KeypadPressSound.GetSound(), Filter.Pvs(uid), uid, - AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f)); - } - - private void PlaySound(EntityUid uid, SoundSpecifier sound, float varyPitch = 0f, - NukeComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - SoundSystem.Play(sound.GetSound(), - Filter.Pvs(uid), uid, AudioHelpers.WithVariation(varyPitch).WithVolume(-5f)); + _audio.Play(component.KeypadPressSound, Filter.Pvs(uid), uid, true, AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f)); } public string GenerateRandomNumberString(int length) @@ -465,7 +456,7 @@ namespace Content.Server.Nuke var sender = Loc.GetString("nuke-component-announcement-sender"); _chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false, null, Color.Red); - NukeArmedAudio(component); + _soundSystem.PlayGlobalOnStation(uid, _audio.GetSound(component.ArmSound)); _itemSlots.SetLock(uid, component.DiskSlot, true); nukeXform.Anchored = true; @@ -494,7 +485,8 @@ namespace Content.Server.Nuke _chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false); component.PlayedNukeSong = false; - NukeDisarmedAudio(component); + _soundSystem.PlayGlobalOnStation(uid, _audio.GetSound(component.DisarmSound)); + _soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke); // disable sound and reset it component.PlayedAlertSound = false; @@ -547,7 +539,7 @@ namespace Content.Server.Nuke OwningStation = transform.GridUid, }); - _soundSystem.StopStationEventMusic(component.Owner, StationEventMusicType.Nuke); + _soundSystem.StopStationEventMusic(uid, StationEventMusicType.Nuke); EntityManager.DeleteEntity(uid); } @@ -567,34 +559,19 @@ namespace Content.Server.Nuke private void DisarmBombDoafter(EntityUid uid, EntityUid user, NukeComponent nuke) { - nuke.DisarmCancelToken = new(); - var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, nuke.DisarmCancelToken.Value, uid) + var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, target: uid) { - TargetCancelledEvent = new NukeDisarmCancelledEvent(), - TargetFinishedEvent = new NukeDisarmSuccessEvent(), - BroadcastFinishedEvent = new NukeDisarmSuccessEvent(), BreakOnDamage = true, BreakOnStun = true, BreakOnTargetMove = true, BreakOnUserMove = true, - NeedHand = true, + NeedHand = true }; _doAfterSystem.DoAfter(doafter); _popups.PopupEntity(Loc.GetString("nuke-component-doafter-warning"), user, user, PopupType.LargeCaution); } - - private void NukeArmedAudio(NukeComponent component) - { - _soundSystem.PlayGlobalOnStation(component.Owner, component.ArmSound.GetSound()); - } - - private void NukeDisarmedAudio(NukeComponent component) - { - _soundSystem.PlayGlobalOnStation(component.Owner, component.DisarmSound.GetSound()); - _soundSystem.StopStationEventMusic(component.Owner, StationEventMusicType.Nuke); - } } public sealed class NukeExplodedEvent : EntityEventArgs @@ -604,18 +581,10 @@ namespace Content.Server.Nuke /// /// Raised directed on the nuke when its disarm doafter is successful. + /// So the game knows not to end. /// public sealed class NukeDisarmSuccessEvent : EntityEventArgs { } - - /// - /// Raised directed on the nuke when its disarm doafter is cancelled. - /// - public sealed class NukeDisarmCancelledEvent : EntityEventArgs - { - - } - } diff --git a/Content.Server/Nutrition/Components/DrinkComponent.cs b/Content.Server/Nutrition/Components/DrinkComponent.cs index e3fc4c8a2d..be882f7947 100644 --- a/Content.Server/Nutrition/Components/DrinkComponent.cs +++ b/Content.Server/Nutrition/Components/DrinkComponent.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Nutrition.EntitySystems; using Content.Shared.FixedPoint; using JetBrains.Annotations; @@ -46,11 +45,5 @@ namespace Content.Server.Nutrition.Components /// [DataField("forceFeedDelay")] public float ForceFeedDelay = 3; - - /// - /// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is - /// currently "in use". - /// - public CancellationTokenSource? CancelToken; } } diff --git a/Content.Server/Nutrition/Components/FoodComponent.cs b/Content.Server/Nutrition/Components/FoodComponent.cs index 511a298867..5d7d325b02 100644 --- a/Content.Server/Nutrition/Components/FoodComponent.cs +++ b/Content.Server/Nutrition/Components/FoodComponent.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Chemistry.EntitySystems; using Content.Server.Nutrition.EntitySystems; using Content.Shared.FixedPoint; @@ -54,12 +53,6 @@ namespace Content.Server.Nutrition.Components [DataField("forceFeedDelay")] public float ForceFeedDelay = 3; - /// - /// Token for interrupting a do-after action (e.g., force feeding). If not null, implies component is - /// currently "in use". - /// - public CancellationTokenSource? CancelToken; - [ViewVariables] public int UsesRemaining { diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 9ab1f48dfc..04c6ee7159 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.Components.SolutionManager; @@ -9,8 +8,11 @@ using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.Administration.Logs; using Content.Shared.Body.Components; +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.IdentityManagement; @@ -46,6 +48,7 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly ReactiveSystem _reaction = default!; public override void Initialize() { @@ -59,8 +62,7 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent>(AddDrinkVerb); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnTransferAttempt); - SubscribeLocalEvent(OnDrink); - SubscribeLocalEvent(OnDrinkCancelled); + SubscribeLocalEvent>(OnDoAfter); } public bool IsEmpty(EntityUid uid, DrinkComponent? component = null) @@ -126,12 +128,13 @@ namespace Content.Server.Nutrition.EntitySystems if (args.Handled || args.Target == null || !args.CanReach) return; - args.Handled = TryDrink(args.User, args.Target.Value, component); + args.Handled = TryDrink(args.User, args.Target.Value, component, uid); } private void OnUse(EntityUid uid, DrinkComponent component, UseInHandEvent args) { - if (args.Handled) return; + if (args.Handled) + return; if (!component.Opened) { @@ -142,7 +145,7 @@ namespace Content.Server.Nutrition.EntitySystems return; } - args.Handled = TryDrink(args.User, args.User, component); + args.Handled = TryDrink(args.User, args.User, component, uid); } private void HandleLand(EntityUid uid, DrinkComponent component, ref LandEvent args) @@ -212,36 +215,30 @@ namespace Content.Server.Nutrition.EntitySystems } } - private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink) + private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item) { - // cannot stack do-afters - if (drink.CancelToken != null) - { - return true; - } - if (!EntityManager.HasComponent(target)) return false; if (!drink.Opened) { _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-not-open", - ("owner", EntityManager.GetComponent(drink.Owner).EntityName)), drink.Owner, user); + ("owner", EntityManager.GetComponent(item).EntityName)), item, user); return true; } - if (!_solutionContainerSystem.TryGetDrainableSolution(drink.Owner, out var drinkSolution) || + if (!_solutionContainerSystem.TryGetDrainableSolution(item, out var drinkSolution) || drinkSolution.Volume <= 0) { _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-is-empty", - ("entity", EntityManager.GetComponent(drink.Owner).EntityName)), drink.Owner, user); + ("entity", EntityManager.GetComponent(item).EntityName)), item, user); return true; } if (_foodSystem.IsMouthBlocked(target, user)) return true; - if (!_interactionSystem.InRangeUnobstructed(user, drink.Owner, popup: true)) + if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true)) return true; var forceDrink = user != target; @@ -254,20 +251,21 @@ namespace Content.Server.Nutrition.EntitySystems user, target); // logging - _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(drink.Owner):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}"); + _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to drink {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}"); } else { // log voluntary drinking - _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(drink.Owner):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}"); + _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}"); } - - drink.CancelToken = new CancellationTokenSource(); var moveBreak = user != target; var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(user, drinkSolution); - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay, drink.CancelToken.Token, target, drink.Owner) + var drinkData = new DrinkData(drinkSolution, flavors); + + var doAfterEventArgs = new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay, + target: target, used: item) { BreakOnUserMove = moveBreak, BreakOnDamage = true, @@ -275,10 +273,10 @@ namespace Content.Server.Nutrition.EntitySystems BreakOnTargetMove = moveBreak, MovementThreshold = 0.01f, DistanceThreshold = 1.0f, - TargetFinishedEvent = new DrinkEvent(user, drink, drinkSolution, flavors), - BroadcastCancelledEvent = new DrinkCancelledEvent(drink), - NeedHand = true, - }); + NeedHand = true + }; + + _doAfterSystem.DoAfter(doAfterEventArgs, drinkData); return true; } @@ -286,103 +284,93 @@ namespace Content.Server.Nutrition.EntitySystems /// /// Raised directed at a victim when someone has force fed them a drink. /// - private void OnDrink(EntityUid uid, BodyComponent body, DrinkEvent args) + private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent args) { - if (args.Drink.Deleted) + if (args.Handled || args.Cancelled || component.Deleted) return; - args.Drink.CancelToken = null; - var transferAmount = FixedPoint2.Min(args.Drink.TransferAmount, args.DrinkSolution.Volume); - var drained = _solutionContainerSystem.Drain(args.Drink.Owner, args.DrinkSolution, transferAmount); + if (!TryComp(args.Args.Target, out var body)) + return; - var forceDrink = uid != args.User; + var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume); + var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount); - if (!_bodySystem.TryGetBodyOrganComponents(uid, out var stomachs, body)) + var forceDrink = args.Args.Target.Value != args.Args.User; + + if (!_bodySystem.TryGetBodyOrganComponents(args.Args.Target.Value, out var stomachs, body)) { - _popupSystem.PopupEntity( - forceDrink ? - Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : - Loc.GetString("drink-component-try-use-drink-had-enough"), - uid, args.User); + _popupSystem.PopupEntity(forceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User); - if (EntityManager.HasComponent(uid)) + if (HasComp(args.Args.Target.Value)) { - _spillableSystem.SpillAt(args.User, drained, "PuddleSmear"); + _spillableSystem.SpillAt(args.Args.User, drained, "PuddleSmear"); + args.Handled = true; return; } - _solutionContainerSystem.Refill(uid, args.DrinkSolution, drained); + _solutionContainerSystem.Refill(args.Args.Target.Value, args.AdditionalData.DrinkSolution, drained); + args.Handled = true; return; } - var firstStomach = stomachs.FirstOrNull( - stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, drained)); + var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, drained)); - // All stomach are full or can't handle whatever solution we have. + //All stomachs are full or can't handle whatever solution we have. if (firstStomach == null) { - _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), - uid, uid); + _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value); if (forceDrink) { - _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), - uid, args.User); - _spillableSystem.SpillAt(uid, drained, "PuddleSmear"); + _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), args.Args.Target.Value, args.Args.User); + _spillableSystem.SpillAt(args.Args.Target.Value, drained, "PuddleSmear"); } else - { - _solutionContainerSystem.TryAddSolution(args.Drink.Owner, args.DrinkSolution, drained); - } + _solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.DrinkSolution, drained); + args.Handled = true; return; } - var flavors = args.FlavorMessage; + var flavors = args.AdditionalData.FlavorMessage; if (forceDrink) { - var targetName = Identity.Entity(uid, EntityManager); - var userName = Identity.Entity(args.User, EntityManager); + var targetName = Identity.Entity(args.Args.Target.Value, EntityManager); + var userName = Identity.Entity(args.Args.User, EntityManager); - _popupSystem.PopupEntity( - Loc.GetString("drink-component-force-feed-success", ("user", userName), ("flavors", flavors)), uid, uid); + _popupSystem.PopupEntity(Loc.GetString("drink-component-force-feed-success", ("user", userName), ("flavors", flavors)), args.Args.Target.Value, args.Args.Target.Value); _popupSystem.PopupEntity( Loc.GetString("drink-component-force-feed-success-user", ("target", targetName)), - args.User, args.User); + args.Args.User, args.Args.User); // log successful forced drinking - _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(args.User):user} forced {ToPrettyString(uid):target} to drink {ToPrettyString(args.Drink.Owner):drink}"); + _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to drink {ToPrettyString(component.Owner):drink}"); } else { _popupSystem.PopupEntity( - Loc.GetString("drink-component-try-use-drink-success-slurp-taste", ("flavors", flavors)), args.User, - args.User); + Loc.GetString("drink-component-try-use-drink-success-slurp-taste", ("flavors", flavors)), args.Args.User, + args.Args.User); _popupSystem.PopupEntity( - Loc.GetString("drink-component-try-use-drink-success-slurp"), args.User, Filter.PvsExcept(args.User), true); + Loc.GetString("drink-component-try-use-drink-success-slurp"), args.Args.User, Filter.PvsExcept(args.Args.User), true); // log successful voluntary drinking - _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} drank {ToPrettyString(args.Drink.Owner):drink}"); + _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.Args.User):target} drank {ToPrettyString(uid):drink}"); } - _audio.PlayPvs(_audio.GetSound(args.Drink.UseSound), uid, AudioParams.Default.WithVolume(-2f)); + _audio.PlayPvs(_audio.GetSound(component.UseSound), args.Args.Target.Value, AudioParams.Default.WithVolume(-2f)); - drained.DoEntityReaction(uid, ReactionMethod.Ingestion); + _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.DrinkSolution, ReactionMethod.Ingestion); + //TODO: Grab the stomach UIDs somehow without using Owner _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp); - } - private static void OnDrinkCancelled(DrinkCancelledEvent args) - { - args.Drink.CancelToken = null; + args.Handled = true; } private void AddDrinkVerb(EntityUid uid, DrinkComponent component, GetVerbsEvent ev) { - if (component.CancelToken != null) - return; - if (uid == ev.User || !ev.CanInteract || !ev.CanAccess || @@ -397,7 +385,7 @@ namespace Content.Server.Nutrition.EntitySystems { Act = () => { - TryDrink(ev.User, ev.User, component); + TryDrink(ev.User, ev.User, component, uid); }, IconTexture = "/Textures/Interface/VerbIcons/drink.svg.192dpi.png", Text = Loc.GetString("drink-system-verb-drink"), @@ -418,5 +406,11 @@ namespace Content.Server.Nutrition.EntitySystems return remainingString; } + + private record struct DrinkData(Solution DrinkSolution, string FlavorMessage) + { + public readonly Solution DrinkSolution = DrinkSolution; + public readonly string FlavorMessage = FlavorMessage; + } } } diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 59a744cbae..0768313eb1 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; @@ -8,8 +7,11 @@ using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.Administration.Logs; using Content.Shared.Body.Components; +using Content.Shared.Chemistry; +using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; @@ -42,6 +44,8 @@ namespace Content.Server.Nutrition.EntitySystems [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly ReactiveSystem _reaction = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { @@ -50,8 +54,7 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent(OnUseFoodInHand); SubscribeLocalEvent(OnFeedFood); SubscribeLocalEvent>(AddEatVerb); - SubscribeLocalEvent(OnFeed); - SubscribeLocalEvent(OnFeedCancelled); + SubscribeLocalEvent>(OnDoAfter); SubscribeLocalEvent(OnInventoryIngestAttempt); } @@ -63,7 +66,7 @@ namespace Content.Server.Nutrition.EntitySystems if (ev.Handled) return; - ev.Handled = TryFeed(ev.User, ev.User, foodComponent); + ev.Handled = TryFeed(ev.User, ev.User, uid, foodComponent); } /// @@ -74,49 +77,41 @@ namespace Content.Server.Nutrition.EntitySystems if (args.Handled || args.Target == null || !args.CanReach) return; - args.Handled = TryFeed(args.User, args.Target.Value, foodComponent); + args.Handled = TryFeed(args.User, args.Target.Value, uid, foodComponent); } - public bool TryFeed(EntityUid user, EntityUid target, FoodComponent food) + public bool TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp) { - // if currently being used to feed, cancel that action. - if (food.CancelToken != null) - { - return true; - } - - if (food.Owner == user || //Suppresses self-eating - EntityManager.TryGetComponent(food.Owner, out var mobState) && _mobStateSystem.IsAlive(food.Owner, mobState)) // Suppresses eating alive mobs + //Suppresses self-eating + if (food == user || EntityManager.TryGetComponent(food, out var mobState) && _mobStateSystem.IsAlive(food, mobState)) // Suppresses eating alive mobs return false; // Target can't be fed if (!EntityManager.HasComponent(target)) return false; - if (!_solutionContainerSystem.TryGetSolution(food.Owner, food.SolutionName, out var foodSolution)) + if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution)) return false; - var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food.Owner, user, foodSolution); + var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food, user, foodSolution); - if (food.UsesRemaining <= 0) + if (foodComp.UsesRemaining <= 0) { - _popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", - ("entity", food.Owner)), user, user); - DeleteAndSpawnTrash(food, user); + _popupSystem.PopupEntity(Loc.GetString("food-system-try-use-food-is-empty", ("entity", food)), user, user); + DeleteAndSpawnTrash(foodComp, food, user); return false; } if (IsMouthBlocked(target, user)) return false; - if (!TryGetRequiredUtensils(user, food, out var utensils)) + if (!TryGetRequiredUtensils(user, foodComp, out var utensils)) return false; - if (!_interactionSystem.InRangeUnobstructed(user, food.Owner, popup: true)) + if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true)) return true; var forceFeed = user != target; - food.CancelToken = new CancellationTokenSource(); if (forceFeed) { @@ -125,17 +120,19 @@ namespace Content.Server.Nutrition.EntitySystems user, target); // logging - _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food.Owner):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}"); + _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}"); } else { // log voluntary eating - _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food.Owner):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}"); + _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}"); } var moveBreak = user != target; - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, forceFeed ? food.ForceFeedDelay : food.Delay, food.CancelToken.Token, target, food.Owner) + var foodData = new FoodData(foodSolution, flavors, utensils); + + var doAfterEventArgs = new DoAfterEventArgs(user, forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food) { BreakOnUserMove = moveBreak, BreakOnDamage = true, @@ -143,116 +140,114 @@ namespace Content.Server.Nutrition.EntitySystems BreakOnTargetMove = moveBreak, MovementThreshold = 0.01f, DistanceThreshold = 1.0f, - TargetFinishedEvent = new FeedEvent(user, food, foodSolution, flavors, utensils), - BroadcastCancelledEvent = new ForceFeedCancelledEvent(food), - NeedHand = true, - }); + NeedHand = true + }; + + _doAfterSystem.DoAfter(doAfterEventArgs, foodData); return true; } - private void OnFeed(EntityUid uid, BodyComponent body, FeedEvent args) + private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent args) { - if (args.Food.Deleted) + if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null) return; - args.Food.CancelToken = null; - - if (!_bodySystem.TryGetBodyOrganComponents(uid, out var stomachs, body)) + if (!TryComp(args.Args.Target.Value, out var body)) return; - var transferAmount = args.Food.TransferAmount != null - ? FixedPoint2.Min((FixedPoint2) args.Food.TransferAmount, args.FoodSolution.Volume) - : args.FoodSolution.Volume; + if (!_bodySystem.TryGetBodyOrganComponents(args.Args.Target.Value, out var stomachs, body)) + return; - var split = _solutionContainerSystem.SplitSolution((args.Food).Owner, args.FoodSolution, transferAmount); + var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, args.AdditionalData.FoodSolution.Volume) : args.AdditionalData.FoodSolution.Volume; + var split = _solutionContainerSystem.SplitSolution(uid, args.AdditionalData.FoodSolution, transferAmount); + //TODO: Get the stomach UID somehow without nabbing owner + var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split)); - var firstStomach = stomachs.FirstOrNull( - stomach => _stomachSystem.CanTransferSolution((stomach.Comp).Owner, split)); - - var forceFeed = uid != args.User; + var forceFeed = args.Args.Target.Value != args.Args.User; // No stomach so just popup a message that they can't eat. if (firstStomach == null) { - _solutionContainerSystem.TryAddSolution(uid, args.FoodSolution, split); - _popupSystem.PopupEntity( - forceFeed ? - Loc.GetString("food-system-you-cannot-eat-any-more-other") : - Loc.GetString("food-system-you-cannot-eat-any-more") - , uid, args.User); + _solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.FoodSolution, split); + _popupSystem.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User); + args.Handled = true; return; } - split.DoEntityReaction(uid, ReactionMethod.Ingestion); + _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.FoodSolution, ReactionMethod.Ingestion); _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp); - var flavors = args.FlavorMessage; + var flavors = args.AdditionalData.FlavorMessage; if (forceFeed) { - var targetName = Identity.Entity(uid, EntityManager); - var userName = Identity.Entity(args.User, EntityManager); + var targetName = Identity.Entity(args.Args.Target.Value, EntityManager); + var userName = Identity.Entity(args.Args.User, EntityManager); _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success", ("user", userName), ("flavors", flavors)), uid, uid); - _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), - args.User, args.User); + _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed-success-user", ("target", targetName)), args.Args.User, args.Args.User); // log successful force feed - _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(args.User):user} forced {ToPrettyString(uid):target} to eat {ToPrettyString(args.Food.Owner):food}"); + _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to eat {ToPrettyString(uid):food}"); } else { - _popupSystem.PopupEntity(Loc.GetString(args.Food.EatMessage, ("food", args.Food.Owner), ("flavors", flavors)), args.User, args.User); + _popupSystem.PopupEntity(Loc.GetString(component.EatMessage, ("foodComp", uid), ("flavors", flavors)), args.Args.User, args.Args.User); // log successful voluntary eating - _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(args.Food.Owner):food}"); + _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.Args.User):target} ate {ToPrettyString(uid):food}"); } - SoundSystem.Play(args.Food.UseSound.GetSound(), Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-1f)); + _audio.Play(component.UseSound, Filter.Pvs(args.Args.Target.Value), args.Args.Target.Value, true, AudioParams.Default.WithVolume(-1f)); // Try to break all used utensils - foreach (var utensil in args.Utensils) + //TODO: Replace utensil owner with actual UID + foreach (var utensil in args.AdditionalData.Utensils) { - _utensilSystem.TryBreak((utensil).Owner, args.User); + _utensilSystem.TryBreak(utensil.Owner, args.Args.User); } - if (args.Food.UsesRemaining > 0) + if (component.UsesRemaining > 0) + { + args.Handled = true; return; + } + + + if (string.IsNullOrEmpty(component.TrashPrototype)) + EntityManager.QueueDeleteEntity(uid); - if (string.IsNullOrEmpty(args.Food.TrashPrototype)) - EntityManager.QueueDeleteEntity(args.Food.Owner); else - DeleteAndSpawnTrash(args.Food, args.User); + DeleteAndSpawnTrash(component, uid, args.Args.User); + + args.Handled = true; } - private void DeleteAndSpawnTrash(FoodComponent component, EntityUid? user = null) + private void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid? user = null) { //We're empty. Become trash. - var position = Transform(component.Owner).MapPosition; + var position = Transform(food).MapPosition; var finisher = EntityManager.SpawnEntity(component.TrashPrototype, position); // If the user is holding the item - if (user != null && _handsSystem.IsHolding(user.Value, component.Owner, out var hand)) + if (user != null && _handsSystem.IsHolding(user.Value, food, out var hand)) { - EntityManager.DeleteEntity((component).Owner); + EntityManager.DeleteEntity(food); // Put the trash in the user's hand _handsSystem.TryPickup(user.Value, finisher, hand); return; } - EntityManager.QueueDeleteEntity(component.Owner); + EntityManager.QueueDeleteEntity(food); } private void AddEatVerb(EntityUid uid, FoodComponent component, GetVerbsEvent ev) { - if (component.CancelToken != null) - return; - if (uid == ev.User || !ev.CanInteract || !ev.CanAccess || @@ -267,7 +262,7 @@ namespace Content.Server.Nutrition.EntitySystems { Act = () => { - TryFeed(ev.User, ev.User, component); + TryFeed(ev.User, ev.User, uid, component); }, IconTexture = "/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png", Text = Loc.GetString("food-system-verb-eat"), @@ -296,7 +291,7 @@ namespace Content.Server.Nutrition.EntitySystems return; if (food.UsesRemaining <= 0) - DeleteAndSpawnTrash(food); + DeleteAndSpawnTrash(food, uid); var firstStomach = stomachs.FirstOrNull( stomach => _stomachSystem.CanTransferSolution(((IComponent) stomach.Comp).Owner, foodSolution)); @@ -320,7 +315,7 @@ namespace Content.Server.Nutrition.EntitySystems if (string.IsNullOrEmpty(food.TrashPrototype)) EntityManager.QueueDeleteEntity(food.Owner); else - DeleteAndSpawnTrash(food); + DeleteAndSpawnTrash(food, uid); } private bool TryGetRequiredUtensils(EntityUid user, FoodComponent component, @@ -361,11 +356,6 @@ namespace Content.Server.Nutrition.EntitySystems return true; } - private static void OnFeedCancelled(ForceFeedCancelledEvent args) - { - args.Food.CancelToken = null; - } - /// /// Block ingestion attempts based on the equipped mask or head-wear /// @@ -415,5 +405,12 @@ namespace Content.Server.Nutrition.EntitySystems return attempt.Cancelled; } + + private record struct FoodData(Solution FoodSolution, string FlavorMessage, List Utensils) + { + public readonly Solution FoodSolution = FoodSolution; + public readonly string FlavorMessage = FlavorMessage; + public readonly List Utensils = Utensils; + } } } diff --git a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs index 3a3b9918c4..4b2ab316a1 100644 --- a/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/UtensilSystem.cs @@ -44,14 +44,14 @@ namespace Content.Server.Nutrition.EntitySystems //Prevents food usage with a wrong utensil if ((food.Utensil & component.Types) == 0) { - _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", food.Owner), ("utensil", component.Owner)), user, user); + _popupSystem.PopupEntity(Loc.GetString("food-system-wrong-utensil", ("food", target), ("utensil", component.Owner)), user, user); return false; } if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true)) return false; - return _foodSystem.TryFeed(user, user, food); + return _foodSystem.TryFeed(user, user, target, food); } /// diff --git a/Content.Server/Nutrition/IngestionEvents.cs b/Content.Server/Nutrition/IngestionEvents.cs index 3c902a6819..c9668ad9db 100644 --- a/Content.Server/Nutrition/IngestionEvents.cs +++ b/Content.Server/Nutrition/IngestionEvents.cs @@ -1,6 +1,3 @@ -using Content.Server.Nutrition.Components; -using Content.Shared.Chemistry.Components; - namespace Content.Server.Nutrition; /// @@ -13,69 +10,3 @@ public sealed class IngestionAttemptEvent : CancellableEntityEventArgs /// public EntityUid? Blocker = null; } - -/// -/// Raised directed at the food after a successful feed do-after. -/// -public sealed class FeedEvent : EntityEventArgs -{ - public readonly EntityUid User; - public readonly FoodComponent Food; - public readonly Solution FoodSolution; - public readonly string FlavorMessage; - public readonly List Utensils; - - public FeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, string flavorMessage, List utensils) - { - User = user; - Food = food; - FoodSolution = foodSolution; - FlavorMessage = flavorMessage; - Utensils = utensils; - } -} - -/// -/// Raised directed at the food after a failed force-feed do-after. -/// -public sealed class ForceFeedCancelledEvent : EntityEventArgs -{ - public readonly FoodComponent Food; - - public ForceFeedCancelledEvent(FoodComponent food) - { - Food = food; - } -} - -/// -/// Raised directed at the drink after a successful force-drink do-after. -/// -public sealed class DrinkEvent : EntityEventArgs -{ - public readonly EntityUid User; - public readonly DrinkComponent Drink; - public readonly Solution DrinkSolution; - public readonly string FlavorMessage; - - public DrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution, string flavorMessage) - { - User = user; - Drink = drink; - DrinkSolution = drinkSolution; - FlavorMessage = flavorMessage; - } -} - -/// -/// Raised directed at the food after a failed force-dink do-after. -/// -public sealed class DrinkCancelledEvent : EntityEventArgs -{ - public readonly DrinkComponent Drink; - - public DrinkCancelledEvent(DrinkComponent drink) - { - Drink = drink; - } -} diff --git a/Content.Server/Power/EntitySystems/ApcSystem.cs b/Content.Server/Power/EntitySystems/ApcSystem.cs index 45ad5a6aca..8ce9d26ec0 100644 --- a/Content.Server/Power/EntitySystems/ApcSystem.cs +++ b/Content.Server/Power/EntitySystems/ApcSystem.cs @@ -1,7 +1,5 @@ using Content.Server.Popups; using Content.Server.Power.Components; -using Content.Server.Tools; -using Content.Server.Wires; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.APC; @@ -10,8 +8,8 @@ using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Tools; using Content.Shared.Tools.Components; -using Content.Shared.Wires; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -27,7 +25,7 @@ namespace Content.Server.Power.EntitySystems [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; private const float ScrewTime = 2f; @@ -201,10 +199,11 @@ namespace Content.Server.Power.EntitySystems { if (!EntityManager.TryGetComponent(args.Used, out ToolComponent? tool)) return; - if (_toolSystem.UseTool(args.Used, args.User, uid, 0f, ScrewTime, new[] { "Screwing" }, doAfterCompleteEvent: new ApcToolFinishedEvent(uid), toolComponent: tool)) - { + + var toolEvData = new ToolEventData(new ApcToolFinishedEvent(uid), fuel: 0f); + + if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new [] { "Screwing" }, toolEvData, toolComponent:tool)) args.Handled = true; - } } private void OnToolFinished(ApcToolFinishedEvent args) @@ -219,13 +218,9 @@ namespace Content.Server.Power.EntitySystems } if (component.IsApcOpen) - { SoundSystem.Play(component.ScrewdriverOpenSound.GetSound(), Filter.Pvs(args.Target), args.Target); - } else - { SoundSystem.Play(component.ScrewdriverCloseSound.GetSound(), Filter.Pvs(args.Target), args.Target); - } } private void UpdatePanelAppearance(EntityUid uid, AppearanceComponent? appearance = null, ApcComponent? apc = null) diff --git a/Content.Server/Power/EntitySystems/CableSystem.cs b/Content.Server/Power/EntitySystems/CableSystem.cs index 4d795edc8a..eead07f34d 100644 --- a/Content.Server/Power/EntitySystems/CableSystem.cs +++ b/Content.Server/Power/EntitySystems/CableSystem.cs @@ -2,9 +2,10 @@ using Content.Server.Administration.Logs; using Content.Server.Electrocution; using Content.Server.Power.Components; using Content.Server.Stack; -using Content.Server.Tools; using Content.Shared.Database; using Content.Shared.Interaction; +using Content.Shared.Tools; +using Content.Shared.Tools.Components; using Robust.Shared.Map; namespace Content.Server.Power.EntitySystems; @@ -13,7 +14,7 @@ public sealed partial class CableSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly ITileDefinitionManager _tileManager = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogs = default!; @@ -35,8 +36,8 @@ public sealed partial class CableSystem : EntitySystem if (args.Handled) return; - var ev = new CuttingFinishedEvent(args.User); - args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, 0, cable.CuttingDelay, new[] { cable.CuttingQuality }, doAfterCompleteEvent: ev, doAfterEventTarget: uid); + var toolEvData = new ToolEventData(new CuttingFinishedEvent(args.User), targetEntity: uid); + args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, new[] { cable.CuttingQuality }, toolEvData); } private void OnCableCut(EntityUid uid, CableComponent cable, CuttingFinishedEvent args) diff --git a/Content.Server/RCD/Systems/RCDSystem.cs b/Content.Server/RCD/Systems/RCDSystem.cs index 9193b7650f..38c4e0f36a 100644 --- a/Content.Server/RCD/Systems/RCDSystem.cs +++ b/Content.Server/RCD/Systems/RCDSystem.cs @@ -4,6 +4,7 @@ using Content.Server.DoAfter; using Content.Server.Popups; using Content.Server.RCD.Components; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; diff --git a/Content.Server/Repairable/RepairableSystem.cs b/Content.Server/Repairable/RepairableSystem.cs index 948640f470..6013d26601 100644 --- a/Content.Server/Repairable/RepairableSystem.cs +++ b/Content.Server/Repairable/RepairableSystem.cs @@ -4,12 +4,14 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Tools; +using Content.Shared.Tools.Components; namespace Content.Server.Repairable { public sealed class RepairableSystem : EntitySystem { - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogger= default!; @@ -30,8 +32,10 @@ namespace Content.Server.Repairable if (args.User == args.Target) delay *= component.SelfRepairPenalty; + var toolEvData = new ToolEventData(null); + // Can the tool actually repair this, does it have enough fuel? - if (!await _toolSystem.UseTool(args.Used, args.User, uid, component.FuelCost, delay, component.QualityNeeded)) + if (!_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, toolEvData, component.FuelCost)) return; if (component.Damage != null) diff --git a/Content.Server/Resist/CanEscapeInventoryComponent.cs b/Content.Server/Resist/CanEscapeInventoryComponent.cs index a41786e9b6..aff5a0b0e3 100644 --- a/Content.Server/Resist/CanEscapeInventoryComponent.cs +++ b/Content.Server/Resist/CanEscapeInventoryComponent.cs @@ -1,5 +1,3 @@ -using System.Threading; - namespace Content.Server.Resist; [RegisterComponent] @@ -10,9 +8,4 @@ public sealed class CanEscapeInventoryComponent : Component /// [DataField("baseResistTime")] public float BaseResistTime = 5f; - - /// - /// Cancellation token used to cancel the DoAfter if the mob is removed before it's complete - /// - public CancellationTokenSource? CancelToken; } diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs index 18546e07f0..2e3fc66ea8 100644 --- a/Content.Server/Resist/EscapeInventorySystem.cs +++ b/Content.Server/Resist/EscapeInventorySystem.cs @@ -2,11 +2,11 @@ using Content.Server.DoAfter; using Content.Server.Contests; using Robust.Shared.Containers; using Content.Server.Popups; -using Robust.Shared.Player; using Content.Shared.Storage; using Content.Shared.Inventory; using Content.Shared.Hands.EntitySystems; using Content.Shared.ActionBlocker; +using Content.Shared.DoAfter; using Content.Shared.Movement.Events; using Content.Shared.Interaction.Events; @@ -31,16 +31,12 @@ public sealed class EscapeInventorySystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnRelayMovement); - SubscribeLocalEvent(OnEscapeComplete); - SubscribeLocalEvent(OnEscapeFail); + SubscribeLocalEvent>(OnEscape); SubscribeLocalEvent(OnDropped); } private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args) { - if (component.CancelToken != null) - return; - if (!_containerSystem.TryGetContainingContainer(uid, out var container) || !_actionBlockerSystem.CanInteract(uid, container.Owner)) return; @@ -69,41 +65,38 @@ public sealed class EscapeInventorySystem : EntitySystem private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component, float multiplier = 1f) { - component.CancelToken = new(); - var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, component.CancelToken.Token, container) + var escapeEvent = new EscapeInventoryEvent(); + var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, target:container) { BreakOnTargetMove = false, BreakOnUserMove = false, BreakOnDamage = true, BreakOnStun = true, - NeedHand = false, - UserFinishedEvent = new EscapeDoAfterComplete(), - UserCancelledEvent = new EscapeDoAfterCancel(), + NeedHand = false }; _popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, user); _popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, container); - _doAfterSystem.DoAfter(doAfterEventArgs); + _doAfterSystem.DoAfter(doAfterEventArgs, escapeEvent); } - private void OnEscapeComplete(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterComplete ev) + private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, DoAfterEvent args) { - //Drops the mob on the tile below the container + if (args.Handled || args.Cancelled) + return; + Transform(uid).AttachParentToContainerOrGrid(EntityManager); - component.CancelToken = null; - } - private void OnEscapeFail(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterCancel ev) - { - component.CancelToken = null; + args.Handled = true; } private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, DroppedEvent args) { - component.CancelToken?.Cancel(); + //TODO: Enter cancel logic here } - private sealed class EscapeDoAfterComplete : EntityEventArgs { } + private sealed class EscapeInventoryEvent : EntityEventArgs + { - private sealed class EscapeDoAfterCancel : EntityEventArgs { } + } } diff --git a/Content.Server/Resist/ResistLockerComponent.cs b/Content.Server/Resist/ResistLockerComponent.cs index 7c3e7ff201..6cf4102d45 100644 --- a/Content.Server/Resist/ResistLockerComponent.cs +++ b/Content.Server/Resist/ResistLockerComponent.cs @@ -19,7 +19,7 @@ public sealed class ResistLockerComponent : Component public bool IsResisting = false; /// - /// Cancellation token used to cancel the DoAfter if the container is opened before it's complete + /// Used to cancel the DoAfter when a locker is open /// - public CancellationTokenSource? CancelToken; + public Shared.DoAfter.DoAfter? DoAfter; } diff --git a/Content.Server/Resist/ResistLockerSystem.cs b/Content.Server/Resist/ResistLockerSystem.cs index 7230b496dc..85b4176d9c 100644 --- a/Content.Server/Resist/ResistLockerSystem.cs +++ b/Content.Server/Resist/ResistLockerSystem.cs @@ -1,11 +1,12 @@ -using Content.Server.Storage.Components; using Content.Server.DoAfter; -using Robust.Shared.Containers; using Content.Server.Popups; -using Content.Shared.Movement.Events; +using Content.Server.Storage.Components; using Content.Server.Storage.EntitySystems; +using Content.Shared.DoAfter; using Content.Shared.Lock; +using Content.Shared.Movement.Events; using Content.Shared.Popups; +using Robust.Shared.Containers; namespace Content.Server.Resist; @@ -20,9 +21,8 @@ public sealed class ResistLockerSystem : EntitySystem { base.Initialize(); SubscribeLocalEvent(OnRelayMovement); - SubscribeLocalEvent(OnDoAfterComplete); - SubscribeLocalEvent(OnDoAfterCancelled); - SubscribeLocalEvent(OnRemovedFromContainer); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnRemoved); } private void OnRelayMovement(EntityUid uid, ResistLockerComponent component, ref ContainerRelayMovementEntityEvent args) @@ -44,25 +44,38 @@ public sealed class ResistLockerSystem : EntitySystem if (!Resolve(target, ref storageComponent, ref resistLockerComponent)) return; - resistLockerComponent.CancelToken = new(); - var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, resistLockerComponent.CancelToken.Token, target) + var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, target:target) { BreakOnTargetMove = false, BreakOnUserMove = true, BreakOnDamage = true, BreakOnStun = true, - NeedHand = false, //No hands 'cause we be kickin' - TargetFinishedEvent = new ResistDoAfterComplete(user, target), - TargetCancelledEvent = new ResistDoAfterCancelled(user) + NeedHand = false //No hands 'cause we be kickin' }; resistLockerComponent.IsResisting = true; _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-start-resisting"), user, user, PopupType.Large); - _doAfterSystem.DoAfter(doAfterEventArgs); + resistLockerComponent.DoAfter = _doAfterSystem.DoAfter(doAfterEventArgs); } - private void OnDoAfterComplete(EntityUid uid, ResistLockerComponent component, ResistDoAfterComplete ev) + private void OnRemoved(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage args) { + if (component.DoAfter != null) + _doAfterSystem.Cancel(uid, component.DoAfter); + } + + private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent args) + { + if (args.Cancelled) + { + component.IsResisting = false; + _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), args.Args.User, args.Args.User, PopupType.Medium); + return; + } + + if (args.Handled || args.Args.Target == null) + return; + component.IsResisting = false; if (TryComp(uid, out var storageComponent)) @@ -70,44 +83,12 @@ public sealed class ResistLockerSystem : EntitySystem if (storageComponent.IsWeldedShut) storageComponent.IsWeldedShut = false; - if (TryComp(ev.Target, out var lockComponent)) - _lockSystem.Unlock(uid, ev.User, lockComponent); + if (TryComp(args.Args.Target.Value, out var lockComponent)) + _lockSystem.Unlock(uid, args.Args.User, lockComponent); - component.CancelToken = null; - _entityStorage.TryOpenStorage(ev.User, storageComponent.Owner); + _entityStorage.TryOpenStorage(args.Args.User, uid); } - } - private void OnDoAfterCancelled(EntityUid uid, ResistLockerComponent component, ResistDoAfterCancelled ev) - { - component.IsResisting = false; - component.CancelToken = null; - _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), ev.User, ev.User, PopupType.Medium); - } - - private void OnRemovedFromContainer(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage message) - { - component.CancelToken?.Cancel(); - } - - private sealed class ResistDoAfterComplete : EntityEventArgs - { - public readonly EntityUid User; - public readonly EntityUid Target; - public ResistDoAfterComplete(EntityUid userUid, EntityUid target) - { - User = userUid; - Target = target; - } - } - - private sealed class ResistDoAfterCancelled : EntityEventArgs - { - public readonly EntityUid User; - - public ResistDoAfterCancelled(EntityUid userUid) - { - User = userUid; - } + args.Handled = true; } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index ee5d603edd..7c3b1508e7 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -1,6 +1,5 @@ using Content.Shared.Popups; using Content.Shared.Damage; -using Content.Server.DoAfter; using Content.Shared.Revenant; using Robust.Shared.Random; using Robust.Shared.Map; @@ -19,6 +18,7 @@ using Content.Shared.Bed.Sleep; using System.Linq; using Content.Server.Maps; using Content.Server.Revenant.Components; +using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; @@ -46,10 +46,8 @@ public sealed partial class RevenantSystem private void InitializeAbilities() { SubscribeLocalEvent(OnInteract); - SubscribeLocalEvent(OnSoulSearchComplete); - SubscribeLocalEvent(OnSoulSearchCancelled); - SubscribeLocalEvent(OnHarvestComplete); - SubscribeLocalEvent(OnHarvestCancelled); + SubscribeLocalEvent>(OnSoulSearch); + SubscribeLocalEvent>(OnHarvest); SubscribeLocalEvent(OnDefileAction); SubscribeLocalEvent(OnOverloadLightsAction); @@ -86,26 +84,23 @@ public sealed partial class RevenantSystem private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant) { - if (revenant.SoulSearchCancelToken != null) - return; - _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium); - revenant.SoulSearchCancelToken = new(); - var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, revenant.SoulSearchCancelToken.Token, target) + var soulSearchEvent = new SoulEvent(); + var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, target:target) { BreakOnUserMove = true, - DistanceThreshold = 2, - UserFinishedEvent = new SoulSearchDoAfterComplete(target), - UserCancelledEvent = new SoulSearchDoAfterCancelled(), + DistanceThreshold = 2 }; - _doAfter.DoAfter(searchDoAfter); + _doAfter.DoAfter(searchDoAfter, soulSearchEvent); } - private void OnSoulSearchComplete(EntityUid uid, RevenantComponent component, SoulSearchDoAfterComplete args) + private void OnSoulSearch(EntityUid uid, RevenantComponent component, DoAfterEvent args) { - if (!TryComp(args.Target, out var essence)) + if (args.Handled || args.Cancelled) + return; + + if (!TryComp(args.Args.Target, out var essence)) return; - component.SoulSearchCancelToken = null; essence.SearchComplete = true; string message; @@ -121,19 +116,13 @@ public sealed partial class RevenantSystem message = "revenant-soul-yield-average"; break; } - _popup.PopupEntity(Loc.GetString(message, ("target", args.Target)), args.Target, uid, PopupType.Medium); - } + _popup.PopupEntity(Loc.GetString(message, ("target", args.Args.Target)), args.Args.Target.Value, uid, PopupType.Medium); - private void OnSoulSearchCancelled(EntityUid uid, RevenantComponent component, SoulSearchDoAfterCancelled args) - { - component.SoulSearchCancelToken = null; + args.Handled = true; } private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence) { - if (revenant.HarvestCancelToken != null) - return; - if (essence.Harvested) { _popup.PopupEntity(Loc.GetString("revenant-soul-harvested"), target, uid, PopupType.SmallCaution); @@ -146,14 +135,13 @@ public sealed partial class RevenantSystem return; } - revenant.HarvestCancelToken = new(); - var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, revenant.HarvestCancelToken.Token, target) + var harvestEvent = new HarvestEvent(); + + var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, target:target) { DistanceThreshold = 2, BreakOnUserMove = true, - NeedHand = false, - UserFinishedEvent = new HarvestDoAfterComplete(target), - UserCancelledEvent = new HarvestDoAfterCancelled(), + NeedHand = false }; _appearance.SetData(uid, RevenantVisuals.Harvesting, true); @@ -162,29 +150,37 @@ public sealed partial class RevenantSystem target, PopupType.Large); TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs); - _doAfter.DoAfter(doAfter); + _doAfter.DoAfter(doAfter, harvestEvent); } - private void OnHarvestComplete(EntityUid uid, RevenantComponent component, HarvestDoAfterComplete args) + private void OnHarvest(EntityUid uid, RevenantComponent component, DoAfterEvent args) { - component.HarvestCancelToken = null; - _appearance.SetData(uid, RevenantVisuals.Harvesting, false); + if (args.Cancelled) + { + _appearance.SetData(uid, RevenantVisuals.Harvesting, false); + return; + } - if (!TryComp(args.Target, out var essence)) + if (args.Handled || args.Args.Target == null) return; - _popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Target)), - args.Target, PopupType.LargeCaution); + _appearance.SetData(uid, RevenantVisuals.Harvesting, false); + + if (!TryComp(args.Args.Target, out var essence)) + return; + + _popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Args.Target)), + args.Args.Target.Value, PopupType.LargeCaution); essence.Harvested = true; ChangeEssenceAmount(uid, essence.EssenceAmount, component); _store.TryAddCurrency(new Dictionary { {component.StolenEssenceCurrencyPrototype, essence.EssenceAmount} }, uid); - if (!HasComp(args.Target)) + if (!HasComp(args.Args.Target)) return; - if (_mobState.IsAlive(args.Target) || _mobState.IsCritical(args.Target)) + if (_mobState.IsAlive(args.Args.Target.Value) || _mobState.IsCritical(args.Args.Target.Value)) { _popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid); component.EssenceRegenCap += component.MaxEssenceUpgradeAmount; @@ -192,17 +188,13 @@ public sealed partial class RevenantSystem //KILL THEMMMM - if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Dead, out var damage)) + if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage)) return; DamageSpecifier dspec = new(); dspec.DamageDict.Add("Poison", damage.Value); - _damage.TryChangeDamage(args.Target, dspec, true, origin: uid); - } + _damage.TryChangeDamage(args.Args.Target, dspec, true, origin: uid); - private void OnHarvestCancelled(EntityUid uid, RevenantComponent component, HarvestDoAfterCancelled args) - { - component.HarvestCancelToken = null; - _appearance.SetData(uid, RevenantVisuals.Harvesting, false); + args.Handled = true; } private void OnDefileAction(EntityUid uid, RevenantComponent component, RevenantDefileActionEvent args) @@ -335,4 +327,14 @@ public sealed partial class RevenantSystem _emag.DoEmagEffect(ent, ent); //it emags itself. spooky. } } + + private sealed class SoulEvent : EntityEventArgs + { + + } + + private sealed class HarvestEvent : EntityEventArgs + { + + } } diff --git a/Content.Server/Sticky/Components/StickyComponent.cs b/Content.Server/Sticky/Components/StickyComponent.cs index c0e916adc5..159022b189 100644 --- a/Content.Server/Sticky/Components/StickyComponent.cs +++ b/Content.Server/Sticky/Components/StickyComponent.cs @@ -80,4 +80,9 @@ public sealed class StickyComponent : Component /// [ViewVariables(VVAccess.ReadOnly)] public EntityUid? StuckTo; + + /// + /// For the DoAfter event to tell if it should stick or unstick + /// + public bool Stick; } diff --git a/Content.Server/Sticky/Systems/StickySystem.cs b/Content.Server/Sticky/Systems/StickySystem.cs index 3d9346bedd..693dff6a1b 100644 --- a/Content.Server/Sticky/Systems/StickySystem.cs +++ b/Content.Server/Sticky/Systems/StickySystem.cs @@ -2,6 +2,7 @@ using Content.Server.DoAfter; using Content.Server.Popups; using Content.Server.Sticky.Components; using Content.Server.Sticky.Events; +using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Sticky.Components; @@ -26,8 +27,7 @@ public sealed class StickySystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStickSuccessful); - SubscribeLocalEvent(OnUnstickSuccessful); + SubscribeLocalEvent(OnStickSuccessful); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent>(AddUnstickVerb); } @@ -84,10 +84,11 @@ public sealed class StickySystem : EntitySystem _popupSystem.PopupEntity(msg, user, user); } + component.Stick = true; + // start sticking object to target - _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target) + _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target, used: uid) { - BroadcastFinishedEvent = new StickSuccessfulEvent(uid, user, target), BreakOnStun = true, BreakOnTargetMove = true, BreakOnUserMove = true, @@ -103,13 +104,18 @@ public sealed class StickySystem : EntitySystem return true; } - private void OnStickSuccessful(StickSuccessfulEvent ev) + private void OnStickSuccessful(EntityUid uid, StickyComponent component, DoAfterEvent args) { - // check if entity still has sticky component - if (!TryComp(ev.Uid, out StickyComponent? component)) + if (args.Handled || args.Cancelled || args.Args.Target == null) return; - StickToEntity(ev.Uid, ev.Target, ev.User, component); + if (component.Stick) + StickToEntity(uid, args.Args.Target.Value, args.Args.User, component); + + else + UnstickFromEntity(uid, args.Args.User, component); + + args.Handled = true; } private void StartUnsticking(EntityUid uid, EntityUid user, StickyComponent? component = null) @@ -127,10 +133,11 @@ public sealed class StickySystem : EntitySystem _popupSystem.PopupEntity(msg, user, user); } + component.Stick = false; + // start unsticking object _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: uid) { - BroadcastFinishedEvent = new UnstickSuccessfulEvent(uid, user), BreakOnStun = true, BreakOnTargetMove = true, BreakOnUserMove = true, @@ -144,15 +151,6 @@ public sealed class StickySystem : EntitySystem } } - private void OnUnstickSuccessful(UnstickSuccessfulEvent ev) - { - // check if entity still has sticky component - if (!TryComp(ev.Uid, out StickyComponent? component)) - return; - - UnstickFromEntity(ev.Uid, ev.User, component); - } - public void StickToEntity(EntityUid uid, EntityUid target, EntityUid user, StickyComponent? component = null) { if (!Resolve(uid, ref component)) @@ -215,30 +213,4 @@ public sealed class StickySystem : EntitySystem component.StuckTo = null; RaiseLocalEvent(uid, new EntityUnstuckEvent(target, user), true); } - - private sealed class StickSuccessfulEvent : EntityEventArgs - { - public readonly EntityUid Uid; - public readonly EntityUid User; - public readonly EntityUid Target; - - public StickSuccessfulEvent(EntityUid uid, EntityUid user, EntityUid target) - { - Uid = uid; - User = user; - Target = target; - } - } - - private sealed class UnstickSuccessfulEvent : EntityEventArgs - { - public readonly EntityUid Uid; - public readonly EntityUid User; - - public UnstickSuccessfulEvent(EntityUid uid, EntityUid user) - { - Uid = uid; - User = user; - } - } } diff --git a/Content.Server/Storage/Components/BluespaceLockerComponent.cs b/Content.Server/Storage/Components/BluespaceLockerComponent.cs index 233219faec..92fc18ef39 100644 --- a/Content.Server/Storage/Components/BluespaceLockerComponent.cs +++ b/Content.Server/Storage/Components/BluespaceLockerComponent.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace Content.Server.Storage.Components; +namespace Content.Server.Storage.Components; [RegisterComponent] public sealed class BluespaceLockerComponent : Component @@ -55,8 +53,6 @@ public sealed class BluespaceLockerComponent : Component [DataField("pickLinksFromNonBluespaceLockers"), ViewVariables(VVAccess.ReadWrite)] public bool PickLinksFromNonBluespaceLockers = true; - public CancellationTokenSource? CancelToken; - /// /// Determines if links automatically added get the source locker set as a target /// diff --git a/Content.Server/Storage/Components/ServerStorageComponent.cs b/Content.Server/Storage/Components/ServerStorageComponent.cs index a16603469d..b0b276ea2c 100644 --- a/Content.Server/Storage/Components/ServerStorageComponent.cs +++ b/Content.Server/Storage/Components/ServerStorageComponent.cs @@ -30,11 +30,6 @@ namespace Content.Server.Storage.Components [DataField("areaInsert")] public bool AreaInsert = false; // "Attacking" with the storage entity causes it to insert all nearby storables after a delay - /// - /// Token for interrupting area insert do after. - /// - public CancellationTokenSource? CancelToken; - [DataField("areaInsertRadius")] public int AreaInsertRadius = 1; diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index fe368eb186..8c2e06cf48 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Threading; +using System.Linq; using Content.Server.DoAfter; using Content.Server.Explosion.EntitySystems; using Content.Server.Mind.Components; @@ -9,6 +8,7 @@ using Content.Server.Storage.Components; using Content.Server.Tools.Systems; using Content.Shared.Access.Components; using Content.Shared.Coordinates; +using Content.Shared.DoAfter; using Content.Shared.Lock; using Content.Shared.Storage.Components; using Robust.Shared.Random; @@ -33,7 +33,7 @@ public sealed class BluespaceLockerSystem : EntitySystem SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(PreOpen); SubscribeLocalEvent(PostClose); - SubscribeLocalEvent(OnBluespaceLockerTeleportDelayComplete); + SubscribeLocalEvent(OnDoAfter); } private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args) @@ -67,13 +67,6 @@ public sealed class BluespaceLockerSystem : EntitySystem if (!Resolve(uid, ref entityStorageComponent)) return; - if (component.CancelToken != null) - { - component.CancelToken.Cancel(); - component.CancelToken = null; - return; - } - if (!component.BehaviorProperties.ActOnOpen) return; @@ -265,9 +258,14 @@ public sealed class BluespaceLockerSystem : EntitySystem PostClose(uid, component); } - private void OnBluespaceLockerTeleportDelayComplete(EntityUid uid, BluespaceLockerComponent component, BluespaceLockerTeleportDelayComplete args) + private void OnDoAfter(EntityUid uid, BluespaceLockerComponent component, DoAfterEvent args) { + if (args.Handled || args.Cancelled) + return; + PostClose(uid, component, false); + + args.Handled = true; } private void PostClose(EntityUid uid, BluespaceLockerComponent component, bool doDelay = true) @@ -278,8 +276,6 @@ public sealed class BluespaceLockerSystem : EntitySystem if (!Resolve(uid, ref entityStorageComponent)) return; - component.CancelToken?.Cancel(); - if (!component.BehaviorProperties.ActOnClose) return; @@ -287,13 +283,8 @@ public sealed class BluespaceLockerSystem : EntitySystem if (doDelay && component.BehaviorProperties.Delay > 0) { EnsureComp(uid); - component.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter( - new DoAfterEventArgs(uid, component.BehaviorProperties.Delay, component.CancelToken.Token) - { - UserFinishedEvent = new BluespaceLockerTeleportDelayComplete() - }); + _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.BehaviorProperties.Delay)); return; } @@ -400,8 +391,4 @@ public sealed class BluespaceLockerSystem : EntitySystem break; } } - - private sealed class BluespaceLockerTeleportDelayComplete : EntityEventArgs - { - } } diff --git a/Content.Server/Storage/EntitySystems/DumpableSystem.cs b/Content.Server/Storage/EntitySystems/DumpableSystem.cs index 806900e7a0..6f61fdc6e8 100644 --- a/Content.Server/Storage/EntitySystems/DumpableSystem.cs +++ b/Content.Server/Storage/EntitySystems/DumpableSystem.cs @@ -6,7 +6,9 @@ using Content.Shared.Verbs; using Content.Server.Disposal.Unit.Components; using Content.Server.Disposal.Unit.EntitySystems; using Content.Server.DoAfter; +using Content.Shared.DoAfter; using Content.Shared.Placeable; +using Content.Shared.Storage; using Robust.Shared.Containers; using Robust.Shared.Random; @@ -17,6 +19,7 @@ namespace Content.Server.Storage.EntitySystems [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; public override void Initialize() { @@ -24,8 +27,7 @@ namespace Content.Server.Storage.EntitySystems SubscribeLocalEvent(OnAfterInteract, after: new[]{ typeof(StorageSystem) }); SubscribeLocalEvent>(AddDumpVerb); SubscribeLocalEvent>(AddUtilityVerbs); - SubscribeLocalEvent(OnDumpCompleted); - SubscribeLocalEvent(OnDumpCancelled); + SubscribeLocalEvent(OnDoAfter); } private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args) @@ -33,16 +35,8 @@ namespace Content.Server.Storage.EntitySystems if (!args.CanReach) return; - if (!TryComp(args.Used, out var storage)) - return; - - if (storage.StoredEntities == null || storage.StoredEntities.Count == 0 || storage.CancelToken != null) - return; - if (HasComp(args.Target) || HasComp(args.Target)) - { - StartDoAfter(uid, args.Target.Value, args.User, component, storage); - } + StartDoAfter(uid, args.Target.Value, args.User, component); } private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent args) @@ -57,7 +51,7 @@ namespace Content.Server.Storage.EntitySystems { Act = () => { - StartDoAfter(uid, null, args.User, dumpable, storage, 0.6f); + StartDoAfter(uid, null, args.User, dumpable);//Had multiplier of 0.6f }, Text = Loc.GetString("dump-verb-name"), IconTexture = "/Textures/Interface/VerbIcons/drop.svg.192dpi.png", @@ -79,7 +73,7 @@ namespace Content.Server.Storage.EntitySystems { Act = () => { - StartDoAfter(uid, args.Target, args.User, dumpable, storage); + StartDoAfter(uid, args.Target, args.User, dumpable); }, Text = Loc.GetString("dump-disposal-verb-name", ("unit", args.Target)), IconEntity = uid @@ -93,7 +87,7 @@ namespace Content.Server.Storage.EntitySystems { Act = () => { - StartDoAfter(uid, args.Target, args.User, dumpable, storage); + StartDoAfter(uid, args.Target, args.User, dumpable); }, Text = Loc.GetString("dump-placeable-verb-name", ("surface", args.Target)), IconEntity = uid @@ -102,21 +96,15 @@ namespace Content.Server.Storage.EntitySystems } } - public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable, ServerStorageComponent storage, float multiplier = 1) + public void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable) { - if (dumpable.CancelToken != null) + if (!TryComp(storageUid, out var storage) || storage.StoredEntities == null) return; - if (storage.StoredEntities == null) - return; + float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * dumpable.Multiplier; - float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * multiplier; - - dumpable.CancelToken = new CancellationTokenSource(); - _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, dumpable.CancelToken.Token, target: targetUid) + _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, target: targetUid, used: storageUid) { - BroadcastFinishedEvent = new DumpCompletedEvent(dumpable, userUid, targetUid, storage.StoredEntities), - BroadcastCancelledEvent = new DumpCancelledEvent(dumpable.Owner), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -124,71 +112,43 @@ namespace Content.Server.Storage.EntitySystems }); } - - private void OnDumpCompleted(DumpCompletedEvent args) + private void OnDoAfter(EntityUid uid, DumpableComponent component, DoAfterEvent args) { - args.Component.CancelToken = null; + if (args.Handled || args.Cancelled || !TryComp(uid, out var storage) || storage.StoredEntities == null) + return; Queue dumpQueue = new(); - foreach (var entity in args.StoredEntities) + foreach (var entity in storage.StoredEntities) { dumpQueue.Enqueue(entity); } - if (TryComp(args.Target, out var disposal)) - { - foreach (var entity in dumpQueue) - { - _disposalUnitSystem.DoInsertDisposalUnit(args.Target.Value, entity, args.User); - } - return; - } - foreach (var entity in dumpQueue) { var transform = Transform(entity); - transform.AttachParentToContainerOrGrid(EntityManager); - transform.LocalPosition = transform.LocalPosition + _random.NextVector2Box() / 2; + _container.AttachParentToContainerOrGrid(transform); + transform.LocalPosition += _random.NextVector2Box() / 2; transform.LocalRotation = _random.NextAngle(); } - if (HasComp(args.Target)) + if (args.Args.Target == null) + return; + + if (HasComp(args.Args.Target.Value)) { foreach (var entity in dumpQueue) { - Transform(entity).LocalPosition = Transform(args.Target.Value).LocalPosition + _random.NextVector2Box() / 4; + _disposalUnitSystem.DoInsertDisposalUnit(args.Args.Target.Value, entity, args.Args.User); } + return; } - } - private void OnDumpCancelled(DumpCancelledEvent args) - { - if (TryComp(args.Uid, out var dumpable)) - dumpable.CancelToken = null; - } - - private sealed class DumpCancelledEvent : EntityEventArgs - { - public readonly EntityUid Uid; - public DumpCancelledEvent(EntityUid uid) + if (HasComp(args.Args.Target.Value)) { - Uid = uid; - } - } - - private sealed class DumpCompletedEvent : EntityEventArgs - { - public DumpableComponent Component { get; } - public EntityUid User { get; } - public EntityUid? Target { get; } - public IReadOnlyList StoredEntities { get; } - - public DumpCompletedEvent(DumpableComponent component, EntityUid user, EntityUid? target, IReadOnlyList storedEntities) - { - Component = component; - User = user; - Target = target; - StoredEntities = storedEntities; + foreach (var entity in dumpQueue) + { + Transform(entity).LocalPosition = Transform(args.Args.Target.Value).LocalPosition + _random.NextVector2Box() / 4; + } } } } diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs index 7cc632247a..568f5d0a5f 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -2,7 +2,6 @@ using System.Linq; using Content.Server.Hands.Components; using Content.Server.Storage.Components; using Content.Shared.Interaction; -using Content.Shared.Movement; using Content.Shared.Storage; using Content.Shared.Verbs; using JetBrains.Annotations; @@ -30,6 +29,7 @@ using static Content.Shared.Storage.SharedStorageComponent; using Content.Shared.ActionBlocker; using Content.Shared.CombatMode; using Content.Shared.Containers.ItemSlots; +using Content.Shared.DoAfter; using Content.Shared.Implants.Components; using Content.Shared.Lock; using Content.Shared.Movement.Events; @@ -75,8 +75,7 @@ namespace Content.Server.Storage.EntitySystems SubscribeLocalEvent(OnBoundUIClosed); SubscribeLocalEvent(OnStorageItemRemoved); - SubscribeLocalEvent(OnAreaPickupComplete); - SubscribeLocalEvent(OnAreaPickupCancelled); + SubscribeLocalEvent>(OnDoAfter); SubscribeLocalEvent>(AddToggleOpenVerb); SubscribeLocalEvent(OnRelayMovement); @@ -84,55 +83,6 @@ namespace Content.Server.Storage.EntitySystems SubscribeLocalEvent(OnStorageFillMapInit); } - private void OnAreaPickupCancelled(EntityUid uid, ServerStorageComponent component, AreaPickupCancelledEvent args) - { - component.CancelToken = null; - } - - private void OnAreaPickupComplete(EntityUid uid, ServerStorageComponent component, AreaPickupCompleteEvent args) - { - component.CancelToken = null; - var successfullyInserted = new List(); - var successfullyInsertedPositions = new List(); - var itemQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - xformQuery.TryGetComponent(uid, out var xform); - - foreach (var entity in args.ValidStorables) - { - // Check again, situation may have changed for some entities, but we'll still pick up any that are valid - if (_containerSystem.IsEntityInContainer(entity) - || entity == args.User - || !itemQuery.HasComponent(entity)) - continue; - - if (xform == null || - !xformQuery.TryGetComponent(entity, out var targetXform) || - targetXform.MapID != xform.MapID) - { - continue; - } - - var position = EntityCoordinates.FromMap( - xform.ParentUid.IsValid() ? xform.ParentUid : uid, - new MapCoordinates(_transform.GetWorldPosition(targetXform, xformQuery), - targetXform.MapID), EntityManager); - - if (PlayerInsertEntityInWorld(uid, args.User, entity, component)) - { - successfullyInserted.Add(entity); - successfullyInsertedPositions.Add(position); - } - } - - // If we picked up atleast one thing, play a sound and do a cool animation! - if (successfullyInserted.Count > 0) - { - _audio.PlayPvs(component.StorageInsertSound, uid); - RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions)); - } - } - private void OnComponentInit(EntityUid uid, ServerStorageComponent storageComp, ComponentInit args) { base.Initialize(); @@ -147,10 +97,7 @@ namespace Content.Server.Storage.EntitySystems private void OnRelayMovement(EntityUid uid, EntityStorageComponent component, ref ContainerRelayMovementEntityEvent args) { - if (!EntityManager.HasComponent(args.Entity)) - return; - - if (_gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay) + if (!EntityManager.HasComponent(args.Entity) || _gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay) return; component.LastInternalOpenAttempt = _gameTiming.CurTime; @@ -163,10 +110,7 @@ namespace Content.Server.Storage.EntitySystems private void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract) - return; - - if (!_entityStorage.CanOpen(args.User, args.Target, silent: true, component)) + if (!args.CanAccess || !args.CanInteract || !_entityStorage.CanOpen(args.User, args.Target, silent: true, component)) return; InteractionVerb verb = new(); @@ -186,10 +130,7 @@ namespace Content.Server.Storage.EntitySystems private void AddOpenUiVerb(EntityUid uid, ServerStorageComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract) - return; - - if (TryComp(uid, out var lockComponent) && lockComponent.Locked) + if (!args.CanAccess || !args.CanInteract || TryComp(uid, out var lockComponent) && lockComponent.Locked) return; // Get the session for the user @@ -222,10 +163,7 @@ namespace Content.Server.Storage.EntitySystems return; var entities = component.Storage?.ContainedEntities; - if (entities == null || entities.Count == 0) - return; - - if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (entities == null || entities.Count == 0 || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) return; // if the target is storage, add a verb to transfer storage. @@ -249,13 +187,7 @@ namespace Content.Server.Storage.EntitySystems /// true if inserted, false otherwise private void OnInteractUsing(EntityUid uid, ServerStorageComponent storageComp, InteractUsingEvent args) { - if (args.Handled) - return; - - if (!storageComp.ClickInsert) - return; - - if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (args.Handled || !storageComp.ClickInsert || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) return; Logger.DebugS(storageComp.LoggerName, $"Storage (UID {uid}) attacked by user (UID {args.User}) with entity (UID {args.Used})."); @@ -273,10 +205,7 @@ namespace Content.Server.Storage.EntitySystems /// private void OnActivate(EntityUid uid, ServerStorageComponent storageComp, ActivateInWorldEvent args) { - if (args.Handled || _combatMode.IsInCombatMode(args.User)) - return; - - if (TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) + if (args.Handled || _combatMode.IsInCombatMode(args.User) || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked) return; OpenStorageUI(uid, args.User, storageComp); @@ -300,12 +229,8 @@ namespace Content.Server.Storage.EntitySystems /// private async void AfterInteract(EntityUid uid, ServerStorageComponent storageComp, AfterInteractEvent args) { - if (!args.CanReach) return; - - if (storageComp.CancelToken != null) - { + if (!args.CanReach) return; - } // Pick up all entities in a radius around the clicked location. // The last half of the if is because carpets exist and this is terrible @@ -328,18 +253,16 @@ namespace Content.Server.Storage.EntitySystems //If there's only one then let's be generous if (validStorables.Count > 1) { - storageComp.CancelToken = new CancellationTokenSource(); - var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, storageComp.CancelToken.Token, target: uid) + var storageData = new StorageData(validStorables); + var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, target: uid) { BreakOnStun = true, BreakOnDamage = true, BreakOnUserMove = true, - NeedHand = true, - TargetCancelledEvent = new AreaPickupCancelledEvent(), - TargetFinishedEvent = new AreaPickupCompleteEvent(args.User, validStorables), + NeedHand = true }; - _doAfterSystem.DoAfter(doAfterArgs); + _doAfterSystem.DoAfter(doAfterArgs, storageData); } return; @@ -374,6 +297,54 @@ namespace Content.Server.Storage.EntitySystems } } + private void OnDoAfter(EntityUid uid, ServerStorageComponent component, DoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + var successfullyInserted = new List(); + var successfullyInsertedPositions = new List(); + var itemQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + xformQuery.TryGetComponent(uid, out var xform); + + foreach (var entity in args.AdditionalData.ValidStorables) + { + // Check again, situation may have changed for some entities, but we'll still pick up any that are valid + if (_containerSystem.IsEntityInContainer(entity) + || entity == args.Args.User + || !itemQuery.HasComponent(entity)) + continue; + + if (xform == null || + !xformQuery.TryGetComponent(entity, out var targetXform) || + targetXform.MapID != xform.MapID) + { + continue; + } + + var position = EntityCoordinates.FromMap( + xform.ParentUid.IsValid() ? xform.ParentUid : uid, + new MapCoordinates(_transform.GetWorldPosition(targetXform, xformQuery), + targetXform.MapID), EntityManager); + + if (PlayerInsertEntityInWorld(uid, args.Args.User, entity, component)) + { + successfullyInserted.Add(entity); + successfullyInsertedPositions.Add(position); + } + } + + // If we picked up atleast one thing, play a sound and do a cool animation! + if (successfullyInserted.Count > 0) + { + _audio.PlayPvs(component.StorageInsertSound, uid); + RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions)); + } + + args.Handled = true; + } + private void OnDestroy(EntityUid uid, ServerStorageComponent storageComp, DestructionEventArgs args) { var storedEntities = storageComp.StoredEntities?.ToList(); @@ -404,10 +375,7 @@ namespace Content.Server.Storage.EntitySystems return; } - if (!_actionBlockerSystem.CanInteract(player, args.InteractedItemUID)) - return; - - if (storageComp.Storage == null || !storageComp.Storage.Contains(args.InteractedItemUID)) + if (!_actionBlockerSystem.CanInteract(player, args.InteractedItemUID) || storageComp.Storage == null || !storageComp.Storage.Contains(args.InteractedItemUID)) return; // Does the player have hands? @@ -419,7 +387,7 @@ namespace Content.Server.Storage.EntitySystems { if (_sharedHandsSystem.TryPickupAnyHand(player, args.InteractedItemUID, handsComp: hands) && storageComp.StorageRemoveSound != null) - SoundSystem.Play(storageComp.StorageRemoveSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, AudioParams.Default); + _audio.Play(storageComp.StorageRemoveSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, AudioParams.Default); return; } @@ -457,7 +425,7 @@ namespace Content.Server.Storage.EntitySystems UpdateStorageVisualization(uid, storageComp); if (storageComp.StorageCloseSound is not null) - SoundSystem.Play(storageComp.StorageCloseSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, storageComp.StorageCloseSound.Params); + _audio.Play(storageComp.StorageCloseSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageCloseSound.Params); } } @@ -473,10 +441,10 @@ namespace Content.Server.Storage.EntitySystems return; _appearance.SetData(uid, StorageVisuals.Open, storageComp.IsOpen, appearance); - _appearance.SetData(uid, SharedBagOpenVisuals.BagState, storageComp.IsOpen ? SharedBagState.Open : SharedBagState.Closed, appearance); + _appearance.SetData(uid, SharedBagOpenVisuals.BagState, storageComp.IsOpen ? SharedBagState.Open : SharedBagState.Closed); if (HasComp(uid)) - _appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen, appearance); + _appearance.SetData(uid, StackVisuals.Hide, !storageComp.IsOpen); } private void RecalculateStorageUsed(ServerStorageComponent storageComp) @@ -581,16 +549,11 @@ namespace Content.Server.Storage.EntitySystems /// true if the entity was inserted, false otherwise public bool Insert(EntityUid uid, EntityUid insertEnt, ServerStorageComponent? storageComp = null, bool playSound = true) { - if (!Resolve(uid, ref storageComp)) - return false; - - if (!CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false) + if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false) return false; if (playSound && storageComp.StorageInsertSound is not null) - { _audio.PlayPvs(storageComp.StorageInsertSound, uid); - } RecalculateStorageUsed(storageComp); UpdateStorageUI(uid, storageComp); @@ -617,11 +580,7 @@ namespace Content.Server.Storage.EntitySystems /// true if inserted, false otherwise public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, ServerStorageComponent? storageComp = null) { - if (!Resolve(uid, ref storageComp)) - return false; - - if (!TryComp(player, out HandsComponent? hands) || - hands.ActiveHandEntity == null) + if (!Resolve(uid, ref storageComp) || !TryComp(player, out HandsComponent? hands) || hands.ActiveHandEntity == null) return false; var toInsert = hands.ActiveHandEntity; @@ -643,10 +602,7 @@ namespace Content.Server.Storage.EntitySystems /// true if inserted, false otherwise public bool PlayerInsertEntityInWorld(EntityUid uid, EntityUid player, EntityUid toInsert, ServerStorageComponent? storageComp = null) { - if (!Resolve(uid, ref storageComp)) - return false; - - if (!_sharedInteractionSystem.InRangeUnobstructed(player, uid, popup: storageComp.ShowPopup)) + if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid, popup: storageComp.ShowPopup)) return false; if (!Insert(uid, toInsert, storageComp)) @@ -663,18 +619,17 @@ namespace Content.Server.Storage.EntitySystems /// The entity to open the UI for public void OpenStorageUI(EntityUid uid, EntityUid entity, ServerStorageComponent? storageComp = null) { - if (!Resolve(uid, ref storageComp)) - return; - - if (!TryComp(entity, out ActorComponent? player)) + if (!Resolve(uid, ref storageComp) || !TryComp(entity, out ActorComponent? player)) return; if (storageComp.StorageOpenSound is not null) - SoundSystem.Play(storageComp.StorageOpenSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, storageComp.StorageOpenSound.Params); + _audio.Play(storageComp.StorageOpenSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageOpenSound.Params); Logger.DebugS(storageComp.LoggerName, $"Storage (UID {uid}) \"used\" by player session (UID {player.PlayerSession.AttachedEntity})."); - _uiSystem.GetUiOrNull(uid, StorageUiKey.Key)?.Open(player.PlayerSession); + var bui = _uiSystem.GetUiOrNull(uid, StorageUiKey.Key); + if (bui != null) + _uiSystem.OpenUi(bui, player.PlayerSession); } /// @@ -683,10 +638,7 @@ namespace Content.Server.Storage.EntitySystems /// public void CloseNestedInterfaces(EntityUid uid, IPlayerSession session, ServerStorageComponent? storageComp = null) { - if (!Resolve(uid, ref storageComp)) - return; - - if (storageComp.StoredEntities == null) + if (!Resolve(uid, ref storageComp) || storageComp.StoredEntities == null) return; // for each containing thing @@ -697,9 +649,7 @@ namespace Content.Server.Storage.EntitySystems foreach (var entity in storageComp.StoredEntities) { if (TryComp(entity, out ServerStorageComponent? storedStorageComp)) - { DebugTools.Assert(storedStorageComp != storageComp, $"Storage component contains itself!? Entity: {uid}"); - } if (!TryComp(entity, out ServerUserInterfaceComponent? ui)) continue; @@ -718,34 +668,22 @@ namespace Content.Server.Storage.EntitySystems var state = new StorageBoundUserInterfaceState((List) storageComp.Storage.ContainedEntities, storageComp.StorageUsed, storageComp.StorageCapacityMax); - _uiSystem.GetUiOrNull(uid, StorageUiKey.Key)?.SetState(state); + var bui = _uiSystem.GetUiOrNull(uid, StorageUiKey.Key); + if (bui != null) + _uiSystem.SetUiState(bui, state); } private void Popup(EntityUid uid, EntityUid player, string message, ServerStorageComponent storageComp) { - if (!storageComp.ShowPopup) return; + if (!storageComp.ShowPopup) + return; _popupSystem.PopupEntity(Loc.GetString(message), player, player); } - /// - /// Raised on storage if it successfully completes area pickup. - /// - private sealed class AreaPickupCompleteEvent : EntityEventArgs + private record struct StorageData(List validStorables) { - public EntityUid User; - public List ValidStorables; - - public AreaPickupCompleteEvent(EntityUid user, List validStorables) - { - User = user; - ValidStorables = validStorables; - } - } - - private sealed class AreaPickupCancelledEvent : EntityEventArgs - { - + public List ValidStorables = validStorables; } } } diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index d70d374f69..3501448e8b 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -15,6 +15,7 @@ using Robust.Server.GameObjects; using System.Threading; using Content.Server.Administration.Logs; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Ensnaring.Components; using Content.Shared.Interaction; using Content.Shared.Strip; @@ -56,7 +57,7 @@ namespace Content.Server.Strip if (!TryComp(entity, out var ensnaring)) continue; - _ensnaring.TryFree(component.Owner, ensnaring, user); + _ensnaring.TryFree(uid, entity, ensnaring, user); return; } } diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 12dddf2f54..98a2aff1a8 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -1,6 +1,6 @@ -using System.Threading; using Content.Server.Administration.Logs; using Content.Server.DoAfter; +using Content.Shared.DoAfter; using Content.Shared.Database; using Content.Shared.Interaction.Events; using Content.Shared.Teleportation.Components; @@ -24,19 +24,17 @@ public sealed class HandTeleporterSystem : EntitySystem { SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnPortalSuccess); - SubscribeLocalEvent(OnPortalCancelled); + SubscribeLocalEvent(OnDoAfter); } - private void OnPortalSuccess(EntityUid uid, HandTeleporterComponent component, HandTeleporterSuccessEvent args) + private void OnDoAfter(EntityUid uid, HandTeleporterComponent component, DoAfterEvent args) { - component.CancelToken = null; - HandlePortalUpdating(uid, component, args.User); - } + if (args.Cancelled || args.Handled) + return; - private void OnPortalCancelled(EntityUid uid, HandTeleporterComponent component, HandTeleporterCancelledEvent args) - { - component.CancelToken = null; + HandlePortalUpdating(uid, component, args.Args.User); + + args.Handled = true; } private void OnUseInHand(EntityUid uid, HandTeleporterComponent component, UseInHandEvent args) @@ -47,12 +45,6 @@ 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 @@ -64,16 +56,12 @@ public sealed class HandTeleporterSystem : EntitySystem if (xform.ParentUid != xform.GridUid) return; - component.CancelToken = new CancellationTokenSource(); - var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, - component.CancelToken.Token, used: uid) + var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, used: uid) { BreakOnDamage = true, BreakOnStun = true, BreakOnUserMove = true, MovementThreshold = 0.5f, - UsedCancelledEvent = new HandTeleporterCancelledEvent(), - UsedFinishedEvent = new HandTeleporterSuccessEvent(args.User) }; _doafter.DoAfter(doafterArgs); diff --git a/Content.Server/Toilet/ToiletSystem.cs b/Content.Server/Toilet/ToiletSystem.cs index 0bb8b3c7d2..b98c5c9d38 100644 --- a/Content.Server/Toilet/ToiletSystem.cs +++ b/Content.Server/Toilet/ToiletSystem.cs @@ -3,7 +3,6 @@ using Content.Server.Buckle.Systems; using Content.Server.Popups; using Content.Server.Storage.Components; using Content.Server.Storage.EntitySystems; -using Content.Server.Tools; using Content.Shared.Body.Components; using Content.Shared.Body.Part; using Content.Shared.Buckle.Components; @@ -13,6 +12,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Toilet; +using Content.Shared.Tools; using Content.Shared.Tools.Components; using Robust.Shared.Audio; using Robust.Shared.Player; @@ -28,7 +28,7 @@ namespace Content.Server.Toilet [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SecretStashSystem _secretStash = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; public override void Initialize() { @@ -102,10 +102,10 @@ namespace Content.Server.Toilet return; component.IsPrying = true; + var toolEvData = new ToolEventData(new ToiletPryFinished(uid)); + // try to pry toilet cistern - if (!_toolSystem.UseTool(args.Used, args.User, uid, 0f, - component.PryLidTime, component.PryingQuality, - new ToiletPryFinished(uid), new ToiletPryInterrupted(uid))) + if (!_toolSystem.UseTool(args.Used, args.User, uid, component.PryLidTime, new [] {component.PryingQuality}, toolEvData)) { component.IsPrying = false; return; diff --git a/Content.Server/Tools/Components/LatticeCuttingComponent.cs b/Content.Server/Tools/Components/LatticeCuttingComponent.cs index addfc38b76..b1e4e18fec 100644 --- a/Content.Server/Tools/Components/LatticeCuttingComponent.cs +++ b/Content.Server/Tools/Components/LatticeCuttingComponent.cs @@ -1,5 +1,4 @@ -using System.Threading; -using Content.Shared.Tools; +using Content.Shared.Tools; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Tools.Components; @@ -18,9 +17,4 @@ public sealed class LatticeCuttingComponent : Component [DataField("vacuumDelay")] public float VacuumDelay = 1.75f; - - /// - /// Used for do_afters. - /// - public CancellationTokenSource? CancelTokenSource = null; } diff --git a/Content.Server/Tools/Components/TilePryingComponent.cs b/Content.Server/Tools/Components/TilePryingComponent.cs index 6a43b471d3..82566e3559 100644 --- a/Content.Server/Tools/Components/TilePryingComponent.cs +++ b/Content.Server/Tools/Components/TilePryingComponent.cs @@ -15,10 +15,5 @@ namespace Content.Server.Tools.Components [DataField("delay")] public float Delay = 1f; - - /// - /// Used for do_afters. - /// - public CancellationTokenSource? CancelToken = null; } } diff --git a/Content.Server/Tools/Systems/WeldableSystem.cs b/Content.Server/Tools/Systems/WeldableSystem.cs index 068af0eceb..ced618ad02 100644 --- a/Content.Server/Tools/Systems/WeldableSystem.cs +++ b/Content.Server/Tools/Systems/WeldableSystem.cs @@ -3,15 +3,15 @@ using Content.Server.Tools.Components; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Interaction; +using Content.Shared.Tools; using Content.Shared.Tools.Components; -using Robust.Server.GameObjects; namespace Content.Server.Tools.Systems; public sealed class WeldableSystem : EntitySystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; public override void Initialize() @@ -65,9 +65,8 @@ public sealed class WeldableSystem : EntitySystem if (!CanWeld(uid, tool, user, component)) return false; - component.BeingWelded = _toolSystem.UseTool(tool, user, uid, component.FuelConsumption, - component.WeldingTime.Seconds, component.WeldingQuality, - new WeldFinishedEvent(user, tool), new WeldCancelledEvent(), uid); + var toolEvData = new ToolEventData(new WeldFinishedEvent(user, tool), targetEntity: uid); + component.BeingWelded = _toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, new[] { component.WeldingQuality }, toolEvData, fuel: component.FuelConsumption); // Log attempt _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} is {(component.IsWelded ? "un" : "")}welding {ToPrettyString(uid):target} at {Transform(uid).Coordinates:targetlocation}"); diff --git a/Content.Server/Tools/ToolSystem.LatticeCutting.cs b/Content.Server/Tools/ToolSystem.LatticeCutting.cs index 165e5708b2..4a62865561 100644 --- a/Content.Server/Tools/ToolSystem.LatticeCutting.cs +++ b/Content.Server/Tools/ToolSystem.LatticeCutting.cs @@ -1,5 +1,4 @@ -using System.Threading; -using Content.Server.Administration.Logs; +using Content.Server.Administration.Logs; using Content.Server.Maps; using Content.Server.Tools.Components; using Content.Shared.Database; @@ -19,17 +18,10 @@ public sealed partial class ToolSystem { SubscribeLocalEvent(OnLatticeCuttingAfterInteract); SubscribeLocalEvent(OnLatticeCutComplete); - SubscribeLocalEvent(OnLatticeCutCancelled); - } - - private void OnLatticeCutCancelled(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCancelledEvent args) - { - component.CancelTokenSource = null; } private void OnLatticeCutComplete(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCompleteEvent args) { - component.CancelTokenSource = null; var gridUid = args.Coordinates.GetGridUid(EntityManager); if (gridUid == null) return; @@ -52,17 +44,14 @@ public sealed partial class ToolSystem if (args.Handled || !args.CanReach || args.Target != null) return; - if (TryCut(args.User, component, args.ClickLocation)) + if (TryCut(uid, args.User, component, args.ClickLocation)) args.Handled = true; } - private bool TryCut(EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation) + private bool TryCut(EntityUid toolEntity, EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation) { - if (component.CancelTokenSource != null) - return true; - ToolComponent? tool = null; - if (component.ToolComponentNeeded && !TryComp(component.Owner, out tool)) + if (component.ToolComponentNeeded && !TryComp(toolEntity, out tool)) return false; if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid)) @@ -82,17 +71,10 @@ public sealed partial class ToolSystem || tile.IsBlockedTurf(true)) return false; - var tokenSource = new CancellationTokenSource(); - component.CancelTokenSource = tokenSource; + var toolEvData = new ToolEventData(new LatticeCuttingCompleteEvent(clickLocation, user), targetEntity: toolEntity); - if (!UseTool(component.Owner, user, null, 0f, component.Delay, new[] {component.QualityNeeded}, - new LatticeCuttingCompleteEvent - { - Coordinates = clickLocation, - User = user - }, new LatticeCuttingCancelledEvent(), toolComponent: tool, doAfterEventTarget: component.Owner, - cancelToken: tokenSource.Token)) - component.CancelTokenSource = null; + if (!UseTool(toolEntity, user, null, component.Delay, new[] {component.QualityNeeded}, toolEvData, toolComponent: tool)) + return false; return true; } @@ -101,10 +83,12 @@ public sealed partial class ToolSystem { public EntityCoordinates Coordinates; public EntityUid User; - } - private sealed class LatticeCuttingCancelledEvent : EntityEventArgs - { + public LatticeCuttingCompleteEvent(EntityCoordinates coordinates, EntityUid user) + { + Coordinates = coordinates; + User = user; + } } } diff --git a/Content.Server/Tools/ToolSystem.TilePrying.cs b/Content.Server/Tools/ToolSystem.TilePrying.cs index fcb0d7e0a3..1af417a533 100644 --- a/Content.Server/Tools/ToolSystem.TilePrying.cs +++ b/Content.Server/Tools/ToolSystem.TilePrying.cs @@ -19,17 +19,10 @@ public sealed partial class ToolSystem { SubscribeLocalEvent(OnTilePryingAfterInteract); SubscribeLocalEvent(OnTilePryComplete); - SubscribeLocalEvent(OnTilePryCancelled); - } - - private void OnTilePryCancelled(EntityUid uid, TilePryingComponent component, TilePryingCancelledEvent args) - { - component.CancelToken = null; } private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingCompleteEvent args) { - component.CancelToken = null; var gridUid = args.Coordinates.GetGridUid(EntityManager); if (!_mapManager.TryGetGrid(gridUid, out var grid)) { @@ -45,18 +38,13 @@ public sealed partial class ToolSystem { if (args.Handled || !args.CanReach || (args.Target != null && !HasComp(args.Target))) return; - if (TryPryTile(args.User, component, args.ClickLocation)) + if (TryPryTile(uid, args.User, component, args.ClickLocation)) args.Handled = true; } - private bool TryPryTile(EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation) + private bool TryPryTile(EntityUid toolEntity, EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation) { - if (component.CancelToken != null) - { - return true; - } - - if (!TryComp(component.Owner, out var tool) && component.ToolComponentNeeded) + if (!TryComp(toolEntity, out var tool) && component.ToolComponentNeeded) return false; if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid)) @@ -74,34 +62,22 @@ public sealed partial class ToolSystem if (!tileDef.CanCrowbar) return false; - var token = new CancellationTokenSource(); - component.CancelToken = token; + var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), targetEntity:toolEntity); - bool success = UseTool( - component.Owner, - user, - null, - 0f, - component.Delay, - new [] {component.QualityNeeded}, - new TilePryingCompleteEvent - { - Coordinates = clickLocation, - }, - new TilePryingCancelledEvent(), - toolComponent: tool, - doAfterEventTarget: component.Owner, - cancelToken: token.Token); - - if (!success) - component.CancelToken = null; + if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool)) + return false; return true; } private sealed class TilePryingCompleteEvent : EntityEventArgs { - public EntityCoordinates Coordinates { get; init; } + public readonly EntityCoordinates Coordinates; + + public TilePryingCompleteEvent(EntityCoordinates coordinates) + { + Coordinates = coordinates; + } } private sealed class TilePryingCancelledEvent : EntityEventArgs diff --git a/Content.Server/Tools/ToolSystem.cs b/Content.Server/Tools/ToolSystem.cs index 0368c7bc29..de37c68005 100644 --- a/Content.Server/Tools/ToolSystem.cs +++ b/Content.Server/Tools/ToolSystem.cs @@ -1,17 +1,9 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chemistry.EntitySystems; -using Content.Server.DoAfter; using Content.Server.Popups; -using Content.Shared.Audio; -using Content.Shared.Item; using Content.Shared.Tools; -using Content.Shared.Tools.Components; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Map; -using Robust.Shared.Player; -using System.Threading; -using System.Threading.Tasks; namespace Content.Server.Tools { @@ -20,12 +12,10 @@ namespace Content.Server.Tools { [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; - [Dependency] private readonly SharedItemSystem _itemSystem = default!; public override void Initialize() { @@ -34,220 +24,6 @@ namespace Content.Server.Tools InitializeTilePrying(); InitializeLatticeCutting(); InitializeWelders(); - - SubscribeLocalEvent(OnDoAfterComplete); - SubscribeLocalEvent(OnDoAfterCancelled); - } - - private void OnDoAfterComplete(ToolDoAfterComplete ev) - { - // Actually finish the tool use! Depending on whether that succeeds or not, either event will be broadcast. - if(ToolFinishUse(ev.Uid, ev.UserUid, ev.Fuel)) - { - if (ev.EventTarget != null) - RaiseLocalEvent(ev.EventTarget.Value, ev.CompletedEvent, false); - else - RaiseLocalEvent(ev.CompletedEvent); - } - else if(ev.CancelledEvent != null) - { - if (ev.EventTarget != null) - RaiseLocalEvent(ev.EventTarget.Value, ev.CancelledEvent, false); - else - RaiseLocalEvent(ev.CancelledEvent); - } - } - - private void OnDoAfterCancelled(ToolDoAfterCancelled ev) - { - if (ev.EventTarget != null) - RaiseLocalEvent(ev.EventTarget.Value, ev.Event, false); - else - RaiseLocalEvent(ev.Event); - } - - /// - /// Whether a tool entity has the specified quality or not. - /// - public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null) - { - return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality); - } - - /// - /// Whether a tool entity has all specified qualities or not. - /// - public bool HasAllQualities(EntityUid uid, IEnumerable qualities, ToolComponent? tool = null) - { - return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities); - } - - /// - /// Sync version of UseTool. - /// - /// The tool entity. - /// The entity using the tool. - /// Optionally, a target to use the tool on. - /// An optional amount of fuel or energy to consume- - /// A doAfter delay in seconds. - /// The tool qualities needed to use the tool. - /// An event to raise once the doAfter is completed successfully. - /// An event to raise once the doAfter is canceled. - /// Where to direct the do-after events. If null, events are broadcast - /// An optional check to perform for the doAfter. - /// The tool component. - /// Token to provide to do_after for cancelling - /// Whether initially, using the tool succeeded. If there's a doAfter delay, you'll need to listen to - /// the and being broadcast - /// to see whether using the tool succeeded or not. If the is zero, - /// this simply returns whether using the tool succeeded or not. - public override bool UseTool( - EntityUid tool, - EntityUid user, - EntityUid? target, - float fuel, - float doAfterDelay, - IEnumerable toolQualitiesNeeded, - object? doAfterCompleteEvent = null, - object? doAfterCancelledEvent = null, - EntityUid? doAfterEventTarget = null, - Func? doAfterCheck = null, - ToolComponent? toolComponent = null, - CancellationToken? cancelToken = null) - { - // No logging here, after all that'd mean the caller would need to check if the component is there or not. - if (!Resolve(tool, ref toolComponent, false)) - return false; - - var ev = new ToolUserAttemptUseEvent(user, target); - RaiseLocalEvent(user, ref ev); - if (ev.Cancelled) - return false; - - if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent)) - return false; - - if (doAfterDelay > 0f) - { - var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, cancelToken ?? default, target) - { - ExtraCheck = doAfterCheck, - BreakOnDamage = true, - BreakOnStun = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - NeedHand = true, - BroadcastFinishedEvent = doAfterCompleteEvent != null ? new ToolDoAfterComplete(doAfterCompleteEvent, doAfterCancelledEvent, tool, user, fuel, doAfterEventTarget) : null, - BroadcastCancelledEvent = doAfterCancelledEvent != null ? new ToolDoAfterCancelled(doAfterCancelledEvent, doAfterEventTarget) : null, - }; - - _doAfterSystem.DoAfter(doAfterArgs); - return true; - } - - return ToolFinishUse(tool, user, fuel, toolComponent); - } - - /// - /// Async version of UseTool. - /// - /// The tool entity. - /// The entity using the tool. - /// Optionally, a target to use the tool on. - /// An optional amount of fuel or energy to consume- - /// A doAfter delay in seconds. - /// The tool qualities needed to use the tool. - /// An optional check to perform for the doAfter. - /// The tool component. - /// Whether using the tool succeeded or not. - public async Task UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel, - float doAfterDelay, IEnumerable toolQualitiesNeeded, Func? doAfterCheck = null, - ToolComponent? toolComponent = null) - { - // No logging here, after all that'd mean the caller would need to check if the component is there or not. - if (!Resolve(tool, ref toolComponent, false)) - return false; - - var ev = new ToolUserAttemptUseEvent(user, target); - RaiseLocalEvent(user, ref ev); - if (ev.Cancelled) - return false; - - if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent)) - return false; - - if (doAfterDelay > 0f) - { - var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, default, target) - { - ExtraCheck = doAfterCheck, - BreakOnDamage = true, - BreakOnStun = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - NeedHand = true, - }; - - var result = await _doAfterSystem.WaitDoAfter(doAfterArgs); - - if (result == DoAfterStatus.Cancelled) - return false; - } - - return ToolFinishUse(tool, user, fuel, toolComponent); - } - - // This is hilariously long. - /// - public Task UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel, - float doAfterDelay, string toolQualityNeeded, Func? doAfterCheck = null, - ToolComponent? toolComponent = null) - { - return UseTool(tool, user, target, fuel, doAfterDelay, new [] {toolQualityNeeded}, doAfterCheck, toolComponent); - } - - private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable toolQualitiesNeeded, ToolComponent? toolComponent = null) - { - if (!Resolve(tool, ref toolComponent)) - return false; - - if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded)) - return false; - - var beforeAttempt = new ToolUseAttemptEvent(fuel, user); - RaiseLocalEvent(tool, beforeAttempt, false); - - return !beforeAttempt.Cancelled; - } - - private bool ToolFinishUse(EntityUid tool, EntityUid user, float fuel, ToolComponent? toolComponent = null) - { - if (!Resolve(tool, ref toolComponent)) - return false; - - var afterAttempt = new ToolUseFinishAttemptEvent(fuel, user); - RaiseLocalEvent(tool, afterAttempt, false); - - if (afterAttempt.Cancelled) - return false; - - if (toolComponent.UseSound != null) - PlayToolSound(tool, toolComponent); - - return true; - } - - public void PlayToolSound(EntityUid uid, ToolComponent? tool = null) - { - if (!Resolve(uid, ref tool)) - return; - - if (tool.UseSound is not {} sound) - return; - - // Pass tool.Owner to Filter.Pvs to avoid a TryGetEntity call. - SoundSystem.Play(sound.GetSound(), Filter.Pvs(tool.Owner), - uid, AudioHelpers.WithVariation(0.175f).WithVolume(-5f)); } public override void Update(float frameTime) @@ -256,85 +32,5 @@ namespace Content.Server.Tools UpdateWelders(frameTime); } - - private sealed class ToolDoAfterComplete : EntityEventArgs - { - public readonly object CompletedEvent; - public readonly object? CancelledEvent; - public readonly EntityUid Uid; - public readonly EntityUid UserUid; - public readonly float Fuel; - public readonly EntityUid? EventTarget; - - public ToolDoAfterComplete(object completedEvent, object? cancelledEvent, EntityUid uid, EntityUid userUid, float fuel, EntityUid? eventTarget = null) - { - CompletedEvent = completedEvent; - Uid = uid; - UserUid = userUid; - Fuel = fuel; - CancelledEvent = cancelledEvent; - EventTarget = eventTarget; - } - } - - private sealed class ToolDoAfterCancelled : EntityEventArgs - { - public readonly object Event; - public readonly EntityUid? EventTarget; - - public ToolDoAfterCancelled(object @event, EntityUid? eventTarget = null) - { - Event = @event; - EventTarget = eventTarget; - } - } - } - - /// - /// Attempt event called *before* any do afters to see if the tool usage should succeed or not. - /// You can change the fuel consumption by changing the Fuel property. - /// - public sealed class ToolUseAttemptEvent : CancellableEntityEventArgs - { - public float Fuel { get; set; } - public EntityUid User { get; } - - public ToolUseAttemptEvent(float fuel, EntityUid user) - { - Fuel = fuel; - User = user; - } - } - - /// - /// Event raised on the user of a tool to see if they can actually use it. - /// - [ByRefEvent] - public struct ToolUserAttemptUseEvent - { - public EntityUid User; - public EntityUid? Target; - public bool Cancelled = false; - - public ToolUserAttemptUseEvent(EntityUid user, EntityUid? target) - { - User = user; - Target = target; - } - } - - /// - /// Attempt event called *after* any do afters to see if the tool usage should succeed or not. - /// You can use this event to consume any fuel needed. - /// - public sealed class ToolUseFinishAttemptEvent : CancellableEntityEventArgs - { - public float Fuel { get; } - public EntityUid User { get; } - - public ToolUseFinishAttemptEvent(float fuel, EntityUid user) - { - Fuel = fuel; - } } } diff --git a/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs b/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs index 1825c407cd..944ce5bb0c 100644 --- a/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs +++ b/Content.Server/VendingMachines/Restock/VendingMachineRestockComponent.cs @@ -8,8 +8,6 @@ namespace Content.Server.VendingMachines.Restock [RegisterComponent] public sealed class VendingMachineRestockComponent : Component { - public CancellationTokenSource? CancelToken; - /// /// The time (in seconds) that it takes to restock a machine. /// diff --git a/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs b/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs index b1a95f0727..e8528edadf 100644 --- a/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs +++ b/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs @@ -1,14 +1,14 @@ using System.Linq; -using System.Threading; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; using Content.Server.Cargo.Systems; using Content.Server.DoAfter; using Content.Server.Wires; +using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.VendingMachines; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; namespace Content.Server.VendingMachines.Restock { @@ -27,16 +27,16 @@ namespace Content.Server.VendingMachines.Restock SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent(OnPriceCalculation); - SubscribeLocalEvent(OnRestockCancelled); } public bool TryAccessMachine(EntityUid uid, - VendingMachineRestockComponent component, + VendingMachineRestockComponent restock, VendingMachineComponent machineComponent, EntityUid user, EntityUid target) { - if (!TryComp(target, out var wires) || !wires.IsPanelOpen) { + if (!TryComp(target, out var wires) || !wires.IsPanelOpen) + { _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-needs-panel-open", ("this", uid), ("user", user), @@ -54,13 +54,9 @@ namespace Content.Server.VendingMachines.Restock EntityUid user, EntityUid target) { - if (!component.CanRestock.Contains(machineComponent.PackPrototypeId)) { - _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-invalid-inventory", - ("this", uid), - ("user", user), - ("target", target) - ), - user); + if (!component.CanRestock.Contains(machineComponent.PackPrototypeId)) + { + _popupSystem.PopupCursor(Loc.GetString("vending-machine-restock-invalid-inventory", ("this", uid), ("user", user), ("target", target)), user); return false; } @@ -69,7 +65,7 @@ namespace Content.Server.VendingMachines.Restock private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args) { - if (component.CancelToken != null || args.Target == null || !args.CanReach) + if (args.Target == null || !args.CanReach) return; if (!TryComp(args.Target, out var machineComponent)) @@ -81,17 +77,8 @@ namespace Content.Server.VendingMachines.Restock if (!TryAccessMachine(uid, component, machineComponent, args.User, args.Target.Value)) return; - component.CancelToken = new CancellationTokenSource(); - - _doAfterSystem.DoAfter(new DoAfterEventArgs( - args.User, - (float) component.RestockDelay.TotalSeconds, - component.CancelToken.Token, - args.Target, - args.Used) + _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, (float) component.RestockDelay.TotalSeconds, target:args.Target, used:uid) { - TargetFinishedEvent = new VendingMachineRestockEvent(args.User, uid), - UsedCancelledEvent = new RestockCancelledEvent(), BreakOnTargetMove = true, BreakOnUserMove = true, BreakOnStun = true, @@ -99,18 +86,11 @@ namespace Content.Server.VendingMachines.Restock NeedHand = true }); - _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start", - ("this", uid), - ("user", args.User), - ("target", args.Target) - ), + _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start", ("this", uid), ("user", args.User), ("target", args.Target)), args.User, PopupType.Medium); - _audioSystem.PlayPvs(component.SoundRestockStart, component.Owner, - AudioParams.Default - .WithVolume(-2f) - .WithVariation(0.2f)); + _audioSystem.PlayPvs(component.SoundRestockStart, component.Owner, AudioParams.Default.WithVolume(-2f).WithVariation(0.2f)); } private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args) @@ -136,13 +116,5 @@ namespace Content.Server.VendingMachines.Restock args.Price += priceSets.Max(); } - - private void OnRestockCancelled(EntityUid uid, VendingMachineRestockComponent component, RestockCancelledEvent args) - { - component.CancelToken?.Cancel(); - component.CancelToken = null; - } - - public readonly struct RestockCancelledEvent { } } } diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index ccef2b1b4c..57355afcce 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; using Content.Shared.Damage; using Content.Shared.Destructible; +using Content.Shared.DoAfter; using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Popups; @@ -55,7 +56,7 @@ namespace Content.Server.VendingMachines SubscribeLocalEvent(OnSelfDispense); - SubscribeLocalEvent(OnRestock); + SubscribeLocalEvent(OnDoAfter); } private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args) @@ -162,29 +163,26 @@ namespace Content.Server.VendingMachines EjectRandom(uid, throwItem: true, forceEject: false, component); } - private void OnRestock(EntityUid uid, VendingMachineComponent component, VendingMachineRestockEvent args) + private void OnDoAfter(EntityUid uid, VendingMachineComponent component, DoAfterEvent args) { - if (!TryComp(args.RestockBox, out var restockComponent)) + if (args.Handled || args.Cancelled || args.Args.Used == null) + return; + + if (!TryComp(args.Args.Used, out var restockComponent)) { - _sawmill.Error($"{ToPrettyString(args.User)} tried to restock {ToPrettyString(uid)} with {ToPrettyString(args.RestockBox)} which did not have a VendingMachineRestockComponent."); + _sawmill.Error($"{ToPrettyString(args.Args.User)} tried to restock {ToPrettyString(uid)} with {ToPrettyString(args.Args.Used.Value)} which did not have a VendingMachineRestockComponent."); return; } TryRestockInventory(uid, component); - _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-done", - ("this", args.RestockBox), - ("user", args.User), - ("target", uid)), - args.User, - PopupType.Medium); + _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-done", ("this", args.Args.Used), ("user", args.Args.User), ("target", uid)), args.Args.User, PopupType.Medium); - _audioSystem.PlayPvs(restockComponent.SoundRestockDone, component.Owner, - AudioParams.Default - .WithVolume(-2f) - .WithVariation(0.2f)); + _audioSystem.PlayPvs(restockComponent.SoundRestockDone, uid, AudioParams.Default.WithVolume(-2f).WithVariation(0.2f)); - Del(args.RestockBox); + Del(args.Args.Used.Value); + + args.Handled = true; } /// @@ -450,18 +448,5 @@ namespace Content.Server.VendingMachines UpdateVendingMachineInterfaceState(vendComponent); TryUpdateVisualState(uid, vendComponent); } - - } - - public sealed class VendingMachineRestockEvent : EntityEventArgs - { - public EntityUid User { get; } - public EntityUid RestockBox { get; } - - public VendingMachineRestockEvent(EntityUid user, EntityUid restockBox) - { - User = user; - RestockBox = restockBox; - } } } diff --git a/Content.Server/Wieldable/WieldableSystem.cs b/Content.Server/Wieldable/WieldableSystem.cs index feea5492b4..b285a972ce 100644 --- a/Content.Server/Wieldable/WieldableSystem.cs +++ b/Content.Server/Wieldable/WieldableSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Popups; using Content.Shared.Verbs; using Robust.Shared.Player; using Content.Server.Actions.Events; +using Content.Shared.DoAfter; using Content.Shared.Weapons.Melee.Events; @@ -30,7 +31,7 @@ namespace Content.Server.Wieldable base.Initialize(); SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnItemWielded); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnItemUnwielded); SubscribeLocalEvent(OnItemLeaveHand); SubscribeLocalEvent(OnVirtualItemDeleted); @@ -125,18 +126,12 @@ namespace Content.Server.Wieldable if (ev.Cancelled) return; - var doargs = new DoAfterEventArgs( - user, - component.WieldTime, - default, - used) + var doargs = new DoAfterEventArgs(user, component.WieldTime, used:used) { BreakOnUserMove = false, BreakOnDamage = true, BreakOnStun = true, - BreakOnTargetMove = true, - TargetFinishedEvent = new ItemWieldedEvent(user), - UserFinishedEvent = new WieldedItemEvent(used) + BreakOnTargetMove = true }; _doAfter.DoAfter(doargs); @@ -154,18 +149,13 @@ namespace Content.Server.Wieldable return; var targEv = new ItemUnwieldedEvent(user); - var userEv = new UnwieldedItemEvent(used); RaiseLocalEvent(used, targEv); - RaiseLocalEvent(user, userEv); } - private void OnItemWielded(EntityUid uid, WieldableComponent component, ItemWieldedEvent args) + private void OnDoAfter(EntityUid uid, WieldableComponent component, DoAfterEvent args) { - if (args.User == null) - return; - - if (!CanWield(uid, component, args.User.Value) || component.Wielded) + if (args.Handled || args.Cancelled || !CanWield(uid, component, args.Args.User) || component.Wielded) return; if (TryComp(uid, out var item)) @@ -177,19 +167,17 @@ namespace Content.Server.Wieldable component.Wielded = true; if (component.WieldSound != null) - { _audioSystem.PlayPvs(component.WieldSound, uid); - } - for (var i = 0; i < component.FreeHandsRequired; i++) + for (int i = 0; i < component.FreeHandsRequired; i++) { - _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.User.Value); + _virtualItemSystem.TrySpawnVirtualItemInHand(uid, args.Args.User); } - _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield", - ("item", uid)), args.User.Value, args.User.Value); - _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", - ("user", args.User.Value),("item", uid)), args.User.Value, Filter.PvsExcept(args.User.Value), true); + _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield", ("item", uid)), args.Args.User, args.Args.User); + _popupSystem.PopupEntity(Loc.GetString("wieldable-component-successful-wield-other", ("user", args.Args.User),("item", uid)), args.Args.User, Filter.PvsExcept(args.Args.User), true); + + args.Handled = true; } private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args) @@ -253,32 +241,6 @@ namespace Content.Server.Wieldable { } - /// - /// Raised on the item that has been wielded. - /// - public sealed class ItemWieldedEvent : EntityEventArgs - { - public EntityUid? User; - - public ItemWieldedEvent(EntityUid? user = null) - { - User = user; - } - } - - /// - /// Raised on the user who wielded the item. - /// - public sealed class WieldedItemEvent : EntityEventArgs - { - public EntityUid Item; - - public WieldedItemEvent(EntityUid item) - { - Item = item; - } - } - public sealed class BeforeUnwieldEvent : CancellableEntityEventArgs { } @@ -301,18 +263,5 @@ namespace Content.Server.Wieldable } } - /// - /// Raised on the user who unwielded the item. - /// - public sealed class UnwieldedItemEvent : EntityEventArgs - { - public EntityUid Item; - - public UnwieldedItemEvent(EntityUid item) - { - Item = item; - } - } - #endregion } diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index d4890fbfa5..8be77f8a0e 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -5,12 +5,13 @@ using Content.Server.Administration.Logs; using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Server.Power.Components; -using Content.Server.Tools; +using Content.Shared.DoAfter; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.GameTicking; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Wires; using Robust.Server.GameObjects; @@ -26,7 +27,7 @@ public sealed class WiresSystem : EntitySystem [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -55,8 +56,7 @@ public sealed class WiresSystem : EntitySystem SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnTimedWire); SubscribeLocalEvent(OnWiresPowered); - SubscribeLocalEvent(OnWireDoAfter); - SubscribeLocalEvent(OnWireDoAfterCancel); + SubscribeLocalEvent>(OnDoAfter); } private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null) @@ -434,14 +434,20 @@ public sealed class WiresSystem : EntitySystem TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool); } - private void OnWireDoAfter(EntityUid uid, WiresComponent component, OnWireDoAfterEvent args) + private void OnDoAfter(EntityUid uid, WiresComponent component, DoAfterEvent args) { - UpdateWires(args.Target, args.User, args.Tool, args.Id, args.Action, component); - } + if (args.Cancelled) + { + component.WiresQueue.Remove(args.AdditionalData.Id); + return; + } - private void OnWireDoAfterCancel(EntityUid uid, WiresComponent component, OnWireDoAfterCancelEvent args) - { - component.WiresQueue.Remove(args.Id); + if (args.Handled || args.Args.Target == null || args.Args.Used == null) + return; + + UpdateWires(args.Args.Target.Value, args.Args.User, args.Args.Used.Value, args.AdditionalData.Id, args.AdditionalData.Action, component); + + args.Handled = true; } private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args) @@ -461,11 +467,9 @@ public sealed class WiresSystem : EntitySystem } else if (!component.IsScrewing && _toolSystem.HasQuality(args.Used, "Screwing", tool)) { - component.IsScrewing = _toolSystem.UseTool(args.Used, args.User, uid, - 0f, ScrewTime, new[] { "Screwing" }, - new WireToolFinishedEvent(uid, args.User), - new WireToolCanceledEvent(uid), - toolComponent: tool); + var toolEvData = new ToolEventData(new WireToolFinishedEvent(uid, args.User)); + + component.IsScrewing = _toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new[] { "Screwing" }, toolEvData, toolComponent: tool); args.Handled = component.IsScrewing; // Log attempt @@ -711,27 +715,16 @@ public sealed class WiresSystem : EntitySystem if (_toolTime > 0f) { - var args = new DoAfterEventArgs(user, _toolTime, default, used) + var data = new WireExtraData(action, id); + var args = new DoAfterEventArgs(user, _toolTime, target:used, used:toolEntity) { NeedHand = true, BreakOnStun = true, BreakOnDamage = true, - BreakOnUserMove = true, - TargetFinishedEvent = new OnWireDoAfterEvent - { - Target = used, - User = user, - Tool = toolEntity, - Action = action, - Id = id - }, - TargetCancelledEvent = new OnWireDoAfterCancelEvent - { - Id = id - } + BreakOnUserMove = true }; - _doAfter.DoAfter(args); + _doAfter.DoAfter(args, data); } else { @@ -739,7 +732,11 @@ public sealed class WiresSystem : EntitySystem } } - + private record struct WireExtraData(WiresAction Action, int Id) + { + public WiresAction Action = Action; + public int Id = Id; + } private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null) { @@ -931,20 +928,6 @@ public sealed class WiresSystem : EntitySystem } public record struct WireToolCanceledEvent(EntityUid Target); - - private sealed class OnWireDoAfterEvent : EntityEventArgs - { - public EntityUid User { get; set; } - public EntityUid Target { get; set; } - public EntityUid Tool { get; set; } - public WiresAction Action { get; set; } - public int Id { get; set; } - } - - private sealed class OnWireDoAfterCancelEvent : EntityEventArgs - { - public int Id { get; set; } - } #endregion } diff --git a/Content.Shared/Construction/Components/AnchorableComponent.cs b/Content.Shared/Construction/Components/AnchorableComponent.cs index dfffa6d62f..0a92b28abe 100644 --- a/Content.Shared/Construction/Components/AnchorableComponent.cs +++ b/Content.Shared/Construction/Components/AnchorableComponent.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Shared.Construction.EntitySystems; using Content.Shared.Tools; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -21,8 +20,6 @@ namespace Content.Shared.Construction.Components [ViewVariables(VVAccess.ReadWrite)] [DataField("delay")] public float Delay = 1f; - - public CancellationTokenSource? CancelToken = null; } public abstract class BaseAnchoredAttemptEvent : CancellableEntityEventArgs diff --git a/Content.Shared/DoAfter/ActiveDoAfterComponent.cs b/Content.Shared/DoAfter/ActiveDoAfterComponent.cs new file mode 100644 index 0000000000..d8d71de953 --- /dev/null +++ b/Content.Shared/DoAfter/ActiveDoAfterComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.DoAfter; + +/// +/// Added to entities that are currently performing any doafters. +/// +[RegisterComponent, NetworkedComponent] +public sealed class ActiveDoAfterComponent : Component +{ + +} diff --git a/Content.Shared/DoAfter/DoAfter.cs b/Content.Shared/DoAfter/DoAfter.cs new file mode 100644 index 0000000000..85d15acba4 --- /dev/null +++ b/Content.Shared/DoAfter/DoAfter.cs @@ -0,0 +1,82 @@ +using System.Threading.Tasks; +using Content.Shared.Hands.Components; +using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; + +namespace Content.Shared.DoAfter; +[Serializable, NetSerializable] +[DataDefinition] +public sealed class DoAfter +{ + [NonSerialized] + [Obsolete] + public Task AsTask; + + [NonSerialized] + [Obsolete("Will be obsolete for EventBus")] + public TaskCompletionSource Tcs; + + //TODO: Should be merged into here + public readonly DoAfterEventArgs EventArgs; + + //ID so the client DoAfterSystem can track + public byte ID; + + public bool Cancelled = false; + + //Cache the delay so the timer properly shows + public float Delay; + + //Keep track of the time this DoAfter started + public TimeSpan StartTime; + + //Keep track of the time this DoAfter was cancelled + public TimeSpan CancelledTime; + + //How long has the do after been running? + public TimeSpan Elapsed = TimeSpan.Zero; + + /// + /// Accrued time when cancelled. + /// + public TimeSpan CancelledElapsed = TimeSpan.Zero; + + public EntityCoordinates UserGrid; + public EntityCoordinates TargetGrid; + + [NonSerialized] + public Action? Done; + +#pragma warning disable RA0004 + public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running; +#pragma warning restore RA0004 + + // NeedHand + public readonly string? ActiveHand; + public 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 SharedHandsComponent? handsComponent)) + { + ActiveHand = handsComponent.ActiveHand?.Name; + ActiveItem = handsComponent.ActiveHandEntity; + } + + Tcs = new TaskCompletionSource(); + AsTask = Tcs.Task; + } +} diff --git a/Content.Shared/DoAfter/DoAfterComponent.cs b/Content.Shared/DoAfter/DoAfterComponent.cs new file mode 100644 index 0000000000..38d4622e43 --- /dev/null +++ b/Content.Shared/DoAfter/DoAfterComponent.cs @@ -0,0 +1,89 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.DoAfter; + +[RegisterComponent, NetworkedComponent] +public sealed class DoAfterComponent : Component +{ + [DataField("doAfters")] + public readonly Dictionary DoAfters = new(); + + [DataField("cancelledDoAfters")] + public readonly Dictionary CancelledDoAfters = new(); + + // So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates) + // we'll just send them the index. Doesn't matter if it wraps around. + [DataField("runningIndex")] + public byte RunningIndex; +} + +[Serializable, NetSerializable] +public sealed class DoAfterComponentState : ComponentState +{ + public Dictionary DoAfters; + + public DoAfterComponentState(Dictionary doAfters) + { + DoAfters = doAfters; + } +} + +/// +/// Use this event to raise your DoAfter events now. +/// Check for cancelled, and if it is, then null the token there. +/// +/// TODO: Add a networked DoAfterEvent to pass in AdditionalData for the future +[Serializable, NetSerializable] +public sealed class DoAfterEvent : HandledEntityEventArgs +{ + public bool Cancelled; + public readonly DoAfterEventArgs Args; + + public DoAfterEvent(bool cancelled, DoAfterEventArgs args) + { + Cancelled = cancelled; + Args = args; + } +} + +/// +/// Use this event to raise your DoAfter events now. +/// Check for cancelled, and if it is, then null the token there. +/// Can't be serialized +/// +/// TODO: Net/Serilization isn't supported so this needs to be networked somehow +public sealed class DoAfterEvent : HandledEntityEventArgs +{ + public T AdditionalData; + public bool Cancelled; + public readonly DoAfterEventArgs Args; + + public DoAfterEvent(T additionalData, bool cancelled, DoAfterEventArgs args) + { + AdditionalData = additionalData; + Cancelled = cancelled; + Args = args; + } +} + +[Serializable, NetSerializable] +public sealed class CancelledDoAfterMessage : EntityEventArgs +{ + public EntityUid Uid; + public byte ID; + + public CancelledDoAfterMessage(EntityUid uid, byte id) + { + Uid = uid; + ID = id; + } +} + +[Serializable, NetSerializable] +public enum DoAfterStatus : byte +{ + Running, + Cancelled, + Finished, +} diff --git a/Content.Shared/DoAfter/DoAfterEventArgs.cs b/Content.Shared/DoAfter/DoAfterEventArgs.cs new file mode 100644 index 0000000000..1aaa90edd1 --- /dev/null +++ b/Content.Shared/DoAfter/DoAfterEventArgs.cs @@ -0,0 +1,121 @@ +using System.Threading; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.DoAfter; +//TODO: Merge into DoAfter +[Serializable, NetSerializable] +public sealed class DoAfterEventArgs +{ + /// + /// The entity invoking do_after + /// + public EntityUid User; + + /// + /// How long does the do_after require to complete + /// + public float Delay; + + /// + /// Applicable target (if relevant) + /// + public EntityUid? Target; + + /// + /// Entity used by the User on the Target. + /// + public EntityUid? Used; + + public bool RaiseOnUser = true; + + public bool RaiseOnTarget = true; + + public bool RaiseOnUsed = true; + + /// + /// Manually cancel the do_after so it no longer runs + /// + [NonSerialized] + public CancellationToken CancelToken; + + // Break the chains + /// + /// Whether we need to keep our active hand as is (i.e. can't change hand or change item). + /// This also covers requiring the hand to be free (if applicable). + /// + public bool NeedHand; + + /// + /// If do_after stops when the user moves + /// + public bool BreakOnUserMove; + + /// + /// If do_after stops when the target moves (if there is a target) + /// + public bool BreakOnTargetMove; + + /// + /// Threshold for user and target movement + /// + public float MovementThreshold; + + public bool BreakOnDamage; + + /// + /// Threshold for user damage + /// + public FixedPoint2? DamageThreshold; + public bool BreakOnStun; + + /// + /// Should the DoAfter event broadcast? + /// + public bool Broadcast; + + /// + /// Threshold for distance user from the used OR target entities. + /// + public float? DistanceThreshold; + + /// + /// Requires a function call once at the end (like InRangeUnobstructed). + /// + /// + /// Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client. + /// + [NonSerialized] + //TODO: Replace with eventbus + public Func? PostCheck; + + /// + /// Additional conditions that need to be met. Return false to cancel. + /// + [NonSerialized] + //TODO Replace with eventbus + public Func? ExtraCheck; + + public DoAfterEventArgs( + EntityUid user, + float delay, + CancellationToken cancelToken = default, + EntityUid? target = null, + EntityUid? used = null) + { + User = user; + Delay = delay; + CancelToken = cancelToken; + Target = target; + Used = used; + MovementThreshold = 0.1f; + DamageThreshold = 1.0; + + if (Target == null) + { + DebugTools.Assert(!BreakOnTargetMove); + BreakOnTargetMove = false; + } + } +} diff --git a/Content.Shared/DoAfter/SharedDoAfterComponent.cs b/Content.Shared/DoAfter/SharedDoAfterComponent.cs deleted file mode 100644 index 3d75916d72..0000000000 --- a/Content.Shared/DoAfter/SharedDoAfterComponent.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Content.Shared.FixedPoint; -using Robust.Shared.GameStates; -using Robust.Shared.Map; -using Robust.Shared.Serialization; - -namespace Content.Shared.DoAfter -{ - [NetworkedComponent()] - public abstract class SharedDoAfterComponent : Component - { - } - - [Serializable, NetSerializable] - public sealed class DoAfterComponentState : ComponentState - { - public List DoAfters { get; } - - public DoAfterComponentState(List doAfters) - { - DoAfters = doAfters; - } - } - - [Serializable, NetSerializable] - public sealed class CancelledDoAfterMessage : EntityEventArgs - { - public EntityUid Uid; - public byte ID { get; } - - public CancelledDoAfterMessage(EntityUid uid, byte id) - { - Uid = uid; - ID = id; - } - } - - // TODO: Merge this with the actual DoAfter - /// - /// We send a trimmed-down version of the DoAfter for the client for it to use. - /// - [Serializable, NetSerializable] - public sealed class ClientDoAfter - { - public bool Cancelled = false; - - /// - /// Accrued time when cancelled. - /// - public float CancelledAccumulator; - - // To see what these do look at DoAfter and DoAfterEventArgs - public byte ID { get; } - - public TimeSpan StartTime { get; } - - public EntityCoordinates UserGrid { get; } - - public EntityCoordinates TargetGrid { get; } - - public EntityUid? Target { get; } - - public float Accumulator; - - public float Delay { get; } - - // TODO: The other ones need predicting - public bool BreakOnUserMove { get; } - - public bool BreakOnTargetMove { get; } - - public float MovementThreshold { get; } - - public FixedPoint2 DamageThreshold { get; } - - public ClientDoAfter(byte id, EntityCoordinates userGrid, EntityCoordinates targetGrid, TimeSpan startTime, - float delay, bool breakOnUserMove, bool breakOnTargetMove, float movementThreshold, FixedPoint2 damageThreshold, EntityUid? target = null) - { - ID = id; - UserGrid = userGrid; - TargetGrid = targetGrid; - StartTime = startTime; - Delay = delay; - BreakOnUserMove = breakOnUserMove; - BreakOnTargetMove = breakOnTargetMove; - MovementThreshold = movementThreshold; - DamageThreshold = damageThreshold; - Target = target; - } - } -} diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs new file mode 100644 index 0000000000..d83570d73b --- /dev/null +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -0,0 +1,385 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Shared.Damage; +using Content.Shared.Hands.Components; +using Content.Shared.Mobs; +using Content.Shared.Stunnable; +using Robust.Shared.GameStates; +using Robust.Shared.Timing; + +namespace Content.Shared.DoAfter; + +public abstract class SharedDoAfterSystem : EntitySystem +{ + [Dependency] protected readonly IGameTiming GameTiming = default!; + + // We cache the list as to not allocate every update tick... + private readonly Queue _pending = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnDamage); + SubscribeLocalEvent(OnStateChanged); + SubscribeLocalEvent(OnDoAfterGetState); + } + + private void Add(EntityUid entity, DoAfterComponent component, DoAfter doAfter) + { + doAfter.ID = component.RunningIndex; + doAfter.Delay = doAfter.EventArgs.Delay; + component.DoAfters.Add(component.RunningIndex, doAfter); + EnsureComp(entity); + component.RunningIndex++; + Dirty(component); + } + + private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args) + { + args.State = new DoAfterComponentState(component.DoAfters); + } + + private void Cancelled(DoAfterComponent component, DoAfter doAfter) + { + if (!component.DoAfters.TryGetValue(doAfter.ID, out var index)) + return; + + component.DoAfters.Remove(doAfter.ID); + + if (component.DoAfters.Count == 0) + RemComp(component.Owner); + + RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index.ID)); + } + + /// + /// Call when the particular DoAfter is finished. + /// Client should be tracking this independently. + /// + private void Finished(DoAfterComponent component, DoAfter doAfter) + { + if (!component.DoAfters.ContainsKey(doAfter.ID)) + return; + + component.DoAfters.Remove(doAfter.ID); + + if (component.DoAfters.Count == 0) + RemComp(component.Owner); + } + + private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead || args.NewMobState != MobState.Critical) + return; + + foreach (var (_, doAfter) in component.DoAfters) + { + Cancel(uid, doAfter, component); + } + } + + /// + /// Cancels DoAfter if it breaks on damage and it meets the threshold + /// + /// The EntityUID of the user + /// + /// + private void OnDamage(EntityUid uid, DoAfterComponent component, DamageChangedEvent args) + { + if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null) + return; + + foreach (var doAfter in component.DoAfters.Values) + { + if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold) + Cancel(uid, doAfter, component); + } + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var (_, comp) in EntityManager.EntityQuery()) + { + foreach (var doAfter in comp.DoAfters.Values.ToArray()) + { + Run(comp.Owner, comp, doAfter); + + switch (doAfter.Status) + { + case DoAfterStatus.Running: + break; + case DoAfterStatus.Cancelled: + _pending.Enqueue(doAfter); + break; + case DoAfterStatus.Finished: + _pending.Enqueue(doAfter); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + while (_pending.TryDequeue(out var doAfter)) + { + if (doAfter.Status == DoAfterStatus.Cancelled) + { + Cancelled(comp, doAfter); + + if (doAfter.Done != null) + doAfter.Done(true); + } + + if (doAfter.Status == DoAfterStatus.Finished) + { + Finished(comp, doAfter); + + if (doAfter.Done != null) + doAfter.Done(false); + } + } + } + } + + /// + /// Tasks that are delayed until the specified time has passed + /// These can be potentially cancelled by the user moving or when other things happen. + /// + /// + /// + [Obsolete("Use the synchronous version instead, DoAfter")] + public async Task WaitDoAfter(DoAfterEventArgs eventArgs) + { + var doAfter = CreateDoAfter(eventArgs); + + await doAfter.AsTask; + + return doAfter.Status; + } + + /// + /// Creates a DoAfter without waiting for it to finish. You can use events with this. + /// These can be potentially cancelled by the user moving or when other things happen. + /// Use this when you need to send extra data with the DoAfter + /// + /// The DoAfterEventArgs + /// The extra data sent over + public void DoAfter(DoAfterEventArgs eventArgs, T data) + { + var doAfter = CreateDoAfter(eventArgs); + + doAfter.Done = cancelled => { Send(data, cancelled, eventArgs); }; + } + + /// + /// Creates a DoAfter without waiting for it to finish. You can use events with this. + /// These can be potentially cancelled by the user moving or when other things happen. + /// Use this if you don't have any extra data to send with the DoAfter + /// + /// The DoAfterEventArgs + public DoAfter DoAfter(DoAfterEventArgs eventArgs) + { + var doAfter = CreateDoAfter(eventArgs); + + doAfter.Done = cancelled => { Send(cancelled, eventArgs); }; + + return doAfter; + } + + private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs) + { + // Setup + eventArgs.CancelToken = new CancellationToken(); + var doAfter = new DoAfter(eventArgs, EntityManager); + // Caller's gonna be responsible for this I guess + var doAfterComponent = Comp(eventArgs.User); + doAfter.ID = doAfterComponent.RunningIndex; + doAfter.StartTime = GameTiming.CurTime; + Add(eventArgs.User, doAfterComponent, doAfter); + return doAfter; + } + + private void Run(EntityUid entity, DoAfterComponent comp, DoAfter doAfter) + { + switch (doAfter.Status) + { + case DoAfterStatus.Running: + break; + case DoAfterStatus.Cancelled: + case DoAfterStatus.Finished: + return; + default: + throw new ArgumentOutOfRangeException(); + } + + doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime; + + if (IsFinished(doAfter)) + { + if (!TryPostCheck(doAfter)) + { + Cancel(entity, doAfter, comp); + } + else + { + doAfter.Tcs.SetResult(DoAfterStatus.Finished); + } + + return; + } + + if (IsCancelled(doAfter)) + { + Cancel(entity, doAfter, comp); + } + } + + private bool TryPostCheck(DoAfter doAfter) + { + return doAfter.EventArgs.PostCheck?.Invoke() != false; + } + + private bool IsFinished(DoAfter doAfter) + { + var delay = TimeSpan.FromSeconds(doAfter.EventArgs.Delay); + + if (doAfter.Elapsed <= delay) + return false; + + return true; + } + + private bool IsCancelled(DoAfter doAfter) + { + var eventArgs = doAfter.EventArgs; + var xForm = GetEntityQuery(); + + if (!Exists(eventArgs.User) || eventArgs.Target is { } target && !Exists(target)) + return true; + + if (eventArgs.CancelToken.IsCancellationRequested) + return true; + + //TODO: Handle Inertia in space + if (eventArgs.BreakOnUserMove && !xForm.GetComponent(eventArgs.User).Coordinates + .InRange(EntityManager, doAfter.UserGrid, eventArgs.MovementThreshold)) + return true; + + if (eventArgs.Target != null && eventArgs.BreakOnTargetMove && !xForm.GetComponent(eventArgs.Target!.Value) + .Coordinates.InRange(EntityManager, doAfter.TargetGrid, eventArgs.MovementThreshold)) + return true; + + if (eventArgs.ExtraCheck != null && !eventArgs.ExtraCheck.Invoke()) + return true; + + if (eventArgs.BreakOnStun && HasComp(eventArgs.User)) + return true; + + if (eventArgs.NeedHand) + { + if (!TryComp(eventArgs.User, out var handsComp)) + { + //TODO: Figure out active hand and item values + + // If we had a hand but no longer have it that's still a paddlin' + if (doAfter.ActiveHand != null) + return true; + } + else + { + var currentActiveHand = handsComp.ActiveHand?.Name; + if (doAfter.ActiveHand != currentActiveHand) + return true; + + var currentItem = handsComp.ActiveHandEntity; + if (doAfter.ActiveItem != currentItem) + return true; + } + } + + if (eventArgs.DistanceThreshold != null) + { + var userXform = xForm.GetComponent(eventArgs.User); + + if (eventArgs.Target != null && !eventArgs.User.Equals(eventArgs.Target)) + { + //recalculate Target location in case Target has also moved + var targetCoords = xForm.GetComponent(eventArgs.Target.Value).Coordinates; + if (!userXform.Coordinates.InRange(EntityManager, targetCoords, eventArgs.DistanceThreshold.Value)) + return true; + } + + if (eventArgs.Used != null) + { + var usedCoords = xForm.GetComponent(eventArgs.Used.Value).Coordinates; + if (!userXform.Coordinates.InRange(EntityManager, usedCoords, eventArgs.DistanceThreshold.Value)) + return true; + } + } + + return false; + } + + public void Cancel(EntityUid entity, DoAfter doAfter, DoAfterComponent? comp = null) + { + if (!Resolve(entity, ref comp, false)) + return; + + if (comp.CancelledDoAfters.ContainsKey(doAfter.ID)) + return; + + if (!comp.DoAfters.ContainsKey(doAfter.ID)) + return; + + doAfter.Cancelled = true; + doAfter.CancelledTime = GameTiming.CurTime; + + var doAfterMessage = comp.DoAfters[doAfter.ID]; + comp.CancelledDoAfters.Add(doAfter.ID, doAfterMessage); + + if (doAfter.Status == DoAfterStatus.Running) + { + doAfter.Tcs.SetResult(DoAfterStatus.Cancelled); + } + } + + /// + /// Send the DoAfter event, used where you don't need any extra data to send. + /// + /// + /// + private void Send(bool cancelled, DoAfterEventArgs args) + { + var ev = new DoAfterEvent(cancelled, args); + + RaiseDoAfterEvent(ev, args); + } + + /// + /// Send the DoAfter event, used where you need extra data to send + /// + /// + /// + /// + /// + private void Send(T data, bool cancelled, DoAfterEventArgs args) + { + var ev = new DoAfterEvent(data, cancelled, args); + + RaiseDoAfterEvent(ev, args); + } + + private void RaiseDoAfterEvent(TEvent ev, DoAfterEventArgs args) where TEvent : notnull + { + if (EntityManager.EntityExists(args.User) && args.RaiseOnUser) + RaiseLocalEvent(args.User, ev, args.Broadcast); + + if (args.Target is { } target && EntityManager.EntityExists(target) && args.RaiseOnTarget) + RaiseLocalEvent(target, ev, args.Broadcast); + + if (args.Used is { } used && EntityManager.EntityExists(used) && args.RaiseOnUsed) + RaiseLocalEvent(used, ev, args.Broadcast); + } +} diff --git a/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs b/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs index 1e69cdf263..d88dd30dd0 100644 --- a/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs +++ b/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs @@ -57,7 +57,6 @@ public sealed class EnsnaringComponent : Component [DataField("canMoveBreakout")] public bool CanMoveBreakout; - public CancellationTokenSource? CancelToken; } /// diff --git a/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs b/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs index a83f617e5e..36509054e6 100644 --- a/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs +++ b/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs @@ -20,8 +20,6 @@ public sealed class MechEquipmentComponent : Component /// [ViewVariables] public EntityUid? EquipmentOwner; - - public CancellationTokenSource? TokenSource = null; } /// diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs index 3a9c76111c..2bd456d774 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs @@ -1,5 +1,4 @@ -using System.Threading; -using Content.Shared.Body.Components; +using Content.Shared.Body.Components; using Content.Shared.DragDrop; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -79,8 +78,6 @@ public abstract class SharedCryoPodComponent: Component public bool IsPrying { get; set; } - public CancellationTokenSource? DragDropCancelToken; - [Serializable, NetSerializable] public enum CryoPodVisuals : byte { diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs index 502669dc5c..5aadbbaeb8 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.Medical.Components; +using Content.Server.Medical.Components; +using Content.Shared.DoAfter; using Content.Shared.Body.Components; using Content.Shared.DragDrop; using Content.Shared.Emag.Systems; @@ -143,17 +144,6 @@ public abstract partial class SharedCryoPodSystem: EntitySystem args.Handled = true; } - protected void DoInsertCryoPod(EntityUid uid, SharedCryoPodComponent cryoPodComponent, DoInsertCryoPodEvent args) - { - cryoPodComponent.DragDropCancelToken = null; - InsertBody(uid, args.ToInsert, cryoPodComponent); - } - - protected void DoInsertCancelCryoPod(EntityUid uid, SharedCryoPodComponent cryoPodComponent, DoInsertCancelledCryoPodEvent args) - { - cryoPodComponent.DragDropCancelToken = null; - } - protected void OnCryoPodPryFinished(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryFinished args) { cryoPodComponent.IsPrying = false; @@ -167,8 +157,6 @@ public abstract partial class SharedCryoPodSystem: EntitySystem #region Event records - protected record DoInsertCryoPodEvent(EntityUid ToInsert); - protected record DoInsertCancelledCryoPodEvent; protected record CryoPodPryFinished; protected record CryoPodPryInterrupted; diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index 23d4ee5091..772255eab8 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -109,7 +109,9 @@ public sealed class EncryptionKeySystem : EntitySystem return; } - if (!_toolSystem.UseTool(args.Used, args.User, uid, 0f, 0f, component.KeysExtractionMethod, toolComponent: tool)) + var toolEvData = new ToolEventData(null); + + if(!_toolSystem.UseTool(args.Used, args.User, uid, 0f, new[] { component.KeysExtractionMethod }, toolEvData, toolComponent: tool)) return; var contained = component.KeyContainer.ContainedEntities.ToArray(); diff --git a/Content.Shared/Revenant/Components/RevenantComponent.cs b/Content.Shared/Revenant/Components/RevenantComponent.cs index 60fa510bff..c9e6824ab0 100644 --- a/Content.Shared/Revenant/Components/RevenantComponent.cs +++ b/Content.Shared/Revenant/Components/RevenantComponent.cs @@ -1,4 +1,3 @@ -using System.Threading; using Content.Shared.Disease; using Content.Shared.FixedPoint; using Content.Shared.Store; @@ -67,9 +66,6 @@ public sealed class RevenantComponent : Component /// [ViewVariables(VVAccess.ReadWrite), DataField("maxEssenceUpgradeAmount")] public float MaxEssenceUpgradeAmount = 10; - - public CancellationTokenSource? SoulSearchCancelToken; - public CancellationTokenSource? HarvestCancelToken; #endregion //In the nearby radius, causes various objects to be thrown, messed with, and containers opened diff --git a/Content.Shared/Storage/Components/DumpableComponent.cs b/Content.Shared/Storage/Components/DumpableComponent.cs index 9632c86c65..15e9726350 100644 --- a/Content.Shared/Storage/Components/DumpableComponent.cs +++ b/Content.Shared/Storage/Components/DumpableComponent.cs @@ -16,8 +16,9 @@ namespace Content.Shared.Storage.Components public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2); /// - /// Cancellation token for the doafter. + /// The multiplier modifier /// - public CancellationTokenSource? CancelToken; + [DataField("multiplier")] + public float Multiplier = 1.0f; } } diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index 95e06c8aec..d30a913adf 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -1,6 +1,4 @@ -using System.Threading; -using Content.Shared.Audio; -using Robust.Shared.Audio; +using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -41,15 +39,4 @@ public sealed class HandTeleporterComponent : Component [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/Tools/Components/ToolComponent.cs b/Content.Shared/Tools/Components/ToolComponent.cs index 8f4b9c4303..bae98e9712 100644 --- a/Content.Shared/Tools/Components/ToolComponent.cs +++ b/Content.Shared/Tools/Components/ToolComponent.cs @@ -20,4 +20,68 @@ namespace Content.Shared.Tools.Components [DataField("useSound")] public SoundSpecifier? UseSound { get; set; } } + + /// + /// Attempt event called *before* any do afters to see if the tool usage should succeed or not. + /// You can change the fuel consumption by changing the Fuel property. + /// + public sealed class ToolUseAttemptEvent : CancellableEntityEventArgs + { + public float Fuel { get; set; } + public EntityUid User { get; } + + public ToolUseAttemptEvent(float fuel, EntityUid user) + { + Fuel = fuel; + User = user; + } + } + + /// + /// Event raised on the user of a tool to see if they can actually use it. + /// + [ByRefEvent] + public struct ToolUserAttemptUseEvent + { + public EntityUid User; + public EntityUid? Target; + public bool Cancelled = false; + + public ToolUserAttemptUseEvent(EntityUid user, EntityUid? target) + { + User = user; + Target = target; + } + } + + /// + /// Attempt event called *after* any do afters to see if the tool usage should succeed or not. + /// You can use this event to consume any fuel needed. + /// + public sealed class ToolUseFinishAttemptEvent : CancellableEntityEventArgs + { + public float Fuel { get; } + public EntityUid User { get; } + + public ToolUseFinishAttemptEvent(float fuel, EntityUid user) + { + Fuel = fuel; + } + } + + public sealed class ToolEventData + { + public readonly Object? Ev; + public readonly Object? CancelledEv; + public readonly float Fuel; + public readonly EntityUid? TargetEntity; + + public ToolEventData(Object? ev, float fuel = 0f, Object? cancelledEv = null, EntityUid? targetEntity = null) + { + Ev = ev; + CancelledEv = cancelledEv; + Fuel = fuel; + TargetEntity = targetEntity; + } + } } diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs b/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs index 9cce7337ec..b723395885 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs @@ -1,8 +1,11 @@ using System.Linq; -using System.Threading; +using Content.Shared.Audio; +using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Tools.Components; +using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Shared.Tools; @@ -11,6 +14,7 @@ public abstract class SharedToolSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; public override void Initialize() { @@ -18,32 +22,77 @@ public abstract class SharedToolSystem : EntitySystem SubscribeLocalEvent(OnMultipleToolActivated); SubscribeLocalEvent(OnMultipleToolGetState); SubscribeLocalEvent(OnMultipleToolHandleState); + + SubscribeLocalEvent>(OnDoAfter); + + SubscribeLocalEvent(OnDoAfterComplete); + SubscribeLocalEvent(OnDoAfterCancelled); } - public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel, - float doAfterDelay, string toolQualityNeeded, object? doAfterCompleteEvent = null, object? doAfterCancelledEvent = null, EntityUid? doAfterEventTarget = null, - Func? doAfterCheck = null, ToolComponent? toolComponent = null) + private void OnDoAfter(EntityUid uid, ToolComponent component, DoAfterEvent args) { - return UseTool(tool, user, target, fuel, doAfterDelay, new[] { toolQualityNeeded }, - doAfterCompleteEvent, doAfterCancelledEvent, doAfterEventTarget, doAfterCheck, toolComponent); + if (args.Handled || args.Cancelled || args.AdditionalData.Ev == null) + return; + + if (ToolFinishUse(uid, args.Args.User, args.AdditionalData.Fuel)) + { + if (args.AdditionalData.TargetEntity != null) + RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.Ev); + else + RaiseLocalEvent(args.AdditionalData.Ev); + + args.Handled = true; + } + else if (args.AdditionalData.CancelledEv != null) + { + if (args.AdditionalData.TargetEntity != null) + RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.CancelledEv); + else + RaiseLocalEvent(args.AdditionalData.CancelledEv); + + args.Handled = true; + } } - public virtual bool UseTool( - EntityUid tool, - EntityUid user, - EntityUid? target, - float fuel, - float doAfterDelay, - IEnumerable toolQualitiesNeeded, - object? doAfterCompleteEvent = null, - object? doAfterCancelledEvent = null, - EntityUid? doAfterEventTarget = null, - Func? doAfterCheck = null, - ToolComponent? toolComponent = null, - CancellationToken? cancelToken = null) + public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, IEnumerable toolQualitiesNeeded, ToolEventData toolEventData, float fuel = 0f, ToolComponent? toolComponent = null, Func? doAfterCheck = null) { - // predicted tools when. - return false; + // No logging here, after all that'd mean the caller would need to check if the component is there or not. + if (!Resolve(tool, ref toolComponent, false)) + return false; + + var ev = new ToolUserAttemptUseEvent(user, target); + RaiseLocalEvent(user, ref ev); + if (ev.Cancelled) + return false; + + if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent)) + return false; + + if (doAfterDelay > 0f) + { + var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, target:target, used:tool) + { + ExtraCheck = doAfterCheck, + BreakOnDamage = true, + BreakOnStun = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true + }; + + _doAfterSystem.DoAfter(doAfterArgs, toolEventData); + return true; + } + + return ToolFinishUse(tool, user, fuel, toolComponent); + } + + public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, string toolQualityNeeded, + ToolEventData toolEventData, float fuel = 0, ToolComponent? toolComponent = null, + Func? doAfterCheck = null) + { + return UseTool(tool, user, target, doAfterDelay, new[] { toolQualityNeeded }, toolEventData, fuel, + toolComponent, doAfterCheck); } private void OnMultipleToolHandleState(EntityUid uid, MultipleToolComponent component, ref ComponentHandleState args) @@ -116,5 +165,125 @@ public abstract class SharedToolSystem : EntitySystem if (_protoMan.TryIndex(current.Behavior.First(), out ToolQualityPrototype? quality)) multiple.CurrentQualityName = Loc.GetString(quality.Name); } + + /// + /// Whether a tool entity has the specified quality or not. + /// + public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null) + { + return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality); + } + + /// + /// Whether a tool entity has all specified qualities or not. + /// + public bool HasAllQualities(EntityUid uid, IEnumerable qualities, ToolComponent? tool = null) + { + return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities); + } + + + private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable toolQualitiesNeeded, ToolComponent? toolComponent = null) + { + if (!Resolve(tool, ref toolComponent)) + return false; + + if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded)) + return false; + + var beforeAttempt = new ToolUseAttemptEvent(fuel, user); + RaiseLocalEvent(tool, beforeAttempt, false); + + return !beforeAttempt.Cancelled; + } + + private bool ToolFinishUse(EntityUid tool, EntityUid user, float fuel, ToolComponent? toolComponent = null) + { + if (!Resolve(tool, ref toolComponent)) + return false; + + var afterAttempt = new ToolUseFinishAttemptEvent(fuel, user); + RaiseLocalEvent(tool, afterAttempt, false); + + if (afterAttempt.Cancelled) + return false; + + if (toolComponent.UseSound != null) + PlayToolSound(tool, toolComponent); + + return true; + } + + public void PlayToolSound(EntityUid uid, ToolComponent? tool = null) + { + if (!Resolve(uid, ref tool)) + return; + + if (tool.UseSound is not {} sound) + return; + + // Pass tool.Owner to Filter.Pvs to avoid a TryGetEntity call. + SoundSystem.Play(sound.GetSound(), Filter.Pvs(tool.Owner), + uid, AudioHelpers.WithVariation(0.175f).WithVolume(-5f)); + } + + private void OnDoAfterComplete(ToolDoAfterComplete ev) + { + // Actually finish the tool use! Depending on whether that succeeds or not, either event will be broadcast. + if(ToolFinishUse(ev.Uid, ev.UserUid, ev.Fuel)) + { + if (ev.EventTarget != null) + RaiseLocalEvent(ev.EventTarget.Value, ev.CompletedEvent, false); + else + RaiseLocalEvent(ev.CompletedEvent); + } + else if(ev.CancelledEvent != null) + { + if (ev.EventTarget != null) + RaiseLocalEvent(ev.EventTarget.Value, ev.CancelledEvent, false); + else + RaiseLocalEvent(ev.CancelledEvent); + } + } + + private void OnDoAfterCancelled(ToolDoAfterCancelled ev) + { + if (ev.EventTarget != null) + RaiseLocalEvent(ev.EventTarget.Value, ev.Event, false); + else + RaiseLocalEvent(ev.Event); + } + + private sealed class ToolDoAfterComplete : EntityEventArgs + { + public readonly object CompletedEvent; + public readonly object? CancelledEvent; + public readonly EntityUid Uid; + public readonly EntityUid UserUid; + public readonly float Fuel; + public readonly EntityUid? EventTarget; + + public ToolDoAfterComplete(object completedEvent, object? cancelledEvent, EntityUid uid, EntityUid userUid, float fuel, EntityUid? eventTarget = null) + { + CompletedEvent = completedEvent; + Uid = uid; + UserUid = userUid; + Fuel = fuel; + CancelledEvent = cancelledEvent; + EventTarget = eventTarget; + } + } + + private sealed class ToolDoAfterCancelled : EntityEventArgs + { + public readonly object Event; + public readonly EntityUid? EventTarget; + + public ToolDoAfterCancelled(object @event, EntityUid? eventTarget = null) + { + Event = @event; + EventTarget = eventTarget; + } + } }