]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
More DoAfter Changes (#14609)
authorLeon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Mon, 3 Apr 2023 01:13:48 +0000 (13:13 +1200)
committerGitHub <noreply@github.com>
Mon, 3 Apr 2023 01:13:48 +0000 (21:13 -0400)
* DoAfters

* Compact Clone()

* Fix mice and cuffables

* Try generalize attempt events

* moves climbabledoafter event to shared, fixes issue with climbable target

* Fix merge (cuffing)

* Make all events netserializable

* handful of doafter events moved

* moves the rest of the events to their respective shared folders

* Changes all mentions of server doafter to shared

* stop stripping cancellation

* fix merge errors

* draw paused doafters

* handle unpausing

* missing netserializable ref

* removes break on stun reference

* removes cuffing state reference

* Fix tools

* Fix door prying.

* Fix construction

* Fix dumping

* Fix wielding assert

* fix rev

* Fix test

* more test fixes

---------

Co-authored-by: keronshb <keronshb@live.com>
170 files changed:
Content.Client/Cuffs/CuffableSystem.cs
Content.Client/DoAfter/DoAfterOverlay.cs
Content.Client/DoAfter/DoAfterSystem.cs
Content.Client/Medical/Cryogenics/CryoPodSystem.cs
Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs
Content.IntegrationTests/Tests/DoAfter/DoAfterServerTest.cs
Content.Server/AirlockPainter/AirlockPainterSystem.cs
Content.Server/Alert/Click/RemoveEnsnare.cs
Content.Server/Animals/Components/UdderComponent.cs
Content.Server/Animals/Systems/UdderSystem.cs
Content.Server/Anomaly/AnomalySystem.Scanner.cs
Content.Server/Anomaly/AnomalySystem.cs
Content.Server/Body/Systems/InternalsSystem.cs
Content.Server/Botany/Systems/BotanySwabSystem.cs
Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs
Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs
Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs
Content.Server/Climbing/ClimbSystem.cs
Content.Server/Construction/AnchorableSystem.cs
Content.Server/Construction/Components/ConstructionComponent.cs
Content.Server/Construction/Components/WelderRefinableComponent.cs
Content.Server/Construction/ConstructionSystem.Initial.cs
Content.Server/Construction/ConstructionSystem.Interactions.cs
Content.Server/Construction/ConstructionSystem.cs
Content.Server/Construction/PartExchangerSystem.cs
Content.Server/Construction/RefiningSystem.cs
Content.Server/Cuffs/CuffableSystem.cs
Content.Server/Disease/DiseaseDiagnosisSystem.cs
Content.Server/Disease/DiseaseSystem.cs
Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs
Content.Server/DoAfter/DoAfterSystem.cs
Content.Server/Doors/Systems/DoorSystem.cs
Content.Server/Dragon/DragonSystem.cs
Content.Server/Engineering/Components/DisassembleOnAltVerbComponent.cs
Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs
Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs
Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs
Content.Server/Ensnaring/EnsnareableSystem.cs
Content.Server/Fluids/EntitySystems/MoppingSystem.cs
Content.Server/Fluids/EntitySystems/SpillableSystem.cs
Content.Server/Forensics/Systems/ForensicPadSystem.cs
Content.Server/Forensics/Systems/ForensicScannerSystem.cs
Content.Server/Gatherable/Components/GatheringToolComponent.cs
Content.Server/Gatherable/GatherableSystem.cs
Content.Server/Guardian/GuardianCreatorComponent.cs
Content.Server/Guardian/GuardianSystem.cs
Content.Server/Implants/ImplanterSystem.cs
Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs
Content.Server/Kitchen/EntitySystems/SharpSystem.cs
Content.Server/Light/EntitySystems/PoweredLightSystem.cs
Content.Server/Magic/MagicSystem.cs
Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs
Content.Server/Mech/Systems/MechEquipmentSystem.cs
Content.Server/Mech/Systems/MechSystem.cs
Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs
Content.Server/Medical/Components/HealingComponent.cs
Content.Server/Medical/CryoPodSystem.cs
Content.Server/Medical/HealingSystem.cs
Content.Server/Medical/HealthAnalyzerSystem.cs
Content.Server/Medical/Stethoscope/StethoscopeSystem.cs
Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs
Content.Server/Nuke/NukeSystem.cs
Content.Server/Nutrition/Components/DrinkComponent.cs
Content.Server/Nutrition/Components/FoodComponent.cs
Content.Server/Nutrition/EntitySystems/DrinkSystem.cs
Content.Server/Nutrition/EntitySystems/FoodSystem.cs
Content.Server/Power/EntitySystems/ApcSystem.cs
Content.Server/Power/EntitySystems/CableSystem.cs
Content.Server/RCD/Components/RCDComponent.cs
Content.Server/RCD/Systems/RCDSystem.cs
Content.Server/Repairable/RepairableSystem.cs
Content.Server/Resist/CanEscapeInventoryComponent.cs
Content.Server/Resist/EscapeInventorySystem.cs
Content.Server/Resist/ResistLockerComponent.cs
Content.Server/Resist/ResistLockerSystem.cs
Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs
Content.Server/Revenant/EntitySystems/RevenantSystem.cs
Content.Server/Sticky/Systems/StickySystem.cs
Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs
Content.Server/Storage/EntitySystems/DumpableSystem.cs
Content.Server/Storage/EntitySystems/StorageSystem.cs
Content.Server/Strip/StrippableSystem.cs
Content.Server/Teleportation/HandTeleporterSystem.cs
Content.Server/Toilet/ToiletSystem.cs
Content.Server/Tools/Components/LatticeCuttingComponent.cs
Content.Server/Tools/Components/TilePryingComponent.cs
Content.Server/Tools/Components/WeldableComponent.cs
Content.Server/Tools/Systems/WeldableSystem.cs
Content.Server/Tools/ToolSystem.LatticeCutting.cs
Content.Server/Tools/ToolSystem.TilePrying.cs
Content.Server/Tools/ToolSystem.Welder.cs
Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs
Content.Server/VendingMachines/VendingMachineSystem.cs
Content.Server/Wieldable/WieldableSystem.cs
Content.Server/Wires/WiresSystem.cs
Content.Shared/ActionBlocker/ActionBlockerSystem.cs
Content.Shared/AirlockPainter/AirlockPainterEvents.cs
Content.Shared/Anomaly/ScannerDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Chemistry/Components/SharedInjectorComponent.cs
Content.Shared/Climbing/SharedClimbSystem.cs
Content.Shared/Clothing/Components/ToggleableClothingComponent.cs
Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs
Content.Shared/Construction/Events.cs [new file with mode: 0644]
Content.Shared/Construction/SharedConstructionSystem.cs
Content.Shared/Cuffs/Components/CuffableComponent.cs
Content.Shared/Cuffs/Components/HandcuffComponent.cs
Content.Shared/Cuffs/SharedCuffableSystem.cs
Content.Shared/Disease/Events/VaccineDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Disposal/SharedDisposalUnitSystem.cs
Content.Shared/DoAfter/ActiveDoAfterComponent.cs
Content.Shared/DoAfter/DoAfter.cs
Content.Shared/DoAfter/DoAfterArgs.cs [new file with mode: 0644]
Content.Shared/DoAfter/DoAfterComponent.cs
Content.Shared/DoAfter/DoAfterEvent.cs [new file with mode: 0644]
Content.Shared/DoAfter/DoAfterEventArgs.cs [deleted file]
Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs [new file with mode: 0644]
Content.Shared/DoAfter/SharedDoAfterSystem.cs
Content.Shared/Doors/Components/DoorComponent.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Content.Shared/Dragon/DragonDevourDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Ensnaring/SharedEnsnareableSystem.cs
Content.Shared/Exchanger/ExchangerDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Fluids/Events.cs [new file with mode: 0644]
Content.Shared/Forensics/Events.cs [new file with mode: 0644]
Content.Shared/Gatherable/GatherableDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Implants/Components/ImplanterComponent.cs
Content.Shared/Implants/SharedImplanterSystem.cs
Content.Shared/Interaction/SharedInteractionSystem.cs
Content.Shared/Internals/InternalsDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Kitchen/SharedKitchenSpikeSystem.cs
Content.Shared/Kitchen/SharpDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Light/PoweredLightDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Magic/SpellbookDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Mech/EntitySystems/SharedMechSystem.cs
Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs
Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs
Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs
Content.Shared/Medical/HealingDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Medical/ReclaimerDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Medical/StethoscopeDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/MedicalScanner/SharedHealthAnalyzerComponent.cs
Content.Shared/Nuke/SharedNuke.cs
Content.Shared/Nutrition/Events.cs [new file with mode: 0644]
Content.Shared/Power/ApcToolFinishedEvent.cs [new file with mode: 0644]
Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs
Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs
Content.Shared/Repairable/SharedRepairableSystem.cs [new file with mode: 0644]
Content.Shared/Resist/EscapeInventoryEvent.cs [new file with mode: 0644]
Content.Shared/Resist/ResistLockerDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Revenant/SharedRevenant.cs
Content.Shared/Spillable/SpillDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Sticky/StickyDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Storage/Components/DumpableComponent.cs
Content.Shared/Storage/EntitySystems/BluespaceLockerDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Storage/Events.cs [new file with mode: 0644]
Content.Shared/Swab/SwabEvents.cs [new file with mode: 0644]
Content.Shared/Teleportation/Components/HandTeleporterComponent.cs
Content.Shared/Toilet/ToiletComponent.cs
Content.Shared/Tools/Components/ToolComponent.cs
Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs
Content.Shared/Tools/Systems/SharedToolSystem.cs [new file with mode: 0644]
Content.Shared/Tools/Systems/WeldFinishedEvent.cs [new file with mode: 0644]
Content.Shared/Udder/MilkingDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/VendingMachines/SharedVendingMachineSystem.cs
Content.Shared/Wieldable/WieldableDoAfterEvent.cs [new file with mode: 0644]
Content.Shared/Wires/Events.cs [new file with mode: 0644]
Content.Shared/Wires/SharedWiresComponent.cs
Content.Shared/Wires/WiresPanelComponent.cs

index 42d0ae03fe67bf09c75d7ad7eb2a7cdd80981558..e69d29222144014aadd8c35cd584cb82d5b99346 100644 (file)
@@ -25,7 +25,6 @@ public sealed class CuffableSystem : SharedCuffableSystem
         if (args.Current is not HandcuffComponentState state)
             return;
 
-        component.Cuffing = state.Cuffing;
         component.OverlayIconState = state.IconState;
     }
 
@@ -41,7 +40,6 @@ public sealed class CuffableSystem : SharedCuffableSystem
             return;
 
         component.CanStillInteract = cuffState.CanStillInteract;
-        component.Uncuffing = cuffState.Uncuffing;
         _actionBlocker.UpdateCanMove(uid);
 
         var ev = new CuffedStateChangeEvent();
index 1af3d8a6b25ea5e562b013171d6fb35935371ba7..090a8bc6a563cd79264df1fb772fe83adca52de1 100644 (file)
@@ -3,6 +3,7 @@ using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Shared.Enums;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Client.DoAfter;
@@ -10,17 +11,30 @@ namespace Content.Client.DoAfter;
 public sealed class DoAfterOverlay : Overlay
 {
     private readonly IEntityManager _entManager;
+    private readonly IGameTiming _timing;
     private readonly SharedTransformSystem _transform;
+    private readonly MetaDataSystem _meta;
 
     private readonly Texture _barTexture;
     private readonly ShaderInstance _shader;
 
+    /// <summary>
+    ///     Flash time for cancelled DoAfters
+    /// </summary>
+    private const float FlashTime = 0.125f;
+
+    // Hardcoded width of the progress bar because it doesn't match the texture.
+    private const float StartX = 2;
+    private const float EndX = 22f;
+
     public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
 
-    public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager)
+    public DoAfterOverlay(IEntityManager entManager, IPrototypeManager protoManager, IGameTiming timing)
     {
         _entManager = entManager;
+        _timing = timing;
         _transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
+        _meta = _entManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
         var sprite = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Interface/Misc/progress_bar.rsi"), "icon");
         _barTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite);
 
@@ -31,7 +45,6 @@ public sealed class DoAfterOverlay : Overlay
     {
         var handle = args.WorldHandle;
         var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
-        var spriteQuery = _entManager.GetEntityQuery<SpriteComponent>();
         var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
 
         // If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid.
@@ -40,43 +53,42 @@ public sealed class DoAfterOverlay : Overlay
         var rotationMatrix = Matrix3.CreateRotation(-rotation);
         handle.UseShader(_shader);
 
-        // TODO: Need active DoAfter component (or alternatively just make DoAfter itself active)
-        foreach (var comp in _entManager.EntityQuery<DoAfterComponent>(true))
+        var curTime = _timing.CurTime;
+
+        var bounds = args.WorldAABB.Enlarged(5f);
+
+        var metaQuery = _entManager.GetEntityQuery<MetaDataComponent>();
+        var enumerator = _entManager.AllEntityQueryEnumerator<ActiveDoAfterComponent, DoAfterComponent, SpriteComponent, TransformComponent>();
+        while (enumerator.MoveNext(out var uid, out _, out var comp, out var sprite, out var xform))
         {
-            if (comp.DoAfters.Count == 0 ||
-                !xformQuery.TryGetComponent(comp.Owner, out var xform) ||
-                xform.MapID != args.MapId)
-            {
+            if (xform.MapID != args.MapId)
                 continue;
-            }
 
-            var worldPosition = _transform.GetWorldPosition(xform);
-            var index = 0;
-            var worldMatrix = Matrix3.CreateTranslation(worldPosition);
+            if (comp.DoAfters.Count == 0)
+                continue;
 
-            foreach (var doAfter in comp.DoAfters.Values)
-            {
-                var elapsed = doAfter.Elapsed;
-                var displayRatio = MathF.Min(1.0f,
-                    (float)elapsed.TotalSeconds / doAfter.Delay);
+            var worldPosition = _transform.GetWorldPosition(xform, xformQuery);
+            if (!bounds.Contains(worldPosition))
+                continue;
 
-                Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
-                Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+            // If the entity is paused, we will draw the do-after as it was when the entity got paused.
+            var meta = metaQuery.GetComponent(uid);
+            var time = meta.EntityPaused
+                ? curTime - _meta.GetPauseTime(uid, meta)
+                : curTime;
 
-                handle.SetTransform(matty);
-                var offset = _barTexture.Height / scale * index;
+            var worldMatrix = Matrix3.CreateTranslation(worldPosition);
+            Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
+            Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
+            handle.SetTransform(matty);
 
+            var offset = 0f;
+
+            foreach (var doAfter in comp.DoAfters.Values)
+            {
                 // Use the sprite itself if we know its bounds. This means short or tall sprites don't get overlapped
                 // by the bar.
-                float yOffset;
-                if (spriteQuery.TryGetComponent(comp.Owner, out var sprite))
-                {
-                    yOffset = sprite.Bounds.Height / 2f + 0.05f;
-                }
-                else
-                {
-                    yOffset = 0.5f;
-                }
+                float yOffset = sprite.Bounds.Height / 2f + 0.05f;
 
                 // Position above the entity (we've already applied the matrix transform to the entity itself)
                 // Offset by the texture size for every do_after we have.
@@ -86,33 +98,30 @@ public sealed class DoAfterOverlay : Overlay
                 // Draw the underlying bar texture
                 handle.DrawTexture(_barTexture, position);
 
-                // Draw the bar itself
-                var cancelled = doAfter.Cancelled;
                 Color color;
-                const float flashTime = 0.125f;
+                float elapsedRatio;
 
                 // if we're cancelled then flick red / off.
-                if (cancelled)
+                if (doAfter.CancelledTime != null)
                 {
-                    var flash = Math.Floor((float)doAfter.CancelledElapsed.TotalSeconds / flashTime) % 2 == 0;
+                    var elapsed = doAfter.CancelledTime.Value - doAfter.StartTime;
+                    elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
+                    var cancelElapsed  = (time - doAfter.CancelledTime.Value).TotalSeconds;
+                    var flash = Math.Floor(cancelElapsed / FlashTime) % 2 == 0;
                     color = new Color(1f, 0f, 0f, flash ? 1f : 0f);
                 }
                 else
                 {
-                    color = GetProgressColor(displayRatio);
+                    var elapsed = time - doAfter.StartTime;
+                    elapsedRatio = (float) Math.Min(1, elapsed.TotalSeconds / doAfter.Args.Delay.TotalSeconds);
+                    color = GetProgressColor(elapsedRatio);
                 }
 
-                // Hardcoded width of the progress bar because it doesn't match the texture.
-                const float startX = 2f;
-                const float endX = 22f;
-
-                var xProgress = (endX - startX) * displayRatio + startX;
-
-                var box = new Box2(new Vector2(startX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter);
+                var xProgress = (EndX - StartX) * elapsedRatio + StartX;
+                var box = new Box2(new Vector2(StartX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter);
                 box = box.Translated(position);
                 handle.DrawRect(box, color);
-
-                index++;
+                offset += _barTexture.Height / scale;
             }
         }
 
index c4e06376da7a0a8456e67052f02bfd433b88586f..ecabaa20e87a5fb94e65dd0f8fcb95c52f0d4601 100644 (file)
@@ -1,9 +1,8 @@
 using Content.Shared.DoAfter;
+using Content.Shared.Hands.Components;
 using Robust.Client.Graphics;
 using Robust.Client.Player;
-using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
 
 namespace Content.Client.DoAfter;
 
@@ -16,19 +15,12 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
     [Dependency] private readonly IOverlayManager _overlay = default!;
     [Dependency] private readonly IPlayerManager _player = default!;
     [Dependency] private readonly IPrototypeManager _prototype = default!;
-
-    /// <summary>
-    ///     We'll use an excess time so stuff like finishing effects can show.
-    /// </summary>
-    public const float ExcessTime = 0.5f;
+    [Dependency] private readonly MetaDataSystem _metadata = default!;
 
     public override void Initialize()
     {
         base.Initialize();
-        UpdatesOutsidePrediction = true;
-        SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
-        SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
-        _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype));
+        _overlay.AddOverlay(new DoAfterOverlay(EntityManager, _prototype, GameTiming));
     }
 
     public override void Shutdown()
@@ -37,147 +29,26 @@ public sealed class DoAfterSystem : SharedDoAfterSystem
         _overlay.RemoveOverlay<DoAfterOverlay>();
     }
 
-    private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
-    {
-        if (args.Current is not DoAfterComponentState state)
-            return;
-
-        foreach (var (_, doAfter) in state.DoAfters)
-        {
-            if (component.DoAfters.ContainsKey(doAfter.ID))
-                continue;
-
-            component.DoAfters.Add(doAfter.ID, doAfter);
-        }
-    }
-
-    private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
+    public override void Update(float frameTime)
     {
-        if (!TryComp<DoAfterComponent>(ev.Uid, out var doAfter))
-            return;
+        // Currently this only predicts do afters initiated by the player.
 
-        Cancel(doAfter, ev.ID);
-    }
+        // TODO maybe predict do-afters if the local player is the target of some other players do-after? Specifically
+        // ones that depend on the target not moving, because the cancellation of those do afters should be readily
+        // predictable by clients.
 
-    /// <summary>
-    ///     Remove a DoAfter without showing a cancellation graphic.
-    /// </summary>
-    public void Remove(DoAfterComponent component, Shared.DoAfter.DoAfter doAfter, bool found = false)
-    {
-        component.DoAfters.Remove(doAfter.ID);
-        component.CancelledDoAfters.Remove(doAfter.ID);
-    }
+        var playerEntity = _player.LocalPlayer?.ControlledEntity;
 
-    /// <summary>
-    ///     Mark a DoAfter as cancelled and show a cancellation graphic.
-    /// </summary>
-    ///     Actual removal is handled by DoAfterEntitySystem.
-    public void Cancel(DoAfterComponent component, byte id)
-    {
-        if (component.CancelledDoAfters.ContainsKey(id))
+        if (!TryComp(playerEntity, out ActiveDoAfterComponent? active))
             return;
 
-        if (!component.DoAfters.ContainsKey(id))
+        if (_metadata.EntityPaused(playerEntity.Value))
             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<DoAfterComponent, TransformComponent>())
-        {
-            var doAfters = comp.DoAfters;
-
-            if (doAfters.Count == 0)
-                continue;
-
-            var userGrid = xform.Coordinates;
-            var toRemove = new RemQueue<Shared.DoAfter.DoAfter>();
-
-            // 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 ((float)doAfter.Elapsed.TotalSeconds + (float)doAfter.CancelledElapsed.TotalSeconds >
-                    doAfter.Delay + ExcessTime)
-                {
-                    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))
-                    {
-                        Cancel(comp, id);
-                        continue;
-                    }
-                }
-
-                if (doAfter.EventArgs.BreakOnTargetMove)
-                {
-                    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(comp, doAfter);
-            }
-
-            // Remove cancelled DoAfters after ExcessTime has elapsed
-            var toRemoveCancelled = new RemQueue<Shared.DoAfter.DoAfter>();
-
-            foreach (var (_, doAfter) in comp.CancelledDoAfters)
-            {
-                var cancelledElapsed = (float)doAfter.CancelledElapsed.TotalSeconds;
-
-                if (cancelledElapsed >  ExcessTime)
-                    toRemoveCancelled.Add(doAfter);
-            }
-
-            foreach (var doAfter in toRemoveCancelled)
-            {
-                Remove(comp, doAfter);
-            }
-        }
+        var time = GameTiming.CurTime;
+        var comp = Comp<DoAfterComponent>(playerEntity.Value);
+        var xformQuery = GetEntityQuery<TransformComponent>();
+        var handsQuery = GetEntityQuery<SharedHandsComponent>();
+        Update(playerEntity.Value, active, comp, time, xformQuery, handsQuery);
     }
 }
index fffbe3469b3936dbccb914afdece059c0762342a..d1918d75fba2f2a928ac799dde506238e3b23352 100644 (file)
@@ -19,7 +19,6 @@ public sealed class CryoPodSystem: SharedCryoPodSystem
         SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
         SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
         SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
-        SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
 
         SubscribeLocalEvent<CryoPodComponent, AppearanceChangeEvent>(OnAppearanceChange);
         SubscribeLocalEvent<InsideCryoPodComponent, ComponentStartup>(OnCryoPodInsertion);
index 5fe84b506c9dcf870659dc0cbb01bd18c6f17fe8..826bbf199bae349eebd826aad4e4a4903de7350a 100644 (file)
@@ -20,10 +20,22 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
 
         var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
         gameplayStateLoad.OnScreenLoad += OnScreenLoad;
+        gameplayStateLoad.OnScreenUnload += OnScreenUnload;
+    }
+
+    private void OnScreenUnload()
+    {
+        var widget = UI;
+        if (widget != null)
+            widget.AlertPressed -= OnAlertPressed;
     }
 
     private void OnScreenLoad()
     {
+        var widget = UI;
+        if (widget != null)
+            widget.AlertPressed += OnAlertPressed;
+
         SyncAlerts();
     }
 
@@ -43,14 +55,6 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
         {
             UI?.SyncControls(system, system.AlertOrder, e);
         }
-
-        // The UI can change underneath us if the user switches between HUD layouts
-        // So ensure we're subscribed to the AlertPressed callback.
-        if (UI != null)
-        {
-            UI.AlertPressed -= OnAlertPressed; // Ensure we don't hook into the callback twice
-            UI.AlertPressed += OnAlertPressed;
-        }
     }
 
     public void OnSystemLoaded(ClientAlertsSystem system)
@@ -65,13 +69,9 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
         system.ClearAlerts -= SystemOnClearAlerts;
     }
 
+
     public void OnStateEntered(GameplayState state)
     {
-        if (UI != null)
-        {
-            UI.AlertPressed += OnAlertPressed;
-        }
-
         // initially populate the frame if system is available
         SyncAlerts();
     }
index f9e6ddff39a83e049f3348ea2609830e12b043fe..db2df0ade2da628aca5bf8d48926d86f0d8f975c 100644 (file)
@@ -1,12 +1,14 @@
-using System.Threading;
+using System;
 using System.Threading.Tasks;
-using Content.Server.DoAfter;
 using Content.Shared.DoAfter;
 using NUnit.Framework;
 using Robust.Shared.GameObjects;
 using Robust.Shared.IoC;
 using Robust.Shared.Map;
+using Robust.Shared.Reflection;
+using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
+using Robust.Shared.Utility;
 
 namespace Content.IntegrationTests.Tests.DoAfter
 {
@@ -22,23 +24,40 @@ namespace Content.IntegrationTests.Tests.DoAfter
   - type: DoAfter
 ";
 
-        public sealed class TestDoAfterSystem : EntitySystem
+        private sealed class TestDoAfterEvent : DoAfterEvent
         {
-            public override void Initialize()
+            public override DoAfterEvent Clone()
             {
-                SubscribeLocalEvent<DoAfterEvent<TestDoAfterData>>(OnTestDoAfterFinishEvent);
+                return this;
             }
+        };
+
+        [Test]
+        public async Task TestSerializable()
+        {
+            await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
+            var server = pairTracker.Pair.Server;
+            await server.WaitIdleAsync();
+            var refMan = server.ResolveDependency<IReflectionManager>();
 
-            private void OnTestDoAfterFinishEvent(DoAfterEvent<TestDoAfterData> ev)
+            await server.WaitPost(() =>
             {
-                ev.AdditionalData.Cancelled = ev.Cancelled;
-            }
-        }
+                Assert.Multiple(() =>
+                {
+                    foreach (var type in refMan.GetAllChildren<DoAfterEvent>(true))
+                    {
+                        if (type.IsAbstract || type == typeof(TestDoAfterEvent))
+                            continue;
 
-        private sealed class TestDoAfterData
-        {
-            public bool Cancelled;
-        };
+                        Assert.That(type.HasCustomAttribute<NetSerializableAttribute>()
+                                    && type.HasCustomAttribute<SerializableAttribute>(),
+                            $"{nameof(DoAfterEvent)} is not NetSerializable. Event: {type.Name}");
+                    }
+                });
+            });
+
+            await pairTracker.CleanReturnAsync();
+        }
 
         [Test]
         public async Task TestFinished()
@@ -48,21 +67,21 @@ namespace Content.IntegrationTests.Tests.DoAfter
             await server.WaitIdleAsync();
 
             var entityManager = server.ResolveDependency<IEntityManager>();
-            var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
-            var data = new TestDoAfterData();
+            var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<SharedDoAfterSystem>();
+            var ev = new TestDoAfterEvent();
 
             // That it finishes successfully
             await server.WaitPost(() =>
             {
                 var tickTime = 1.0f / IoCManager.Resolve<IGameTiming>().TickRate;
                 var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace);
-                var cancelToken = new CancellationTokenSource();
-                var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token) { Broadcast = true };
-                doAfterSystem.DoAfter(args, data);
+                var args = new DoAfterArgs(mob, tickTime / 2, ev, null) { Broadcast = true };
+                Assert.That(doAfterSystem.TryStartDoAfter(args));
+                Assert.That(ev.Cancelled, Is.False);
             });
 
             await server.WaitRunTicks(1);
-            Assert.That(data.Cancelled, Is.False);
+            Assert.That(ev.Cancelled, Is.False);
 
             await pairTracker.CleanReturnAsync();
         }
@@ -73,22 +92,32 @@ namespace Content.IntegrationTests.Tests.DoAfter
             await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
             var server = pairTracker.Pair.Server;
             var entityManager = server.ResolveDependency<IEntityManager>();
-            var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<DoAfterSystem>();
-            var data = new TestDoAfterData();
+            var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem<SharedDoAfterSystem>();
+            DoAfterId? id = default;
+            var ev = new TestDoAfterEvent();
+
 
             await server.WaitPost(() =>
             {
                 var tickTime = 1.0f / IoCManager.Resolve<IGameTiming>().TickRate;
 
                 var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace);
-                var cancelToken = new CancellationTokenSource();
-                var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token) { Broadcast = true };
-                doAfterSystem.DoAfter(args, data);
-                cancelToken.Cancel();
+                var args = new DoAfterArgs(mob, tickTime * 2, ev, null) { Broadcast = true };
+
+                if (!doAfterSystem.TryStartDoAfter(args, out id))
+                {
+                    Assert.Fail();
+                    return;
+                }
+
+                Assert.That(!ev.Cancelled);
+                doAfterSystem.Cancel(id);
+                Assert.That(ev.Cancelled);
+
             });
 
             await server.WaitRunTicks(3);
-            Assert.That(data.Cancelled, Is.True);
+            Assert.That(ev.Cancelled);
 
             await pairTracker.CleanReturnAsync();
         }
index b21ee825e073c82f2fea22466ae77992349d6b8b..59f0d6ae7bd7cf2a2968507c4bfc6972248284f2 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
 using Content.Server.Popups;
 using Content.Server.UserInterface;
 using Content.Shared.AirlockPainter;
@@ -10,7 +9,6 @@ using Content.Shared.Doors.Components;
 using Content.Shared.Interaction;
 using JetBrains.Annotations;
 using Robust.Server.GameObjects;
-using Robust.Shared.Player;
 
 namespace Content.Server.AirlockPainter
 {
@@ -22,7 +20,7 @@ namespace Content.Server.AirlockPainter
     {
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
         [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -34,23 +32,21 @@ namespace Content.Server.AirlockPainter
             SubscribeLocalEvent<AirlockPainterComponent, AfterInteractEvent>(AfterInteractOn);
             SubscribeLocalEvent<AirlockPainterComponent, ActivateInWorldEvent>(OnActivate);
             SubscribeLocalEvent<AirlockPainterComponent, AirlockPainterSpritePickedMessage>(OnSpritePicked);
-            SubscribeLocalEvent<AirlockPainterComponent, DoAfterEvent<AirlockPainterData>>(OnDoAfter);
+            SubscribeLocalEvent<AirlockPainterComponent, AirlockPainterDoAfterEvent>(OnDoAfter);
         }
 
-        private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, DoAfterEvent<AirlockPainterData> args)
+        private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, AirlockPainterDoAfterEvent args)
         {
+            component.IsSpraying = false;
+
             if (args.Handled || args.Cancelled)
-            {
-                component.IsSpraying = false;
                 return;
-            }
 
             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);
+                _audio.PlayPvs(component.SpraySound, uid);
+                _appearance.SetData(args.Args.Target.Value, DoorVisuals.BaseRSI, args.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;
@@ -88,16 +84,14 @@ namespace Content.Server.AirlockPainter
             }
             component.IsSpraying = true;
 
-            var airlockPainterData = new AirlockPainterData(sprite);
-            var doAfterEventArgs = new DoAfterEventArgs(args.User, component.SprayTime, target:target, used:uid)
+            var doAfterEventArgs = new DoAfterArgs(args.User, component.SprayTime, new AirlockPainterDoAfterEvent(sprite), uid, target: target, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 NeedHand = true,
             };
-            _doAfterSystem.DoAfter(doAfterEventArgs, airlockPainterData);
+            _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
 
             // Log attempt
             _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is painting {ToPrettyString(uid):target} to '{style}' at {Transform(uid).Coordinates:targetlocation}");
@@ -118,10 +112,5 @@ namespace Content.Server.AirlockPainter
             _userInterfaceSystem.TrySetUiState(uid, AirlockPainterUiKey.Key,
                 new AirlockPainterBoundUserInterfaceState(component.Index));
         }
-
-        private record struct AirlockPainterData(string Sprite)
-        {
-            public string Sprite = Sprite;
-        }
     }
 }
index 975734152acf523ca2df4a6420d89749a9120397..1913dea90553e8caf26b0362237888770b87a6d7 100644 (file)
@@ -18,7 +18,7 @@ public sealed class RemoveEnsnare : IAlertClick
                 if (!entManager.TryGetComponent(ensnare, out EnsnaringComponent? ensnaringComponent))
                     return;
 
-                entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, ensnare, ensnaringComponent);
+                entManager.EntitySysManager.GetEntitySystem<EnsnareableSystem>().TryFree(player, player, ensnare, ensnaringComponent);
             }
         }
     }
index f47b9a855ce48f393da7594c275edb1994069d2a..ad591022c194871ebc99bf74e66120cf9477f7e6 100644 (file)
@@ -37,7 +37,5 @@ namespace Content.Server.Animals.Components
         public float UpdateRate = 5;
 
         public float AccumulatedFrameTime;
-
-        public bool BeingMilked;
     }
 }
index adb8a2bf83f35f186dabe85972a48a3298aa3480..ad0e6be27f362e68a30f2ef6738524bed67bbc2b 100644 (file)
@@ -1,13 +1,13 @@
 using Content.Server.Animals.Components;
 using Content.Server.Chemistry.Components.SolutionManager;
 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.Udder;
 using Content.Shared.Verbs;
 
 namespace Content.Server.Animals.Systems
@@ -18,7 +18,7 @@ namespace Content.Server.Animals.Systems
     internal sealed class UdderSystem : EntitySystem
     {
         [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
 
         public override void Initialize()
@@ -26,7 +26,7 @@ namespace Content.Server.Animals.Systems
             base.Initialize();
 
             SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
-            SubscribeLocalEvent<UdderComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<UdderComponent, MilkingDoAfterEvent>(OnDoAfter);
         }
         public override void Update(float frameTime)
         {
@@ -64,38 +64,21 @@ namespace Content.Server.Animals.Systems
             if (!Resolve(uid, ref udder))
                 return;
 
-            if (udder.BeingMilked)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("udder-system-already-milking"), uid, userUid);
-                return;
-            }
-
-            udder.BeingMilked = true;
-
-            var doargs = new DoAfterEventArgs(userUid, 5, target:uid, used:containerUid)
+            var doargs = new DoAfterArgs(userUid, 5, new MilkingDoAfterEvent(), uid, uid, used: containerUid)
             {
                 BreakOnUserMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 BreakOnTargetMove = true,
-                MovementThreshold = 1.0f
+                MovementThreshold = 1.0f,
             };
 
-            _doAfterSystem.DoAfter(doargs);
+            _doAfterSystem.TryStartDoAfter(doargs);
         }
 
-        private void OnDoAfter(EntityUid uid, UdderComponent component, DoAfterEvent args)
+        private void OnDoAfter(EntityUid uid, UdderComponent component, MilkingDoAfterEvent args)
         {
-            if (args.Cancelled)
-            {
-                component.BeingMilked = false;
+            if (args.Cancelled || args.Handled || args.Args.Used == null)
                 return;
-            }
-
-            if (args.Handled || args.Args.Used == null)
-                return;
-
-            component.BeingMilked = false;
 
             if (!_solutionContainerSystem.TryGetSolution(uid, component.TargetSolutionName, out var solution))
                 return;
@@ -103,6 +86,7 @@ namespace Content.Server.Animals.Systems
             if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution))
                 return;
 
+            args.Handled = true;
             var quantity = solution.Volume;
             if(quantity == 0)
             {
@@ -118,8 +102,6 @@ namespace Content.Server.Animals.Systems
 
             _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid,
                 args.Args.User, PopupType.Medium);
-
-            args.Handled = true;
         }
 
         private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent<AlternativeVerb> args)
index 3cd08b0de1282c98fe52d75b7b67a57af15c88af..2b090c1689828877a010268e38842a271709a5a3 100644 (file)
@@ -17,7 +17,7 @@ public sealed partial class AnomalySystem
     {
         SubscribeLocalEvent<AnomalyScannerComponent, BoundUIOpenedEvent>(OnScannerUiOpened);
         SubscribeLocalEvent<AnomalyScannerComponent, AfterInteractEvent>(OnScannerAfterInteract);
-        SubscribeLocalEvent<AnomalyScannerComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<AnomalyScannerComponent, ScannerDoAfterEvent>(OnDoAfter);
 
         SubscribeLocalEvent<AnomalyShutdownEvent>(OnScannerAnomalyShutdown);
         SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
@@ -81,7 +81,7 @@ public sealed partial class AnomalySystem
         if (!HasComp<AnomalyComponent>(target))
             return;
 
-        _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ScanDoAfterDuration, target:target, used:uid)
+        _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.ScanDoAfterDuration, new ScannerDoAfterEvent(), uid, target: target, used: uid)
         {
             DistanceThreshold = 2f
         });
index 3de46afb46b423e043337e9f9331409ba185465f..45e48364804d9a12f4090026d09d25a51adff910 100644 (file)
@@ -1,12 +1,12 @@
 using Content.Server.Anomaly.Components;
 using Content.Server.Atmos.EntitySystems;
 using Content.Server.Audio;
-using Content.Server.DoAfter;
 using Content.Server.Explosion.EntitySystems;
 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;
@@ -24,7 +24,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
     [Dependency] private readonly IPrototypeManager _prototype = default!;
     [Dependency] private readonly AmbientSoundSystem _ambient = default!;
     [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly ExplosionSystem _explosion = default!;
     [Dependency] private readonly MaterialStorageSystem _material = default!;
     [Dependency] private readonly RadioSystem _radio = default!;
index ed6ed288ccd5f8bc81df2d8211397ab6b1ac22eb..6b0c8e046e6fbc0d0e9f16ab16b58f4d50272483 100644 (file)
@@ -9,8 +9,8 @@ using Robust.Shared.Containers;
 using Robust.Shared.Prototypes;
 using Content.Shared.Verbs;
 using Content.Server.Popups;
-using Content.Server.DoAfter;
 using Content.Shared.DoAfter;
+using Content.Shared.Internals;
 using Robust.Shared.Utility;
 
 namespace Content.Server.Body.Systems;
@@ -20,7 +20,7 @@ public sealed class InternalsSystem : EntitySystem
     [Dependency] private readonly IPrototypeManager _protoManager = default!;
     [Dependency] private readonly AlertsSystem _alerts = default!;
     [Dependency] private readonly AtmosphereSystem _atmos = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly GasTankSystem _gasTank = default!;
     [Dependency] private readonly HandsSystem _hands = default!;
     [Dependency] private readonly InventorySystem _inventory = default!;
@@ -34,7 +34,7 @@ public sealed class InternalsSystem : EntitySystem
         SubscribeLocalEvent<InternalsComponent, ComponentStartup>(OnInternalsStartup);
         SubscribeLocalEvent<InternalsComponent, ComponentShutdown>(OnInternalsShutdown);
         SubscribeLocalEvent<InternalsComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
-        SubscribeLocalEvent<InternalsComponent, DoAfterEvent<InternalsData>>(OnDoAfter);
+        SubscribeLocalEvent<InternalsComponent, InternalsDoAfterEvent>(OnDoAfter);
     }
 
     private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent<InteractionVerb> args)
@@ -85,24 +85,19 @@ public sealed class InternalsSystem : EntitySystem
 
         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 = !isUser ? internals.Delay : 1.0f;
 
-            _doAfter.DoAfter(new DoAfterEventArgs(user, delay, target:uid)
+            _doAfter.TryStartDoAfter(new DoAfterArgs(user, delay, new InternalsDoAfterEvent(), uid, target: uid)
             {
                 BreakOnUserMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 BreakOnTargetMove = true,
                 MovementThreshold = 0.1f,
-                RaiseOnUser = isUser,
-                RaiseOnTarget = !isUser
-            }, internalsData);
+            });
 
             return;
         }
@@ -110,12 +105,12 @@ public sealed class InternalsSystem : EntitySystem
         _gasTank.ConnectToInternals(tank);
     }
 
-    private void OnDoAfter(EntityUid uid, InternalsComponent component, DoAfterEvent<InternalsData> args)
+    private void OnDoAfter(EntityUid uid, InternalsComponent component, InternalsDoAfterEvent args)
     {
         if (args.Cancelled || args.Handled)
             return;
 
-        ToggleInternals(uid, args.Args.User, true, component);
+        ToggleInternals(uid, args.User, true, component);
 
         args.Handled = true;
     }
@@ -266,9 +261,4 @@ public sealed class InternalsSystem : EntitySystem
 
         return null;
     }
-
-    private record struct InternalsData
-    {
-
-    }
 }
index ef7cc1760b34e603f91a15a8050093a326f317bd..5e5bff0f5cd873fb476c2eca77b03f5f0abe1d81 100644 (file)
@@ -1,15 +1,15 @@
 using Content.Server.Botany.Components;
-using Content.Server.DoAfter;
 using Content.Server.Popups;
 using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.Interaction;
+using Content.Shared.Swab;
 
 namespace Content.Server.Botany.Systems;
 
 public sealed class BotanySwabSystem : EntitySystem
 {
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly MutationSystem _mutationSystem = default!;
 
@@ -18,7 +18,7 @@ public sealed class BotanySwabSystem : EntitySystem
         base.Initialize();
         SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
         SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
-        SubscribeLocalEvent<BotanySwabComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<BotanySwabComponent, BotanySwabDoAfterEvent>(OnDoAfter);
     }
 
     /// <summary>
@@ -44,12 +44,11 @@ public sealed class BotanySwabSystem : EntitySystem
         if (args.Target == null || !args.CanReach || !HasComp<PlantHolderComponent>(args.Target))
             return;
 
-        _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
+        _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, swab.SwabDelay, new BotanySwabDoAfterEvent(), uid, target: args.Target, used: uid)
         {
             Broadcast = true,
             BreakOnTargetMove = true,
             BreakOnUserMove = true,
-            BreakOnStun = true,
             NeedHand = true
         });
     }
@@ -57,9 +56,9 @@ public sealed class BotanySwabSystem : EntitySystem
     /// <summary>
     /// Save seed data or cross-pollenate.
     /// </summary>
-    private void OnDoAfter(EntityUid uid, BotanySwabComponent component, DoAfterEvent args)
+    private void OnDoAfter(EntityUid uid, BotanySwabComponent swab, DoAfterEvent args)
     {
-        if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant) || !TryComp<BotanySwabComponent>(args.Args.Used, out var swab))
+        if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant))
             return;
 
         if (swab.SeedData == null)
index 598f470d6e734bf94a149fc7fa177ae7a680e60e..08bcf300d19152d43f023330530b8676dfb7bb44 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Server.Body.Components;
 using Content.Server.Chemistry.Components;
 using Content.Server.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Database;
@@ -28,7 +29,7 @@ public sealed partial class ChemistrySystem
     {
         SubscribeLocalEvent<InjectorComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
         SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
-        SubscribeLocalEvent<InjectorComponent, DoAfterEvent>(OnInjectDoAfter);
+        SubscribeLocalEvent<InjectorComponent, InjectorDoAfterEvent>(OnInjectDoAfter);
         SubscribeLocalEvent<InjectorComponent, ComponentStartup>(OnInjectorStartup);
         SubscribeLocalEvent<InjectorComponent, UseInHandEvent>(OnInjectorUse);
         SubscribeLocalEvent<InjectorComponent, AfterInteractEvent>(OnInjectorAfterInteract);
@@ -129,18 +130,10 @@ public sealed partial class ChemistrySystem
 
     private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args)
     {
-        if (args.Cancelled)
-        {
-            component.IsInjecting = false;
-            return;
-        }
-
-        if (args.Handled || args.Args.Target == null)
+        if (args.Cancelled || args.Handled || args.Args.Target == null)
             return;
 
         UseInjector(args.Args.Target.Value, args.Args.User, uid, component);
-
-        component.IsInjecting = false;
         args.Handled = true;
     }
 
@@ -223,10 +216,6 @@ public sealed partial class ChemistrySystem
         if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution))
             return;
 
-        //If it found it's injecting
-        if (component.IsInjecting)
-            return;
-
         var actualDelay = MathF.Max(component.Delay, 1f);
 
         // Injections take 1 second longer per additional 5u
@@ -269,17 +258,12 @@ public sealed partial class ChemistrySystem
                 _adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
         }
 
-        component.IsInjecting = true;
-
-        _doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector)
+        _doAfter.TryStartDoAfter(new DoAfterArgs(user, actualDelay, new InjectorDoAfterEvent(), injector, target: target, used: injector)
         {
-            RaiseOnTarget = isTarget,
-            RaiseOnUser = !isTarget,
             BreakOnUserMove = true,
             BreakOnDamage = true,
-            BreakOnStun = true,
             BreakOnTargetMove = true,
-            MovementThreshold = 0.1f
+            MovementThreshold = 0.1f,
         });
     }
 
@@ -434,4 +418,5 @@ public sealed partial class ChemistrySystem
         Dirty(component);
         AfterDraw(component, injector);
     }
+
 }
index 6efb96b55bc97e880d80a428fd8ad829a47e7054..b4425f66ce5f1a1bf6edbf44d6b131f12da37113 100644 (file)
@@ -1,10 +1,10 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
 using Content.Server.Interaction;
 using Content.Server.Popups;
 using Content.Shared.CombatMode;
 using Content.Shared.Chemistry;
+using Content.Shared.DoAfter;
 using Content.Shared.Mobs.Systems;
 
 namespace Content.Server.Chemistry.EntitySystems;
@@ -15,7 +15,7 @@ public sealed partial class ChemistrySystem : EntitySystem
     [Dependency] private readonly IEntityManager _entMan = default!;
     [Dependency] private readonly InteractionSystem _interaction = default!;
     [Dependency] private readonly BloodstreamSystem _blood = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly PopupSystem _popup = default!;
     [Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
index 13ee95be14614d0b7d170b26076711c6b52e881a..8f9a20e239bd730263032c6d7f33aa8a24acefc6 100644 (file)
@@ -317,11 +317,11 @@ public sealed partial class SolutionContainerSystem : EntitySystem
         return true;
     }
 
-    public bool TryGetSolution(EntityUid uid, string name,
+    public bool TryGetSolution([NotNullWhen(true)] EntityUid? uid, string name,
         [NotNullWhen(true)] out Solution? solution,
         SolutionContainerManagerComponent? solutionsMgr = null)
     {
-        if (!Resolve(uid, ref solutionsMgr, false))
+        if (uid == null || !Resolve(uid.Value, ref solutionsMgr, false))
         {
             solution = null;
             return false;
index 3befaa3e87a4c96f1f355ad10e60edef2309ebe7..82e41d66cef2a5cbd88c4b96b11634b6a13cbabc 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Server.Body.Systems;
 using Content.Server.Climbing.Components;
-using Content.Server.DoAfter;
 using Content.Server.Interaction;
 using Content.Server.Popups;
 using Content.Server.Stunnable;
@@ -36,7 +35,7 @@ public sealed class ClimbSystem : SharedClimbSystem
     [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
     [Dependency] private readonly BodySystem _bodySystem = default!;
     [Dependency] private readonly DamageableSystem _damageableSystem = default!;
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly FixtureSystem _fixtureSystem = default!;
     [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly InteractionSystem _interactionSystem = default!;
@@ -57,7 +56,7 @@ public sealed class ClimbSystem : SharedClimbSystem
         SubscribeLocalEvent<ClimbableComponent, GetVerbsEvent<AlternativeVerb>>(AddClimbableVerb);
         SubscribeLocalEvent<ClimbableComponent, DragDropTargetEvent>(OnClimbableDragDrop);
 
-        SubscribeLocalEvent<ClimbingComponent, DoAfterEvent<ClimbExtraEvent>>(OnDoAfter);
+        SubscribeLocalEvent<ClimbingComponent, ClimbDoAfterEvent>(OnDoAfter);
         SubscribeLocalEvent<ClimbingComponent, EndCollideEvent>(OnClimbEndCollide);
         SubscribeLocalEvent<ClimbingComponent, BuckleChangeEvent>(OnBuckleChange);
         SubscribeLocalEvent<ClimbingComponent, ComponentGetState>(OnClimbingGetState);
@@ -114,22 +113,17 @@ public sealed class ClimbSystem : SharedClimbSystem
         if (_bonkSystem.TryBonk(entityToMove, climbable))
             return;
 
-        var ev = new ClimbExtraEvent();
-
-        var args = new DoAfterEventArgs(user, component.ClimbDelay, target: climbable, used: entityToMove)
+        var args = new DoAfterArgs(user, component.ClimbDelay, new ClimbDoAfterEvent(), entityToMove, target: climbable, used: entityToMove)
         {
             BreakOnTargetMove = true,
             BreakOnUserMove = true,
-            BreakOnDamage = true,
-            BreakOnStun = true,
-            RaiseOnUser = false,
-            RaiseOnTarget = false,
+            BreakOnDamage = true
         };
 
-        _doAfterSystem.DoAfter(args, ev);
+        _doAfterSystem.TryStartDoAfter(args);
     }
 
-    private void OnDoAfter(EntityUid uid, ClimbingComponent component, DoAfterEvent<ClimbExtraEvent> args)
+    private void OnDoAfter(EntityUid uid, ClimbingComponent component, ClimbDoAfterEvent args)
     {
         if (args.Handled || args.Cancelled || args.Args.Target == null || args.Args.Used == null)
             return;
@@ -436,10 +430,6 @@ public sealed class ClimbSystem : SharedClimbSystem
         _fixtureRemoveQueue.Clear();
     }
 
-    private sealed class ClimbExtraEvent : EntityEventArgs
-    {
-        //Honestly this is only here because otherwise this activates on every single doafter on a human
-    }
 }
 
 /// <summary>
index 3cb09d642f3f01134e41951b83adb82382fb6885..4d22fa5fb1fd1afb56ff2de284ab19b62ce21890 100644 (file)
@@ -5,6 +5,7 @@ using Content.Server.Pulling;
 using Content.Shared.Construction.Components;
 using Content.Shared.Construction.EntitySystems;
 using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.Pulling.Components;
 using Content.Shared.Tools;
@@ -40,23 +41,29 @@ namespace Content.Server.Construction
 
         private void OnUnanchorComplete(EntityUid uid, AnchorableComponent component, TryUnanchorCompletedEvent args)
         {
+            if (args.Cancelled || args.Used is not { } used)
+                return;
+
             var xform = Transform(uid);
 
-            RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, args.Using));
+            RaiseLocalEvent(uid, new BeforeUnanchoredEvent(args.User, used));
             xform.Anchored = false;
-            RaiseLocalEvent(uid, new UserUnanchoredEvent(args.User, args.Using));
+            RaiseLocalEvent(uid, new UserUnanchoredEvent(args.User, used));
 
             _popup.PopupEntity(Loc.GetString("anchorable-unanchored"), uid);
 
             _adminLogger.Add(
                 LogType.Unanchor,
                 LogImpact.Low,
-                $"{EntityManager.ToPrettyString(args.User):user} unanchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(args.Using):using}"
+                $"{EntityManager.ToPrettyString(args.User):user} unanchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}"
             );
         }
 
         private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryAnchorCompletedEvent args)
         {
+            if (args.Cancelled || args.Used is not { } used)
+                return;
+
             var xform = Transform(uid);
             if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
                 !TileFree(xform.Coordinates, anchorBody))
@@ -78,16 +85,16 @@ namespace Content.Server.Construction
             if (component.Snap)
                 xform.Coordinates = xform.Coordinates.SnapToGrid(EntityManager, _mapManager);
 
-            RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, args.Using));
+            RaiseLocalEvent(uid, new BeforeAnchoredEvent(args.User, used));
             xform.Anchored = true;
-            RaiseLocalEvent(uid, new UserAnchoredEvent(args.User, args.Using));
+            RaiseLocalEvent(uid, new UserAnchoredEvent(args.User, used));
 
             _popup.PopupEntity(Loc.GetString("anchorable-anchored"), uid);
 
             _adminLogger.Add(
                 LogType.Anchor,
                 LogImpact.Low,
-                $"{EntityManager.ToPrettyString(args.User):user} anchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(args.Using):using}"
+                $"{EntityManager.ToPrettyString(args.User):user} anchored {EntityManager.ToPrettyString(uid):anchored} using {EntityManager.ToPrettyString(used):using}"
             );
         }
 
@@ -182,8 +189,7 @@ namespace Content.Server.Construction
                 return;
             }
 
-            var toolEvData = new ToolEventData(new TryAnchorCompletedEvent(userUid, usingUid), targetEntity:uid);
-            _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
+            _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryAnchorCompletedEvent());
         }
 
         /// <summary>
@@ -204,8 +210,7 @@ namespace Content.Server.Construction
             if (!Valid(uid, userUid, usingUid, false))
                 return;
 
-            var toolEvData = new ToolEventData(new TryUnanchorCompletedEvent(userUid, usingUid), targetEntity:uid);
-            _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, toolEvData);
+            _tool.UseTool(usingUid, userUid, uid, anchorable.Delay, usingTool.Qualities, new TryUnanchorCompletedEvent());
         }
 
         /// <summary>
@@ -236,32 +241,5 @@ namespace Content.Server.Construction
                 _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}");
             }
         }
-
-        private abstract class AnchorEvent : EntityEventArgs
-        {
-            public EntityUid User;
-            public EntityUid Using;
-
-            protected AnchorEvent(EntityUid userUid, EntityUid usingUid)
-            {
-                User = userUid;
-                Using = usingUid;
-            }
-        }
-
-        private sealed class TryUnanchorCompletedEvent : AnchorEvent
-        {
-            public TryUnanchorCompletedEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
-            {
-            }
-        }
-
-
-        private sealed class TryAnchorCompletedEvent : AnchorEvent
-        {
-            public TryAnchorCompletedEvent(EntityUid userUid, EntityUid usingUid) : base(userUid, usingUid)
-            {
-            }
-        }
     }
 }
index 6274128423a4ab5ae74665a48f67ca8f422a4c3f..08ffb99fe84325535e62bc111e181b759da2cdd2 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Shared.Construction.Prototypes;
+using Content.Shared.DoAfter;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Server.Construction.Components
@@ -33,10 +34,12 @@ namespace Content.Server.Construction.Components
         [DataField("deconstructionTarget")]
         public string? DeconstructionNode { get; set; } = "start";
 
-        [ViewVariables]
-        public bool WaitingDoAfter { get; set; } = false;
+        [DataField("doAfter")]
+        public DoAfterId? DoAfter;
 
         [ViewVariables]
+        // TODO Force flush interaction queue before serializing to YAML.
+        // Otherwise you can end up with entities stuck in invalid states (e.g., waiting for DoAfters).
         public readonly Queue<object> InteractionQueue = new();
     }
 }
index a2bca0f4307e6ebe42220bf065b13bced3d2771c..c0bfd9fce92f426e56571e81627233847450369e 100644 (file)
@@ -21,7 +21,5 @@ namespace Content.Server.Construction.Components
 
         [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
         public string QualityNeeded = "Welding";
-
-        public bool BeingWelded;
     }
 }
index e72848031a14c2095235f6b15b36332c909e4c5b..dc002be2a4ff556bfd384bdc87100cef40c1f42d 100644 (file)
@@ -227,10 +227,9 @@ namespace Content.Server.Construction
                 return null;
             }
 
-            var doAfterArgs = new DoAfterEventArgs(user, doAfterTime)
+            var doAfterArgs = new DoAfterArgs(user, doAfterTime, new AwaitedDoAfterEvent(), null)
             {
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 BreakOnTargetMove = false,
                 BreakOnUserMove = true,
                 NeedHand = false,
index e39a4c3d39cc3a0f8a6094bc4ab9f610a31c5c89..bfe8f7e04df83a96cf13cfb33721b1e6e5fd3b8b 100644 (file)
@@ -9,6 +9,7 @@ using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Tools.Components;
 using Robust.Shared.Containers;
+using Robust.Shared.Map;
 using Robust.Shared.Utility;
 #if EXCEPTION_TOLERANCE
 // ReSharper disable once RedundantUsingDirective
@@ -29,19 +30,7 @@ namespace Content.Server.Construction
 
         private void InitializeInteractions()
         {
-            #region DoAfter Subscriptions
-
-            // DoAfter handling.
-            // The ConstructionDoAfter events are meant to be raised either directed or broadcast.
-            // If they're raised broadcast, we will re-raise them as directed on the target.
-            // This allows us to easily use the DoAfter system for our purposes.
-            SubscribeLocalEvent<ConstructionDoAfterComplete>(OnDoAfterComplete);
-            SubscribeLocalEvent<ConstructionDoAfterCancelled>(OnDoAfterCancelled);
-            SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterComplete>(EnqueueEvent);
-            SubscribeLocalEvent<ConstructionComponent, ConstructionDoAfterCancelled>(EnqueueEvent);
-            SubscribeLocalEvent<ConstructionComponent, DoAfterEvent<ConstructionData>>(OnDoAfter);
-
-            #endregion
+            SubscribeLocalEvent<ConstructionComponent, ConstructionInteractDoAfterEvent>(EnqueueEvent);
 
             // Event handling. Add your subscriptions here! Just make sure they're all handled by EnqueueEvent.
             SubscribeLocalEvent<ConstructionComponent, InteractUsingEvent>(EnqueueEvent, new []{typeof(AnchorableSystem)});
@@ -158,11 +147,13 @@ namespace Content.Server.Construction
             if (!CheckConditions(uid, edge.Conditions))
                 return HandleResult.False;
 
-            // We can only perform the "step completed" logic if this returns true.
-            if (HandleStep(uid, ev, step, validation, out var user, construction)
-                is var handle and not HandleResult.True)
+            var handle = HandleStep(uid, ev, step, validation, out var user, construction);
+            if (handle is not HandleResult.True)
                 return handle;
 
+            // Handle step should never handle the interaction during validation.
+            DebugTools.Assert(!validation);
+
             // We increase the step index, meaning we move to the next step!
             construction.StepIndex++;
 
@@ -198,10 +189,12 @@ namespace Content.Server.Construction
 
             // Let HandleInteraction actually handle the event for this step.
             // We can only perform the rest of our logic if it returns true.
-            if (HandleInteraction(uid, ev, step, validation, out user, construction)
-                is var handle and not HandleResult.True)
+            var handle = HandleInteraction(uid, ev, step, validation, out user, construction);
+            if (handle is not HandleResult.True)
                 return handle;
 
+            DebugTools.Assert(!validation);
+
             // Actually perform the step completion actions, since the step was handled correctly.
             PerformActions(uid, user, step.Completed);
 
@@ -225,48 +218,29 @@ namespace Content.Server.Construction
                 return HandleResult.False;
 
             // Whether this event is being re-handled after a DoAfter or not. Check DoAfterState for more info.
-            var doAfterState = validation ? DoAfterState.Validation : DoAfterState.None;
-
-            // Custom data from a prior HandleInteraction where a DoAfter was called...
-            object? doAfterData = null;
+            var doAfterState = DoAfterState.None;
 
             // The DoAfter events can only perform special logic when we're not validating events.
-            if (!validation)
+            if (ev is ConstructionInteractDoAfterEvent interactDoAfter)
             {
-                // Some events are handled specially... Such as doAfter.
-                switch (ev)
-                {
-                    case ConstructionDoAfterComplete complete:
-                    {
-                        // DoAfter completed!
-                        ev = complete.WrappedEvent;
-                        doAfterState = DoAfterState.Completed;
-                        doAfterData = complete.CustomData;
-                        construction.WaitingDoAfter = false;
-                        break;
-                    }
+                if (!validation)
+                    construction.DoAfter = null;
 
-                    case ConstructionDoAfterCancelled cancelled:
-                    {
-                        // DoAfter failed!
-                        ev = cancelled.WrappedEvent;
-                        doAfterState = DoAfterState.Cancelled;
-                        doAfterData = cancelled.CustomData;
-                        construction.WaitingDoAfter = false;
-                        break;
-                    }
-                }
-            }
+                if (interactDoAfter.Cancelled)
+                    return HandleResult.False;
 
-            // Can't perform any interactions while we're waiting for a DoAfter...
-            // This also makes any event validation fail.
-            if (construction.WaitingDoAfter)
-                return HandleResult.False;
+                ev = new InteractUsingEvent(
+                    interactDoAfter.User,
+                    interactDoAfter.Used!.Value,
+                    uid,
+                    interactDoAfter.ClickLocation);
+
+                doAfterState = DoAfterState.Completed;
+            }
 
             // The cases in this switch will handle the interaction and return
             switch (step)
             {
-
                 // --- CONSTRUCTION STEP EVENT HANDLING START ---
                 #region Construction Step Event Handling
                 // So you want to create your own custom step for construction?
@@ -282,14 +256,16 @@ namespace Content.Server.Construction
                     if (ev is not InteractUsingEvent interactUsing)
                         break;
 
+                    if (construction.DoAfter != null && !validation)
+                    {
+                        _doAfterSystem.Cancel(construction.DoAfter);
+                        return HandleResult.False;
+                    }
+
                     // TODO: Sanity checks.
 
                     user = interactUsing.User;
 
-                    // If this step's DoAfter was cancelled, we just fail the interaction.
-                    if (doAfterState == DoAfterState.Cancelled)
-                        return HandleResult.False;
-
                     var insert = interactUsing.Used;
 
                     // Since many things inherit this step, we delegate the "is this entity valid?" logic to them.
@@ -298,29 +274,34 @@ namespace Content.Server.Construction
                         return HandleResult.False;
 
                     // If we're only testing whether this step would be handled by the given event, then we're done.
-                    if (doAfterState == DoAfterState.Validation)
+                    if (validation)
                         return HandleResult.Validated;
 
                     // If we still haven't completed this step's DoAfter...
                     if (doAfterState == DoAfterState.None && insertStep.DoAfter > 0)
                     {
-                        // 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)
+                        var doAfterEv = new ConstructionInteractDoAfterEvent(interactUsing);
+
+                        var doAfterEventArgs = new DoAfterArgs(interactUsing.User, step.DoAfter, doAfterEv, uid, uid, interactUsing.Used)
                         {
                             BreakOnDamage = false,
-                            BreakOnStun = true,
                             BreakOnTargetMove = true,
                             BreakOnUserMove = true,
                             NeedHand = true
                         };
 
-                        _doAfterSystem.DoAfter(doAfterEventArgs, constructionData);
+                        var started  = _doAfterSystem.TryStartDoAfter(doAfterEventArgs, out construction.DoAfter);
 
-                        // 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.
-                        construction.WaitingDoAfter = true;
+                        if (!started)
+                            return HandleResult.False;
+
+#if DEBUG
+                        // Verify that the resulting DoAfter event will be handled by the current construction state.
+                        // if it can't what is even the point of raising this DoAfter?
+                        doAfterEv.DoAfter = new(default, doAfterEventArgs, default);
+                        var result = HandleInteraction(uid, doAfterEv, step, validation: true, out _, construction);
+                        DebugTools.Assert(result == HandleResult.Validated);
+#endif
                         return HandleResult.DoAfter;
                     }
 
@@ -362,40 +343,47 @@ namespace Content.Server.Construction
                     if (ev is not InteractUsingEvent interactUsing)
                         break;
 
+                    if (construction.DoAfter != null && !validation)
+                    {
+                        _doAfterSystem.Cancel(construction.DoAfter);
+                        return HandleResult.False;
+                    }
+
                     // TODO: Sanity checks.
 
                     user = interactUsing.User;
 
                     // If we're validating whether this event handles the step...
-                    if (doAfterState == DoAfterState.Validation)
+                    if (validation)
                     {
                         // Then we only really need to check whether the tool entity has that quality or not.
+                        // TODO fuel consumption?
                         return _toolSystem.HasQuality(interactUsing.Used, toolInsertStep.Tool)
-                            ? HandleResult.Validated : HandleResult.False;
+                            ? HandleResult.Validated
+                            : HandleResult.False;
                     }
 
                     // If we're handling an event after its DoAfter finished...
-                    if (doAfterState != DoAfterState.None)
-                        return doAfterState == DoAfterState.Completed ? HandleResult.True : HandleResult.False;
-
-                    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!
-                    if (toolInsertStep.DoAfter <= 0)
-                        return HandleResult.True;
-
-                    construction.WaitingDoAfter = true;
-                    return HandleResult.DoAfter;
+                    if (doAfterState == DoAfterState.Completed)
+                        return  HandleResult.True;
+
+                    var result  = _toolSystem.UseTool(
+                        interactUsing.Used,
+                        interactUsing.User,
+                        uid,
+                        TimeSpan.FromSeconds(toolInsertStep.DoAfter),
+                        new [] { toolInsertStep.Tool },
+                        new ConstructionInteractDoAfterEvent(interactUsing),
+                        out construction.DoAfter,
+                        fuel: toolInsertStep.Fuel);
+
+                    return construction.DoAfter != null ? HandleResult.DoAfter : HandleResult.False;
                 }
 
                 case TemperatureConstructionGraphStep temperatureChangeStep:
                 {
-                    if (ev is not OnTemperatureChangeEvent) {
+                    if (ev is not OnTemperatureChangeEvent)
                         break;
-                    }
 
                     if (TryComp<TemperatureComponent>(uid, out var tempComp))
                     {
@@ -550,7 +538,8 @@ namespace Content.Server.Construction
         ///          in which case they will also be set as handled.</remarks>
         private void EnqueueEvent(EntityUid uid, ConstructionComponent construction, object args)
         {
-            // Handled events get treated specially.
+            // For handled events, we will check if the event leads to a valid construction interaction.
+            // If it does, we mark the event as handled and then enqueue it as normal.
             if (args is HandledEntityEventArgs handled)
             {
                 // If they're already handled, we do nothing.
@@ -572,95 +561,6 @@ namespace Content.Server.Construction
             if (_queuedUpdates.Add(uid))
                 _constructionUpdateQueue.Enqueue(uid);
         }
-
-        private void OnDoAfter(EntityUid uid, ConstructionComponent component, DoAfterEvent<ConstructionData> args)
-        {
-            if (!Exists(args.Args.Target) || args.Handled)
-                return;
-
-            if (args.Cancelled)
-            {
-                RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CancelEvent);
-                args.Handled = true;
-                return;
-            }
-
-            RaiseLocalEvent(args.Args.Target.Value, args.AdditionalData.CompleteEvent);
-            args.Handled = true;
-        }
-
-        private void OnDoAfterComplete(ConstructionDoAfterComplete ev)
-        {
-            // Make extra sure the target entity exists...
-            if (!Exists(ev.TargetUid))
-                return;
-
-            // Re-raise this event, but directed on the target UID.
-            RaiseLocalEvent(ev.TargetUid, ev, false);
-        }
-
-        private void OnDoAfterCancelled(ConstructionDoAfterCancelled ev)
-        {
-            // Make extra sure the target entity exists...
-            if (!Exists(ev.TargetUid))
-                return;
-
-            // Re-raise this event, but directed on the target UID.
-            RaiseLocalEvent(ev.TargetUid, ev, false);
-        }
-
-        #endregion
-
-        #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;
-            }
-        }
-
-        /// <summary>
-        ///     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.
-        /// </summary>
-        private sealed class ConstructionDoAfterComplete : EntityEventArgs
-        {
-            public readonly EntityUid TargetUid;
-            public readonly object WrappedEvent;
-            public readonly object? CustomData;
-
-            public ConstructionDoAfterComplete(EntityUid targetUid, object wrappedEvent, object? customData = null)
-            {
-                TargetUid = targetUid;
-                WrappedEvent = wrappedEvent;
-                CustomData = customData;
-            }
-        }
-
-        /// <summary>
-        ///     This event signals that a construction interaction's DoAfter has failed or has been cancelled.
-        ///     This wraps the original event and also keeps some custom data that event handlers might need.
-        /// </summary>
-        private sealed class ConstructionDoAfterCancelled : EntityEventArgs
-        {
-            public readonly EntityUid TargetUid;
-            public readonly object WrappedEvent;
-            public readonly object? CustomData;
-
-            public ConstructionDoAfterCancelled(EntityUid targetUid, object wrappedEvent, object? customData = null)
-            {
-                TargetUid = targetUid;
-                WrappedEvent = wrappedEvent;
-                CustomData = customData;
-            }
-        }
-
         #endregion
 
         #region Internal Enum Definitions
@@ -676,23 +576,11 @@ namespace Content.Server.Construction
             /// </summary>
             None,
 
-            /// <summary>
-            ///     If Validation, we want to validate whether the specified event would handle the step or not.
-            ///     Will NOT modify the construction state at all.
-            /// </summary>
-            Validation,
-
             /// <summary>
             ///     If Completed, this is the second (and last) time we're seeing this event, and
             ///     the doAfter that was called the first time successfully completed. Handle completion logic now.
             /// </summary>
-            Completed,
-
-            /// <summary>
-            ///     If Cancelled, this is the second (and last) time we're seeing this event, and
-            ///     the doAfter that was called the first time was cancelled. Handle cleanup logic now.
-            /// </summary>
-            Cancelled
+            Completed
         }
 
         /// <summary>
index 45b8f232df23ec632dedfa5f40d8c187b9956652..237540f9feda2104837af5b2aff1437041dcc958 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Server.Construction.Components;
-using Content.Server.DoAfter;
 using Content.Server.Stack;
 using Content.Shared.Construction;
+using Content.Shared.DoAfter;
 using Content.Shared.Tools;
 using JetBrains.Annotations;
 using Robust.Server.Containers;
@@ -19,7 +19,7 @@ namespace Content.Server.Construction
         [Dependency] private readonly ILogManager _logManager = default!;
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
         [Dependency] private readonly IRobustRandom _robustRandom = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly ContainerSystem _container = default!;
         [Dependency] private readonly StackSystem _stackSystem = default!;
         [Dependency] private readonly SharedToolSystem _toolSystem = default!;
index c865f47926494d9ee19f3a8d0fd6971147d50edc..2ea4cd10f37c0f428453c8c39cf6ae3ae513ff27 100644 (file)
@@ -1,10 +1,10 @@
 using System.Linq;
 using Content.Server.Construction.Components;
-using Content.Server.DoAfter;
 using Content.Server.Storage.Components;
 using Content.Server.Storage.EntitySystems;
 using Content.Shared.DoAfter;
 using Content.Shared.Construction.Components;
+using Content.Shared.Exchanger;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
 using Robust.Shared.Containers;
@@ -16,7 +16,7 @@ namespace Content.Server.Construction;
 public sealed class PartExchangerSystem : EntitySystem
 {
     [Dependency] private readonly ConstructionSystem _construction = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedContainerSystem _container = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -26,18 +26,14 @@ public sealed class PartExchangerSystem : EntitySystem
     public override void Initialize()
     {
         SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
-        SubscribeLocalEvent<PartExchangerComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<PartExchangerComponent, ExchangerDoAfterEvent>(OnDoAfter);
     }
 
     private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
     {
+        component.AudioStream?.Stop();
         if (args.Cancelled || args.Handled || args.Args.Target == null)
-        {
-            component.AudioStream?.Stop();
             return;
-        }
-
-        component.AudioStream?.Stop();
 
         if (!TryComp<MachineComponent>(args.Args.Target.Value, out var machine))
             return;
@@ -112,11 +108,11 @@ public sealed class PartExchangerSystem : EntitySystem
 
         component.AudioStream = _audio.PlayPvs(component.ExchangeSound, uid);
 
-        _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.ExchangeDuration, target:args.Target, used:args.Used)
+        _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid)
         {
             BreakOnDamage = true,
-            BreakOnStun = true,
             BreakOnUserMove = true
         });
     }
+
 }
index 73b4f8d760e6eb57d394ff491982873587ad7118..cd4eb533e3bcbf7977e56019e91c3c11f6341000 100644 (file)
@@ -1,9 +1,11 @@
 using Content.Server.Construction.Components;
 using Content.Server.Stack;
+using Content.Shared.Construction;
+using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Stacks;
 using Content.Shared.Tools;
-using Content.Shared.Tools.Components;
+using Robust.Shared.Serialization;
 
 namespace Content.Server.Construction
 {
@@ -15,28 +17,21 @@ namespace Content.Server.Construction
         {
             base.Initialize();
             SubscribeLocalEvent<WelderRefinableComponent, InteractUsingEvent>(OnInteractUsing);
+            SubscribeLocalEvent<WelderRefinableComponent, WelderRefineDoAfterEvent>(OnDoAfter);
         }
 
-        private async void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args)
+        private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args)
         {
-            // check if object is welder
-            if (!HasComp<ToolComponent>(args.Used))
+            if (args.Handled)
                 return;
 
-            // check if someone is already welding object
-            if (component.BeingWelded)
-                return;
-
-            component.BeingWelded = true;
-
-            var toolEvData = new ToolEventData(null);
+            args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent(), component.RefineFuel);
+        }
 
-            if (!_toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, toolEvData, component.RefineFuel))
-            {
-                // failed to veld - abort refine
-                component.BeingWelded = false;
+        private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args)
+        {
+            if (args.Cancelled)
                 return;
-            }
 
             // get last owner coordinates and delete it
             var resultPosition = Transform(uid).Coordinates;
@@ -50,7 +45,7 @@ namespace Content.Server.Construction
                 // TODO: If something has a stack... Just use a prototype with a single thing in the stack.
                 // This is not a good way to do it.
                 if (TryComp<StackComponent?>(droppedEnt, out var stack))
-                    _stackSystem.SetCount(droppedEnt,1, stack);
+                    _stackSystem.SetCount(droppedEnt, 1, stack);
             }
         }
     }
index 3891941caded667a108f756da81ed16dc7de1b8c..2b65803673bcb30168d5eb64dbd31cc3365c6b60 100644 (file)
@@ -18,7 +18,7 @@ namespace Content.Server.Cuffs
 
         private void OnHandcuffGetState(EntityUid uid, HandcuffComponent component, ref ComponentGetState args)
         {
-            args.State = new HandcuffComponentState(component.OverlayIconState, component.Cuffing);
+            args.State = new HandcuffComponentState(component.OverlayIconState);
         }
 
         private void OnCuffableGetState(EntityUid uid, CuffableComponent component, ref ComponentGetState args)
@@ -33,7 +33,6 @@ namespace Content.Server.Cuffs
                 TryComp(component.LastAddedCuffs, out cuffs);
             args.State = new CuffableComponentState(component.CuffedHandCount,
                 component.CanStillInteract,
-                component.Uncuffing,
                 cuffs?.CuffedRSI,
                 $"{cuffs?.OverlayIconState}-{component.CuffedHandCount}",
                 cuffs?.Color);
index 98b4d6c23026c4501ac1a7e114de24192ea1d690..37136435a46f2a71478f4f7ecf6ee31a102dbc0e 100644 (file)
@@ -3,7 +3,6 @@ using Content.Shared.Disease;
 using Content.Shared.Interaction;
 using Content.Shared.Inventory;
 using Content.Shared.Examine;
-using Content.Server.DoAfter;
 using Content.Server.Popups;
 using Content.Server.Hands.Components;
 using Content.Server.Nutrition.EntitySystems;
@@ -18,7 +17,7 @@ using Content.Shared.Tools.Components;
 using Content.Server.Station.Systems;
 using Content.Shared.DoAfter;
 using Content.Shared.IdentityManagement;
-using Robust.Server.GameObjects;
+using Content.Shared.Swab;
 
 namespace Content.Server.Disease
 {
@@ -27,7 +26,7 @@ namespace Content.Server.Disease
     /// </summary>
     public sealed class DiseaseDiagnosisSystem : EntitySystem
     {
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly InventorySystem _inventorySystem = default!;
@@ -47,7 +46,7 @@ namespace Content.Server.Disease
             // Private Events
             SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
             SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
-            SubscribeLocalEvent<DiseaseSwabComponent, DoAfterEvent>(OnSwabDoAfter);
+            SubscribeLocalEvent<DiseaseSwabComponent, DiseaseSwabDoAfterEvent>(OnSwabDoAfter);
         }
 
         private Queue<EntityUid> AddQueue = new();
@@ -116,15 +115,10 @@ namespace Content.Server.Disease
                 return;
             }
 
-            var isTarget = args.User != args.Target;
-
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, swab.SwabDelay, new DiseaseSwabDoAfterEvent(), uid, target: args.Target, used: uid)
             {
-                RaiseOnTarget = isTarget,
-                RaiseOnUser = !isTarget,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index db6f0dc6a532625e1038190bc58245341facffb0..93a1cab5a34f590de1d36a7dbcf1e714356b7c71 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Body.Systems;
 using Content.Server.Chat.Systems;
 using Content.Server.Disease.Components;
-using Content.Server.DoAfter;
 using Content.Server.Nutrition.EntitySystems;
 using Content.Server.Popups;
 using Content.Shared.Clothing.Components;
@@ -37,7 +36,7 @@ namespace Content.Server.Disease
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
         [Dependency] private readonly ISerializationManager _serializationManager = default!;
         [Dependency] private readonly IRobustRandom _random = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly EntityLookupSystem _lookup = default!;
         [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
@@ -58,7 +57,7 @@ namespace Content.Server.Disease
             // Handling stuff from other systems
             SubscribeLocalEvent<DiseaseCarrierComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
             // Private events stuff
-            SubscribeLocalEvent<DiseaseVaccineComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<DiseaseVaccineComponent, VaccineDoAfterEvent>(OnDoAfter);
         }
 
         private Queue<EntityUid> AddQueue = new();
@@ -276,20 +275,21 @@ namespace Content.Server.Disease
         /// </summary>
         private void OnAfterInteract(EntityUid uid, DiseaseVaccineComponent vaxx, AfterInteractEvent args)
         {
-            if (args.Target == null || !args.CanReach)
+            if (args.Target == null || !args.CanReach || args.Handled)
                 return;
 
+            args.Handled = true;
+
             if (vaxx.Used)
             {
                 _popupSystem.PopupEntity(Loc.GetString("vaxx-already-used"), args.User, args.User);
                 return;
             }
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, target: args.Target, used:uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, vaxx.InjectDelay, new VaccineDoAfterEvent(), uid, target: args.Target, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index 5a3c2ed6428622fece15443cbe8aecda13bf422a..e64f40a48831e06df8d089ce207ddd229f2e3074 100644 (file)
@@ -4,7 +4,6 @@ using Content.Server.Administration.Logs;
 using Content.Server.Atmos.EntitySystems;
 using Content.Server.Disposal.Tube.Components;
 using Content.Server.Disposal.Unit.Components;
-using Content.Server.DoAfter;
 using Content.Server.Hands.Components;
 using Content.Server.Popups;
 using Content.Server.Power.Components;
@@ -42,7 +41,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
         [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
         [Dependency] private readonly AppearanceSystem _appearance = default!;
         [Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly EntityLookupSystem _lookup = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
@@ -79,7 +78,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
             SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<Verb>>(AddClimbInsideVerb);
 
             // Units
-            SubscribeLocalEvent<DisposalUnitComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<DisposalUnitComponent, DisposalDoAfterEvent>(OnDoAfter);
 
             //UI
             SubscribeLocalEvent<DisposalUnitComponent, SharedDisposalUnitComponent.UiButtonPressedMessage>(OnUiButtonPressed);
@@ -489,19 +488,15 @@ namespace Content.Server.Disposal.Unit.EntitySystems
 
             // 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, target:toInsertId, used:unitId)
+            var doAfterArgs = new DoAfterArgs(userId.Value, delay, new DisposalDoAfterEvent(), unitId, target: toInsertId, used: unitId)
             {
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                NeedHand = false,
-                RaiseOnTarget = false,
-                RaiseOnUser = false,
-                RaiseOnUsed = true,
+                NeedHand = false
             };
 
-            _doAfterSystem.DoAfter(doAfterArgs);
+            _doAfterSystem.TryStartDoAfter(doAfterArgs);
             return true;
         }
 
index ad0d8d27d88d492572dec30dcfc8212712210679..b3d2711f7ba94e442ac09dbdce9b08e0d070b83f 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Shared.DoAfter;
-using Content.Shared.Mobs;
 using JetBrains.Annotations;
 
 namespace Content.Server.DoAfter;
index bb01665b8dbb62e2d9cfc37d0b5412d2b4c1346a..d6ecd5d65be0d7b2a05e46b4336bb26cb792b599 100644 (file)
@@ -20,6 +20,7 @@ using Content.Server.Power.EntitySystems;
 using Content.Shared.Tools;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Events;
+using Content.Shared.DoAfter;
 
 namespace Content.Server.Doors.Systems;
 
@@ -41,8 +42,7 @@ public sealed class DoorSystem : SharedDoorSystem
         // Mob prying doors
         SubscribeLocalEvent<DoorComponent, GetVerbsEvent<AlternativeVerb>>(OnDoorAltVerb);
 
-        SubscribeLocalEvent<DoorComponent, PryFinishedEvent>(OnPryFinished);
-        SubscribeLocalEvent<DoorComponent, PryCancelledEvent>(OnPryCancelled);
+        SubscribeLocalEvent<DoorComponent, DoorPryDoAfterEvent>(OnPryFinished);
         SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
         SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
         SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
@@ -174,9 +174,6 @@ public sealed class DoorSystem : SharedDoorSystem
     /// </summary>
     public bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door, bool force = false)
     {
-        if (door.BeingPried)
-            return false;
-
         if (door.State == DoorState.Welded)
             return false;
 
@@ -194,20 +191,14 @@ public sealed class DoorSystem : SharedDoorSystem
         var modEv = new DoorGetPryTimeModifierEvent(user);
         RaiseLocalEvent(target, modEv, false);
 
-        door.BeingPried = true;
-        var toolEvData = new ToolEventData(new PryFinishedEvent(), cancelledEv: new PryCancelledEvent(),targetEntity: target);
-        _toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, new[] { door.PryingQuality }, toolEvData);
+        _toolSystem.UseTool(tool, user, target, modEv.PryTimeModifier * door.PryTime, door.PryingQuality, new DoorPryDoAfterEvent());
         return true; // we might not actually succeeded, but a do-after has started
     }
 
-    private void OnPryCancelled(EntityUid uid, DoorComponent door, PryCancelledEvent args)
+    private void OnPryFinished(EntityUid uid, DoorComponent door, DoAfterEvent args)
     {
-        door.BeingPried = false;
-    }
-
-    private void OnPryFinished(EntityUid uid, DoorComponent door, PryFinishedEvent args)
-    {
-        door.BeingPried = false;
+        if (args.Cancelled)
+            return;
 
         if (door.State == DoorState.Closed)
             StartOpening(uid, door);
@@ -309,6 +300,3 @@ public sealed class DoorSystem : SharedDoorSystem
     }
 }
 
-public sealed class PryFinishedEvent : EntityEventArgs { }
-public sealed class PryCancelledEvent : EntityEventArgs { }
-
index f4d3809b85327bc1c5e665d4c6bf91982fd0d535..d969272ab9041d9bfa1814e22216028d70ebcb3f 100644 (file)
@@ -1,11 +1,9 @@
 using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
 using Content.Server.Popups;
 using Content.Shared.Actions;
 using Content.Shared.Chemistry.Components;
 using Robust.Shared.Containers;
 using Robust.Shared.Player;
-using System.Threading;
 using Content.Server.Chat.Systems;
 using Content.Server.GameTicking;
 using Content.Server.GameTicking.Rules;
@@ -33,7 +31,7 @@ namespace Content.Server.Dragon
         [Dependency] private readonly ITileDefinitionManager _tileDef = default!;
         [Dependency] private readonly ChatSystem _chat = default!;
         [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
         [Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
@@ -63,7 +61,7 @@ namespace Content.Server.Dragon
             SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
             SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
 
-            SubscribeLocalEvent<DragonComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<DragonComponent, DragonDevourDoAfterEvent>(OnDoAfter);
 
             SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
 
@@ -75,7 +73,7 @@ namespace Content.Server.Dragon
             SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
         }
 
-        private void OnDoAfter(EntityUid uid, DragonComponent component, DoAfterEvent args)
+        private void OnDoAfter(EntityUid uid, DragonComponent component, DragonDevourDoAfterEvent args)
         {
             if (args.Handled || args.Cancelled)
                 return;
@@ -95,8 +93,7 @@ namespace Content.Server.Dragon
             else if (args.Args.Target != null)
                 EntityManager.QueueDeleteEntity(args.Args.Target.Value);
 
-            if (component.SoundDevour != null)
-                _audioSystem.PlayPvs(component.SoundDevour, uid, component.SoundDevour.Params);
+            _audioSystem.PlayPvs(component.SoundDevour, uid);
         }
 
         public override void Update(float frameTime)
@@ -355,11 +352,10 @@ namespace Content.Server.Dragon
                     case MobState.Critical:
                     case MobState.Dead:
 
-                        _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.DevourTime, target:target)
+                        _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.DevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid)
                         {
                             BreakOnTargetMove = true,
                             BreakOnUserMove = true,
-                            BreakOnStun = true,
                         });
                         break;
                     default:
@@ -375,11 +371,10 @@ namespace Content.Server.Dragon
             if (component.SoundStructureDevour != null)
                 _audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params);
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.StructureDevourTime, target:target)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.StructureDevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
             });
         }
     }
index 802c86518ed9c65a03a99201a3b3bf7fab84c7f6..7bd620963ca3f056b8e5dd002206aecec1670390 100644 (file)
@@ -12,7 +12,5 @@ namespace Content.Server.Engineering.Components
 
         [DataField("doAfter")]
         public float DoAfterTime = 0;
-
-        public CancellationTokenSource TokenSource { get; } = new();
     }
 }
index 79d25f3520b5873cf4ae24a42a74fcbe40c990ff..91bf49b7ec9b0fc035ba2445ca75a45aeb8bd0a2 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.DoAfter;
 using Content.Server.Engineering.Components;
 using Content.Shared.DoAfter;
 using Content.Shared.Hands.EntitySystems;
@@ -41,18 +40,16 @@ namespace Content.Server.Engineering.EntitySystems
             if (string.IsNullOrEmpty(component.Prototype))
                 return;
 
-            if (component.DoAfterTime > 0 && TryGet<DoAfterSystem>(out var doAfterSystem))
+            if (component.DoAfterTime > 0 && TryGet<SharedDoAfterSystem>(out var doAfterSystem))
             {
-                var doAfterArgs = new DoAfterEventArgs(user, component.DoAfterTime, component.TokenSource.Token)
+                var doAfterArgs = new DoAfterArgs(user, component.DoAfterTime, new AwaitedDoAfterEvent(), null)
                 {
                     BreakOnUserMove = true,
-                    BreakOnStun = true,
                 };
                 var result = await doAfterSystem.WaitDoAfter(doAfterArgs);
 
                 if (result != DoAfterStatus.Finished)
                     return;
-                component.TokenSource.Cancel();
             }
 
             if (component.Deleted || Deleted(component.Owner))
index 988b72ae883ee081710bbe7dc83913ca40cb987e..89a74b255811ab41b3dfe94805377c05c3eb7075 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Coordinates.Helpers;
-using Content.Server.DoAfter;
 using Content.Server.Engineering.Components;
 using Content.Server.Stack;
 using Content.Shared.DoAfter;
@@ -15,7 +14,7 @@ namespace Content.Server.Engineering.EntitySystems
     public sealed class SpawnAfterInteractSystem : EntitySystem
     {
         [Dependency] private readonly IMapManager _mapManager = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly StackSystem _stackSystem = default!;
 
         public override void Initialize()
@@ -46,11 +45,9 @@ namespace Content.Server.Engineering.EntitySystems
 
             if (component.DoAfterTime > 0)
             {
-                var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime)
+                var doAfterArgs = new DoAfterArgs(args.User, component.DoAfterTime, new AwaitedDoAfterEvent(), null)
                 {
                     BreakOnUserMove = true,
-                    BreakOnStun = true,
-                    PostCheck = IsTileClear,
                 };
                 var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
 
@@ -58,7 +55,7 @@ namespace Content.Server.Engineering.EntitySystems
                     return;
             }
 
-            if (component.Deleted || Deleted(component.Owner))
+            if (component.Deleted || !IsTileClear())
                 return;
 
             if (EntityManager.TryGetComponent<StackComponent?>(component.Owner, out var stackComp)
index dbb45f1f9b5c701c4ac3c8e2c6efdb878684e940..f65871ef997b19b04a0e6693aa43dbb1b736ce75 100644 (file)
@@ -1,7 +1,6 @@
-using System.Threading;
-using Content.Server.DoAfter;
 using Content.Shared.Alert;
 using Content.Shared.DoAfter;
+using Content.Shared.Ensnaring;
 using Content.Shared.Ensnaring.Components;
 using Content.Shared.IdentityManagement;
 using Content.Shared.StepTrigger.Systems;
@@ -11,7 +10,7 @@ namespace Content.Server.Ensnaring;
 
 public sealed partial class EnsnareableSystem
 {
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly AlertsSystem _alerts = default!;
 
     public void InitializeEnsnaring()
@@ -74,40 +73,35 @@ public sealed partial class EnsnareableSystem
     /// <summary>
     /// Used where you want to try to free an entity with the <see cref="EnsnareableComponent"/>
     /// </summary>
-    /// <param name="target">The entity that will be free</param>
+    /// <param name="target">The entity that will be freed</param>
+    /// <param name="user">The entity that is freeing the target</param>
     /// <param name="ensnare">The entity used to ensnare</param>
     /// <param name="component">The ensnaring component</param>
-    public void TryFree(EntityUid target, EntityUid ensnare, EnsnaringComponent component, EntityUid? user = null)
+    public void TryFree(EntityUid target,  EntityUid user, EntityUid ensnare, EnsnaringComponent component)
     {
         //Don't do anything if they don't have the ensnareable component.
         if (!HasComp<EnsnareableComponent>(target))
             return;
 
-        var isOwner = !(user != null && target != user);
-        var freeTime = isOwner ? component.BreakoutTime : component.FreeTime;
-        bool breakOnMove;
+        var freeTime = user == target ? component.BreakoutTime : component.FreeTime;
+        var breakOnMove = user != target || !component.CanMoveBreakout;
 
-        if (isOwner)
-            breakOnMove = !component.CanMoveBreakout;
-        else
-            breakOnMove = true;
-
-        var doAfterEventArgs = new DoAfterEventArgs(target, freeTime, target: target, used:ensnare)
+        var doAfterEventArgs = new DoAfterArgs(user, freeTime, new EnsnareableDoAfterEvent(), target, target: target, used: ensnare)
         {
             BreakOnUserMove = breakOnMove,
             BreakOnTargetMove = breakOnMove,
             BreakOnDamage = false,
-            BreakOnStun = true,
-            NeedHand = true
+            NeedHand = true,
+            BlockDuplicate = true,
         };
 
-        _doAfter.DoAfter(doAfterEventArgs);
+        if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+            return;
 
-        if (isOwner)
+        if (user == 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", ensnare), ("user", Identity.Entity(target, EntityManager))), user.Value, user.Value);
+        else
+            _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-other", ("ensnare", ensnare), ("user", Identity.Entity(target, EntityManager))), user, user);
     }
 
     /// <summary>
index d883cdb4188c47297f1c08a2e98b7d493bc0180d..48e34537f8ade139fdb3791cfa550b810768395d 100644 (file)
@@ -20,7 +20,7 @@ public sealed partial class EnsnareableSystem : SharedEnsnareableSystem
         InitializeEnsnaring();
 
         SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnEnsnareableInit);
-        SubscribeLocalEvent<EnsnareableComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<EnsnareableComponent, EnsnareableDoAfterEvent>(OnDoAfter);
     }
 
     private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
index 4f7304ee6c4729349d2382aa5b5b3cdb4b487abc..9aab13ba6e704e03a355260dff057c409e8ecfb7 100644 (file)
@@ -1,7 +1,5 @@
-using System.Linq;
 using Content.Server.Chemistry.Components.SolutionManager;
 using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
 using Content.Server.Fluids.Components;
 using Content.Server.Popups;
 using Content.Shared.Chemistry.Components;
@@ -20,7 +18,7 @@ namespace Content.Server.Fluids.EntitySystems;
 [UsedImplicitly]
 public sealed class MoppingSystem : SharedMoppingSystem
 {
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly SpillableSystem _spillableSystem = default!;
     [Dependency] private readonly TagSystem _tagSystem = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
@@ -35,8 +33,8 @@ public sealed class MoppingSystem : SharedMoppingSystem
         base.Initialize();
         SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
         SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
+        SubscribeLocalEvent<AbsorbentComponent, AbsorbantDoAfterEvent>(OnDoAfter);
         SubscribeLocalEvent<AbsorbentComponent, SolutionChangedEvent>(OnAbsorbentSolutionChange);
-        SubscribeLocalEvent<AbsorbentComponent, DoAfterEvent<AbsorbantData>>(OnDoAfter);
     }
 
     private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args)
@@ -256,48 +254,34 @@ public sealed class MoppingSystem : SharedMoppingSystem
         if (!component.InteractingEntities.Add(target))
             return;
 
-        var aborbantData = new AbsorbantData(targetSolution, msg, sfx, transferAmount);
+        var ev = new AbsorbantDoAfterEvent(targetSolution, msg, sfx, transferAmount);
 
-        var doAfterArgs = new DoAfterEventArgs(user, delay, target: target, used:used)
+        var doAfterArgs = new DoAfterArgs(user, delay, ev, used, target: target, used: used)
         {
             BreakOnUserMove = true,
-            BreakOnStun = true,
             BreakOnDamage = true,
             MovementThreshold = 0.2f
         };
 
-        _doAfterSystem.DoAfter(doAfterArgs, aborbantData);
+        _doAfterSystem.TryStartDoAfter(doAfterArgs);
     }
 
-    private void OnDoAfter(EntityUid uid, AbsorbentComponent component, DoAfterEvent<AbsorbantData> args)
+    private void OnDoAfter(EntityUid uid, AbsorbentComponent component, AbsorbantDoAfterEvent args)
     {
-        if (args.Args.Target == null)
+        if (args.Target == null)
             return;
 
-        if (args.Cancelled)
-        {
-            //Remove the interacting entities or else it breaks the mop
-            component.InteractingEntities.Remove(args.Args.Target.Value);
-            return;
-        }
+        component.InteractingEntities.Remove(args.Target.Value);
 
-        if (args.Handled)
+        if (args.Cancelled || args.Handled)
             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);
+        _audio.PlayPvs(args.Sound, uid);
+        _popups.PopupEntity(Loc.GetString(args.Message, ("target", args.Target.Value), ("used", uid)), uid);
+        _solutionSystem.TryTransferSolution(args.Target.Value, uid, args.TargetSolution,
+            AbsorbentComponent.SolutionName, args.TransferAmount);
+        component.InteractingEntities.Remove(args.Target.Value);
 
         args.Handled = true;
     }
-
-    private record struct AbsorbantData(string TargetSolution, string Message, SoundSpecifier Sound, FixedPoint2 TransferAmount)
-    {
-        public readonly string TargetSolution = TargetSolution;
-        public readonly string Message = Message;
-        public readonly SoundSpecifier Sound = Sound;
-        public readonly FixedPoint2 TransferAmount = TransferAmount;
-    }
 }
index 32e6dcbaa45597aea721abf99ea7dec321cb0e7d..f98656e291f1d9d9f25aff5fd5742deb51a4eda5 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
 using Content.Server.Fluids.Components;
 using Content.Server.Nutrition.Components;
 using Content.Shared.Chemistry.Components;
@@ -17,6 +16,7 @@ using Robust.Shared.Prototypes;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Content.Shared.DoAfter;
+using Content.Shared.Spillable;
 
 namespace Content.Server.Fluids.EntitySystems;
 
@@ -29,7 +29,7 @@ public sealed class SpillableSystem : EntitySystem
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
     [Dependency] private readonly IAdminLogManager _adminLogger= default!;
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
 
     public override void Initialize()
     {
@@ -38,7 +38,7 @@ public sealed class SpillableSystem : EntitySystem
         SubscribeLocalEvent<SpillableComponent, GetVerbsEvent<Verb>>(AddSpillVerb);
         SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
         SubscribeLocalEvent<SpillableComponent, SolutionSpikeOverflowEvent>(OnSpikeOverflow);
-        SubscribeLocalEvent<SpillableComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<SpillableComponent, SpillDoAfterEvent>(OnDoAfter);
     }
 
     private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args)
@@ -128,29 +128,17 @@ public sealed class SpillableSystem : EntitySystem
         Verb verb = new();
         verb.Text = Loc.GetString("spill-target-verb-get-data-text");
         // TODO VERB ICONS spill icon? pouring out a glass/beaker?
-        if (component.SpillDelay == null)
-        {
-            verb.Act = () =>
-            {
-                var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target,
-                    solution, solution.Volume);
-                SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear");
-            };
-        }
-        else
+
+        verb.Act = () =>
         {
-            verb.Act = () =>
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, component.SpillDelay ?? 0, new SpillDoAfterEvent(), uid, target: uid)
             {
-                _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.SpillDelay.Value, target:uid)
-                {
-                    BreakOnTargetMove = true,
-                    BreakOnUserMove = true,
-                    BreakOnDamage = true,
-                    BreakOnStun = true,
-                    NeedHand = true
-                });
-            };
-        }
+                BreakOnTargetMove = true,
+                BreakOnUserMove = true,
+                BreakOnDamage = true,
+                NeedHand = true,
+            });
+        };
         verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
         verb.DoContactInteraction = true;
         args.Verbs.Add(verb);
index bf419e2f64b261e11d4c0d453a7cd80f78cebcbc..f7a79ef974189ae7f4dffe9e71f0aaa97138cd5a 100644 (file)
@@ -1,11 +1,10 @@
 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.Forensics;
 using Content.Shared.IdentityManagement;
-using Robust.Shared.Serialization;
 
 namespace Content.Server.Forensics
 {
@@ -14,7 +13,7 @@ namespace Content.Server.Forensics
     /// </summary>
     public sealed class ForensicPadSystem : EntitySystem
     {
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly InventorySystem _inventory = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
 
@@ -23,7 +22,7 @@ namespace Content.Server.Forensics
             base.Initialize();
             SubscribeLocalEvent<ForensicPadComponent, ExaminedEvent>(OnExamined);
             SubscribeLocalEvent<ForensicPadComponent, AfterInteractEvent>(OnAfterInteract);
-            SubscribeLocalEvent<ForensicPadComponent, DoAfterEvent<ForensicPadData>>(OnDoAfter);
+            SubscribeLocalEvent<ForensicPadComponent, ForensicPadDoAfterEvent>(OnDoAfter);
         }
 
         private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args)
@@ -79,25 +78,21 @@ namespace Content.Server.Forensics
 
         private void StartScan(EntityUid used, EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
         {
-            var padData = new ForensicPadData(sample);
+            var ev = new ForensicPadDoAfterEvent(sample);
 
-            var doAfterEventArgs = new DoAfterEventArgs(user, pad.ScanDelay, target: target, used: used)
+            var doAfterEventArgs = new DoAfterArgs(user, pad.ScanDelay, ev, used, target: target, used: used)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
-                NeedHand = true,
-                RaiseOnUser = false
+                NeedHand = true
             };
 
-            _doAfterSystem.DoAfter(doAfterEventArgs, padData);
+            _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
         }
 
-        private void OnDoAfter(EntityUid uid, ForensicPadComponent component, DoAfterEvent<ForensicPadData> args)
+        private void OnDoAfter(EntityUid uid, ForensicPadComponent padComponent, ForensicPadDoAfterEvent args)
         {
-            if (args.Handled
-                || args.Cancelled
-                || !EntityManager.TryGetComponent(args.Args.Used, out ForensicPadComponent? padComponent))
+            if (args.Handled || args.Cancelled)
             {
                 return;
             }
@@ -110,20 +105,10 @@ namespace Content.Server.Forensics
                     MetaData(uid).EntityName = Loc.GetString("forensic-pad-gloves-name", ("entity", args.Args.Target));
             }
 
-            padComponent.Sample = args.AdditionalData.Sample;
+            padComponent.Sample = args.Sample;
             padComponent.Used = true;
 
             args.Handled = true;
         }
-
-        private sealed class ForensicPadData
-        {
-            public string Sample;
-
-            public ForensicPadData(string sample)
-            {
-                Sample = sample;
-            }
-        }
     }
 }
index 83dfc87223af0156c4f5b26c6d858829a392d291..4f629590c8cab20fc2d2864f80d0f807b196b2ac 100644 (file)
@@ -3,7 +3,6 @@ using System.Text; // todo: remove this stinky LINQy
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
 using Robust.Shared.Timing;
-using Content.Server.DoAfter;
 using Content.Server.Paper;
 using Content.Server.Popups;
 using Content.Server.UserInterface;
@@ -18,7 +17,7 @@ namespace Content.Server.Forensics
     public sealed class ForensicScannerSystem : EntitySystem
     {
         [Dependency] private readonly IGameTiming _gameTiming = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly PaperSystem _paperSystem = default!;
@@ -39,7 +38,7 @@ namespace Content.Server.Forensics
             SubscribeLocalEvent<ForensicScannerComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
             SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerPrintMessage>(OnPrint);
             SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerClearMessage>(OnClear);
-            SubscribeLocalEvent<ForensicScannerComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerDoAfterEvent>(OnDoAfter);
         }
 
         private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component)
@@ -91,11 +90,10 @@ namespace Content.Server.Forensics
         /// </remarks>
         private void StartScan(EntityUid uid, ForensicScannerComponent component, EntityUid user, EntityUid target)
         {
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.ScanDelay, target: target, used: uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, component.ScanDelay, new ForensicScannerDoAfterEvent(), uid, target: target, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index 9bf5bb20187c11968ddba6d4ddb9eed5e3b5b9bd..1d6f8069048289edd3602536f189f96e568d386d 100644 (file)
@@ -32,6 +32,7 @@ namespace Content.Server.Gatherable.Components
         public int MaxGatheringEntities = 1;
 
         [ViewVariables]
+        [DataField("gatheringEntities")]
         public readonly List<EntityUid> GatheringEntities = new();
     }
 }
index e82a5130d34660c07ce4ee818798d9643887643d..0aa8c256f857802e250d7cc9819ab644ed200587 100644 (file)
@@ -1,11 +1,8 @@
-using System.Threading;
 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.Gatherable;
 using Content.Shared.Interaction;
 using Content.Shared.Tag;
 using Robust.Shared.Prototypes;
@@ -17,9 +14,8 @@ public sealed class GatherableSystem : EntitySystem
 {
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
     [Dependency] private readonly DestructibleSystem _destructible = default!;
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly TagSystem _tagSystem = default!;
 
@@ -28,7 +24,7 @@ public sealed class GatherableSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<GatherableComponent, InteractUsingEvent>(OnInteractUsing);
-        SubscribeLocalEvent<GatherableComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<GatherableComponent, GatherableDoAfterEvent>(OnDoAfter);
     }
 
     private void OnInteractUsing(EntityUid uid, GatherableComponent component, InteractUsingEvent args)
@@ -44,33 +40,29 @@ public sealed class GatherableSystem : EntitySystem
         var damageTime = (damageRequired / tool.Damage.Total).Float();
         damageTime = Math.Max(1f, damageTime);
 
-        var doAfter = new DoAfterEventArgs(args.User, damageTime, target: uid, used: args.Used)
+        var doAfter = new DoAfterArgs(args.User, damageTime, new GatherableDoAfterEvent(), uid, target: uid, used: args.Used)
         {
             BreakOnDamage = true,
-            BreakOnStun = true,
             BreakOnTargetMove = true,
             BreakOnUserMove = true,
             MovementThreshold = 0.25f,
         };
 
-        _doAfterSystem.DoAfter(doAfter);
+        _doAfterSystem.TryStartDoAfter(doAfter);
     }
 
-    private void OnDoAfter(EntityUid uid, GatherableComponent component, DoAfterEvent args)
+    private void OnDoAfter(EntityUid uid, GatherableComponent component, GatherableDoAfterEvent args)
     {
         if(!TryComp<GatheringToolComponent>(args.Args.Used, out var tool) || args.Args.Target == null)
             return;
 
+        tool.GatheringEntities.Remove(args.Args.Target.Value);
         if (args.Handled || args.Cancelled)
-        {
-            tool.GatheringEntities.Remove(args.Args.Target.Value);
             return;
-        }
 
         // Complete the gathering process
         _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)
index e120cc3b256354dfc03385f5d814fbc186891a4c..7729348453e7391a469b4e54fa83b0eb6565b77a 100644 (file)
@@ -28,7 +28,5 @@ namespace Content.Server.Guardian
         /// </summary>
         [DataField("delay")]
         public float InjectionDelay = 5f;
-
-        public bool Injecting = false;
     }
 }
index b1ca676e04dce985e94e1b1a61463e387fd770d2..ed6811449b0f0eca42b73897675d685ea893c484 100644 (file)
@@ -1,10 +1,10 @@
-using Content.Server.DoAfter;
 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.Guardian;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
@@ -22,7 +22,7 @@ namespace Content.Server.Guardian
     /// </summary>
     public sealed class GuardianSystem : EntitySystem
     {
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly DamageableSystem _damageSystem = default!;
         [Dependency] private readonly SharedActionsSystem _actionSystem = default!;
@@ -35,7 +35,7 @@ namespace Content.Server.Guardian
             SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
             SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
             SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
-            SubscribeLocalEvent<GuardianCreatorComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<GuardianCreatorComponent, GuardianCreatorDoAfterEvent>(OnDoAfter);
 
             SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
             SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
@@ -161,12 +161,7 @@ namespace Content.Server.Guardian
                 return;
             }
 
-            if (component.Injecting)
-                return;
-
-            component.Injecting = true;
-
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target, used: injector)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, component.InjectionDelay, new GuardianCreatorDoAfterEvent(), injector, target: target, used: injector)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true
@@ -179,10 +174,7 @@ namespace Content.Server.Guardian
                 return;
 
             if (args.Cancelled || component.Deleted || component.Used || !_handsSystem.IsHolding(args.Args.User, uid, out _) || HasComp<GuardianHostComponent>(args.Args.Target))
-            {
-                component.Injecting = false;
                 return;
-            }
 
             var hostXform = Transform(args.Args.Target.Value);
             var host = EnsureComp<GuardianHostComponent>(args.Args.Target.Value);
index 451f4d074ec134930a0ed56431bd4b3ad28615b2..f34d8eeb06f8d810a23efa19b59af40a12696c4b 100644 (file)
@@ -1,9 +1,6 @@
-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;
 using Content.Shared.Implants.Components;
@@ -18,7 +15,7 @@ namespace Content.Server.Implants;
 public sealed partial class ImplanterSystem : SharedImplanterSystem
 {
     [Dependency] private readonly PopupSystem _popup = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly SharedContainerSystem _container = default!;
 
     public override void Initialize()
@@ -26,12 +23,11 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
         base.Initialize();
         InitializeImplanted();
 
-        SubscribeLocalEvent<ImplanterComponent, HandDeselectedEvent>(OnHandDeselect);
         SubscribeLocalEvent<ImplanterComponent, AfterInteractEvent>(OnImplanterAfterInteract);
         SubscribeLocalEvent<ImplanterComponent, ComponentGetState>(OnImplanterGetState);
 
-        SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<ImplantEvent>>(OnImplant);
-        SubscribeLocalEvent<ImplanterComponent, DoAfterEvent<DrawEvent>>(OnDraw);
+        SubscribeLocalEvent<ImplanterComponent, ImplantEvent>(OnImplant);
+        SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw);
     }
 
     private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
@@ -62,12 +58,6 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
         args.Handled = true;
     }
 
-    private void OnHandDeselect(EntityUid uid, ImplanterComponent component, HandDeselectedEvent args)
-    {
-        component.CancelToken?.Cancel();
-        component.CancelToken = null;
-    }
-
     /// <summary>
     /// Attempt to implant someone else.
     /// </summary>
@@ -77,27 +67,21 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
     /// <param name="implanter">The implanter being used</param>
     public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
     {
-        if (component.CancelToken != null)
+        var args = new DoAfterArgs(user, component.ImplantTime, new ImplantEvent(), implanter, target: target, used: implanter)
+        {
+            BreakOnUserMove = true,
+            BreakOnTargetMove = true,
+            BreakOnDamage = true,
+            NeedHand = true,
+        };
+
+        if (!_doAfter.TryStartDoAfter(args))
             return;
 
         _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
 
         var userName = Identity.Entity(user, EntityManager);
         _popup.PopupEntity(Loc.GetString("implanter-component-implanting-target", ("user", userName)), user, target, PopupType.LargeCaution);
-
-        component.CancelToken?.Cancel();
-        component.CancelToken = new CancellationTokenSource();
-
-        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,
-            NeedHand = true
-        }, implantEvent);
     }
 
     /// <summary>
@@ -110,21 +94,17 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
     //TODO: Remove when surgery is in
     public void TryDraw(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter)
     {
-        _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
-
-        component.CancelToken?.Cancel();
-        component.CancelToken = new CancellationTokenSource();
-
-        var drawEvent = new DrawEvent();
-
-        _doAfter.DoAfter(new DoAfterEventArgs(user, component.DrawTime, target:target,used:implanter)
+        var args = new DoAfterArgs(user, component.DrawTime, new DrawEvent(), implanter, target: target, used: implanter)
         {
             BreakOnUserMove = true,
             BreakOnTargetMove = true,
             BreakOnDamage = true,
-            BreakOnStun = true,
-            NeedHand = true
-        }, drawEvent);
+            NeedHand = true,
+        };
+
+        if (_doAfter.TryStartDoAfter(args))
+            _popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user);
+
     }
 
     private void OnImplanterGetState(EntityUid uid, ImplanterComponent component, ref ComponentGetState args)
@@ -132,47 +112,23 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
         args.State = new ImplanterComponentState(component.CurrentMode, component.ImplantOnly);
     }
 
-    private void OnImplant(EntityUid uid, ImplanterComponent component, DoAfterEvent<ImplantEvent> args)
+    private void OnImplant(EntityUid uid, ImplanterComponent component, ImplantEvent args)
     {
-        if (args.Cancelled)
-        {
-            component.CancelToken = null;
+        if (args.Cancelled || args.Handled || args.Target == null || args.Used == null)
             return;
-        }
 
-        if (args.Handled || args.Args.Target == null || args.Args.Used == null)
-            return;
-
-        Implant(args.Args.Used.Value, args.Args.Target.Value, component);
+        Implant(args.Used.Value, args.Target.Value, component);
 
         args.Handled = true;
-        component.CancelToken = null;
     }
 
-    private void OnDraw(EntityUid uid, ImplanterComponent component, DoAfterEvent<DrawEvent> args)
+    private void OnDraw(EntityUid uid, ImplanterComponent component, DrawEvent args)
     {
-        if (args.Cancelled)
-        {
-            component.CancelToken = null;
-            return;
-        }
-
-        if (args.Handled || args.Args.Used == null || args.Args.Target == null)
+        if (args.Cancelled || args.Handled || args.Used == null || args.Target == null)
             return;
 
-        Draw(args.Args.Used.Value, args.Args.User, args.Args.Target.Value, component);
+        Draw(args.Used.Value, args.User, args.Target.Value, component);
 
         args.Handled = true;
-        component.CancelToken = null;
-    }
-
-    private sealed class ImplantEvent : EntityEventArgs
-    {
-
-    }
-
-    private sealed class DrawEvent : EntityEventArgs
-    {
-
     }
 }
index 3b608ceb8739da72e12344b1df45f598caacc388..f9d41e82c03a708bccb2c03698af03d3a58611f7 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
 using Content.Server.Kitchen.Components;
 using Content.Server.Popups;
 using Content.Shared.Database;
@@ -25,7 +24,7 @@ namespace Content.Server.Kitchen.EntitySystems
     public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
     {
         [Dependency] private readonly PopupSystem _popupSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfter = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
         [Dependency] private readonly IAdminLogManager _logger = default!;
         [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
         [Dependency] private readonly IRobustRandom _random = default!;
@@ -43,7 +42,7 @@ namespace Content.Server.Kitchen.EntitySystems
             SubscribeLocalEvent<KitchenSpikeComponent, DragDropTargetEvent>(OnDragDrop);
 
             //DoAfter
-            SubscribeLocalEvent<KitchenSpikeComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<KitchenSpikeComponent, SpikeDoAfterEvent>(OnDoAfter);
 
             SubscribeLocalEvent<KitchenSpikeComponent, SuicideEvent>(OnSuicide);
 
@@ -251,16 +250,15 @@ namespace Content.Server.Kitchen.EntitySystems
             butcherable.BeingButchered = true;
             component.InUse = true;
 
-            var doAfterArgs = new DoAfterEventArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, target:victimUid, used:uid)
+            var doAfterArgs = new DoAfterArgs(userUid, component.SpikeDelay + butcherable.ButcherDelay, new SpikeDoAfterEvent(), uid, target: victimUid, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 NeedHand = true
             };
 
-            _doAfter.DoAfter(doAfterArgs);
+            _doAfter.TryStartDoAfter(doAfterArgs);
 
             return true;
         }
index 4a9e259e2973a4acfe0d6c2f55276ad5e55a4d15..5a798132b9b2363396c61c9d8c408fda200e9399 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
 using Content.Server.Kitchen.Components;
 using Content.Shared.Body.Components;
 using Content.Shared.Interaction;
@@ -9,6 +8,7 @@ using Content.Shared.Storage;
 using Content.Shared.Verbs;
 using Content.Shared.Destructible;
 using Content.Shared.DoAfter;
+using Content.Shared.Kitchen;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
 using Robust.Server.Containers;
@@ -21,7 +21,7 @@ public sealed class SharpSystem : EntitySystem
 {
     [Dependency] private readonly BodySystem _bodySystem = default!;
     [Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!;
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
     [Dependency] private readonly ContainerSystem _containerSystem = default!;
     [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
@@ -32,7 +32,7 @@ public sealed class SharpSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<SharpComponent, AfterInteractEvent>(OnAfterInteract);
-        SubscribeLocalEvent<SharpComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<SharpComponent, SharpDoAfterEvent>(OnDoAfter);
 
         SubscribeLocalEvent<ButcherableComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
     }
@@ -63,16 +63,15 @@ public sealed class SharpSystem : EntitySystem
             return;
 
         var doAfter =
-            new DoAfterEventArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, target: target, used: knife)
+            new DoAfterArgs(user, sharp.ButcherDelayModifier * butcher.ButcherDelay, new SharpDoAfterEvent(), knife, target: target, used: knife)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 NeedHand = true
             };
 
-        _doAfterSystem.DoAfter(doAfter);
+        _doAfterSystem.TryStartDoAfter(doAfter);
     }
 
     private void OnDoAfter(EntityUid uid, SharpComponent component, DoAfterEvent args)
index 545ad4d4f315309fb1826e7439c1113f2d953dbc..9cd0c06dc4ab2ad783f806a98fa31ab0ebe1b57d 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Administration.Logs;
 using Content.Server.DeviceNetwork;
 using Content.Server.DeviceNetwork.Systems;
-using Content.Server.DoAfter;
 using Content.Server.Ghost;
 using Content.Server.Light.Components;
 using Content.Server.MachineLinking.Events;
@@ -40,7 +39,7 @@ namespace Content.Server.Light.EntitySystems
         [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
         [Dependency] private readonly SignalLinkerSystem _signalSystem = default!;
         [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
 
@@ -63,8 +62,7 @@ namespace Content.Server.Light.EntitySystems
 
             SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
 
-            SubscribeLocalEvent<PoweredLightComponent, DoAfterEvent>(OnDoAfter);
-
+            SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
             SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
         }
 
@@ -140,11 +138,10 @@ namespace Content.Server.Light.EntitySystems
             }
 
             // removing a working bulb, so require a delay
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, light.EjectBulbDelay, target:uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid)
             {
                 BreakOnUserMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true
             });
 
             args.Handled = true;
@@ -428,7 +425,7 @@ namespace Content.Server.Light.EntitySystems
         private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
         {
             args.Affected = true;
-            TryDestroyBulb(uid, component);  
+            TryDestroyBulb(uid, component);
         }
     }
 }
index 5cdb549b7a81d589b9a0fcaa9c4a027067651645..5374d9ab748603d980c58b570bc1a7979cdfa323 100644 (file)
@@ -1,8 +1,6 @@
-using System.Threading;
 using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
 using Content.Server.Coordinates.Helpers;
-using Content.Server.DoAfter;
 using Content.Server.Doors.Systems;
 using Content.Server.Magic.Events;
 using Content.Server.Weapons.Ranged.Systems;
@@ -13,6 +11,7 @@ using Content.Shared.DoAfter;
 using Content.Shared.Doors.Components;
 using Content.Shared.Doors.Systems;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Magic;
 using Content.Shared.Maps;
 using Content.Shared.Physics;
 using Content.Shared.Spawners.Components;
@@ -42,7 +41,7 @@ public sealed class MagicSystem : EntitySystem
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
     [Dependency] private readonly SharedDoorSystem _doorSystem = default!;
     [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly GunSystem _gunSystem = default!;
     [Dependency] private readonly PhysicsSystem _physics = default!;
     [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
@@ -54,7 +53,7 @@ public sealed class MagicSystem : EntitySystem
 
         SubscribeLocalEvent<SpellbookComponent, ComponentInit>(OnInit);
         SubscribeLocalEvent<SpellbookComponent, UseInHandEvent>(OnUse);
-        SubscribeLocalEvent<SpellbookComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<SpellbookComponent, SpellbookDoAfterEvent>(OnDoAfter);
 
         SubscribeLocalEvent<InstantSpawnSpellEvent>(OnInstantSpawn);
         SubscribeLocalEvent<TeleportSpellEvent>(OnTeleportSpell);
@@ -111,16 +110,15 @@ public sealed class MagicSystem : EntitySystem
 
     private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args)
     {
-        var doAfterEventArgs = new DoAfterEventArgs(args.User, component.LearnTime, target:uid)
+        var doAfterEventArgs = new DoAfterArgs(args.User, component.LearnTime, new SpellbookDoAfterEvent(), uid, target: uid)
         {
             BreakOnTargetMove = true,
             BreakOnUserMove = true,
             BreakOnDamage = true,
-            BreakOnStun = true,
             NeedHand = true //What, are you going to read with your eyes only??
         };
 
-        _doAfter.DoAfter(doAfterEventArgs);
+        _doAfter.TryStartDoAfter(doAfterEventArgs);
     }
 
     #region Spells
index baf4eafd212b3798cd2575d6551a483387e29fbd..310c5a50c8d2092c4e6099b961dbf52cc47a50d6 100644 (file)
@@ -1,11 +1,9 @@
 using System.Linq;
-using Content.Server.DoAfter;
 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;
@@ -26,7 +24,7 @@ public sealed class MechGrabberSystem : EntitySystem
 {
     [Dependency] private readonly SharedContainerSystem _container = default!;
     [Dependency] private readonly MechSystem _mech = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly InteractionSystem _interaction = default!;
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly TransformSystem _transform = default!;
@@ -41,7 +39,7 @@ public sealed class MechGrabberSystem : EntitySystem
         SubscribeLocalEvent<MechGrabberComponent, AttemptRemoveMechEquipmentEvent>(OnAttemptRemove);
 
         SubscribeLocalEvent<MechGrabberComponent, InteractNoHandEvent>(OnInteract);
-        SubscribeLocalEvent<MechGrabberComponent, DoAfterEvent>(OnMechGrab);
+        SubscribeLocalEvent<MechGrabberComponent, GrabberDoAfterEvent>(OnMechGrab);
     }
 
     private void OnGrabberMessage(EntityUid uid, MechGrabberComponent component, MechEquipmentUiMessageRelayEvent args)
@@ -150,7 +148,7 @@ public sealed class MechGrabberSystem : EntitySystem
 
         args.Handled = true;
         component.AudioStream = _audio.PlayPvs(component.GrabSound, uid);
-        _doAfter.DoAfter(new DoAfterEventArgs(args.User, component.GrabDelay, target:target, used:uid)
+        _doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.GrabDelay, new GrabberDoAfterEvent(), uid, target: target, used: uid)
         {
             BreakOnTargetMove = true,
             BreakOnUserMove = true
index 8a9032a21dba68ebd6dbc2bc936ca9b28b663c9b..9ffa1030e114fceb97d864c1c98e07e650cdfb80 100644 (file)
@@ -1,5 +1,4 @@
-using Content.Server.DoAfter;
-using Content.Server.Mech.Components;
+using Content.Server.Mech.Components;
 using Content.Server.Popups;
 using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
@@ -13,14 +12,14 @@ namespace Content.Server.Mech.Systems;
 public sealed class MechEquipmentSystem : EntitySystem
 {
     [Dependency] private readonly MechSystem _mech = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly PopupSystem _popup = default!;
 
     /// <inheritdoc/>
     public override void Initialize()
     {
         SubscribeLocalEvent<MechEquipmentComponent, AfterInteractEvent>(OnUsed);
-        SubscribeLocalEvent<MechEquipmentComponent, DoAfterEvent<InsertEquipmentEvent>>(OnInsertEquipment);
+        SubscribeLocalEvent<MechEquipmentComponent, InsertEquipmentEvent>(OnInsertEquipment);
     }
 
     private void OnUsed(EntityUid uid, MechEquipmentComponent component, AfterInteractEvent args)
@@ -46,18 +45,16 @@ public sealed class MechEquipmentSystem : EntitySystem
 
         _popup.PopupEntity(Loc.GetString("mech-equipment-begin-install", ("item", uid)), mech);
 
-        var insertEquipment = new InsertEquipmentEvent();
-        var doAfterEventArgs = new DoAfterEventArgs(args.User, component.InstallDuration, target: mech, used: uid)
+        var doAfterEventArgs = new DoAfterArgs(args.User, component.InstallDuration, new InsertEquipmentEvent(), uid, target: mech, used: uid)
         {
-            BreakOnStun = true,
             BreakOnTargetMove = true,
             BreakOnUserMove = true
         };
 
-        _doAfter.DoAfter(doAfterEventArgs, insertEquipment);
+        _doAfter.TryStartDoAfter(doAfterEventArgs);
     }
 
-    private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, DoAfterEvent<InsertEquipmentEvent> args)
+    private void OnInsertEquipment(EntityUid uid, MechEquipmentComponent component, InsertEquipmentEvent args)
     {
         if (args.Handled || args.Cancelled || args.Args.Target == null)
             return;
@@ -67,9 +64,4 @@ public sealed class MechEquipmentSystem : EntitySystem
 
         args.Handled = true;
     }
-
-    private sealed class InsertEquipmentEvent : EntityEventArgs
-    {
-
-    }
 }
index cf41335f35968686c2e159b1b6c6111f14c146de..d264eb59b43350d2d8eed2e5ec1e2316f897f580 100644 (file)
@@ -1,6 +1,5 @@
 using System.Linq;
 using Content.Server.Atmos.EntitySystems;
-using Content.Server.DoAfter;
 using Content.Server.Mech.Components;
 using Content.Server.Power.Components;
 using Content.Shared.ActionBlocker;
@@ -27,7 +26,7 @@ public sealed class MechSystem : SharedMechSystem
     [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
     [Dependency] private readonly ContainerSystem _container = default!;
     [Dependency] private readonly DamageableSystem _damageable = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly IMapManager _map = default!;
     [Dependency] private readonly UserInterfaceSystem _ui = default!;
     [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
@@ -45,9 +44,9 @@ public sealed class MechSystem : SharedMechSystem
         SubscribeLocalEvent<MechComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<MechComponent, GetVerbsEvent<AlternativeVerb>>(OnAlternativeVerb);
         SubscribeLocalEvent<MechComponent, MechOpenUiEvent>(OnOpenUi);
-        SubscribeLocalEvent<MechComponent, DoAfterEvent<RemoveBatteryEvent>>(OnRemoveBattery);
-        SubscribeLocalEvent<MechComponent, DoAfterEvent<MechEntryEvent>>(OnMechEntry);
-        SubscribeLocalEvent<MechComponent, DoAfterEvent<MechExitEvent>>(OnMechExit);
+        SubscribeLocalEvent<MechComponent, RemoveBatteryEvent>(OnRemoveBattery);
+        SubscribeLocalEvent<MechComponent, MechEntryEvent>(OnMechEntry);
+        SubscribeLocalEvent<MechComponent, MechExitEvent>(OnMechExit);
 
         SubscribeLocalEvent<MechComponent, DamageChangedEvent>(OnDamageChanged);
         SubscribeLocalEvent<MechComponent, MechEquipmentRemoveMessage>(OnRemoveEquipmentMessage);
@@ -84,22 +83,17 @@ public sealed class MechSystem : SharedMechSystem
 
         if (TryComp<ToolComponent>(args.Used, out var tool) && tool.Qualities.Contains("Prying") && component.BatterySlot.ContainedEntity != null)
         {
-            var removeBattery = new RemoveBatteryEvent();
-
-            var doAfterEventArgs = new DoAfterEventArgs(args.User, component.BatteryRemovalDelay, target: uid, used: args.Target)
+            var doAfterEventArgs = new DoAfterArgs(args.User, component.BatteryRemovalDelay, new RemoveBatteryEvent(), uid, target: uid, used: args.Target)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                RaiseOnTarget = true,
-                RaiseOnUsed = false,
-                RaiseOnUser = false,
             };
 
-            _doAfter.DoAfter(doAfterEventArgs, removeBattery);
+            _doAfter.TryStartDoAfter(doAfterEventArgs);
         }
     }
 
-    private void OnRemoveBattery(EntityUid uid, MechComponent component, DoAfterEvent<RemoveBatteryEvent> args)
+    private void OnRemoveBattery(EntityUid uid, MechComponent component, RemoveBatteryEvent args)
     {
         if (args.Cancelled || args.Handled)
             return;
@@ -166,17 +160,12 @@ public sealed class MechSystem : SharedMechSystem
                 Text = Loc.GetString("mech-verb-enter"),
                 Act = () =>
                 {
-                    var mechEntryEvent = new MechEntryEvent();
-                    var doAfterEventArgs = new DoAfterEventArgs(args.User, component.EntryDelay, target: uid)
+                    var doAfterEventArgs = new DoAfterArgs(args.User, component.EntryDelay, new MechEntryEvent(), uid, target: uid)
                     {
                         BreakOnUserMove = true,
-                        BreakOnStun = true,
-                        RaiseOnTarget = true,
-                        RaiseOnUsed = false,
-                        RaiseOnUser = false,
                     };
 
-                    _doAfter.DoAfter(doAfterEventArgs, mechEntryEvent);
+                    _doAfter.TryStartDoAfter(doAfterEventArgs);
                 }
             };
             var openUiVerb = new AlternativeVerb //can't hijack someone else's mech
@@ -201,25 +190,20 @@ public sealed class MechSystem : SharedMechSystem
                         return;
                     }
 
-                    var mechExitEvent = new MechExitEvent();
-                    var doAfterEventArgs = new DoAfterEventArgs(args.User, component.ExitDelay, target: uid)
+                    var doAfterEventArgs = new DoAfterArgs(args.User, component.ExitDelay, new MechExitEvent(), uid, target: uid)
                     {
                         BreakOnUserMove = true,
                         BreakOnTargetMove = true,
-                        BreakOnStun = true,
-                        RaiseOnTarget = true,
-                        RaiseOnUsed = false,
-                        RaiseOnUser = false,
                     };
 
-                    _doAfter.DoAfter(doAfterEventArgs, mechExitEvent);
+                    _doAfter.TryStartDoAfter(doAfterEventArgs);
                 }
             };
             args.Verbs.Add(ejectVerb);
         }
     }
 
-    private void OnMechEntry(EntityUid uid, MechComponent component, DoAfterEvent<MechEntryEvent> args)
+    private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent args)
     {
         if (args.Cancelled || args.Handled)
             return;
@@ -230,7 +214,7 @@ public sealed class MechSystem : SharedMechSystem
         args.Handled = true;
     }
 
-    private void OnMechExit(EntityUid uid, MechComponent component, DoAfterEvent<MechExitEvent> args)
+    private void OnMechExit(EntityUid uid, MechComponent component, MechExitEvent args)
     {
         if (args.Cancelled || args.Handled)
             return;
@@ -454,27 +438,4 @@ public sealed class MechSystem : SharedMechSystem
         args.Handled = true;
     }
     #endregion
-
-    /// <summary>
-    ///     Event raised when the battery is successfully removed from the mech,
-    ///     on both success and failure
-    /// </summary>
-    private sealed class RemoveBatteryEvent : EntityEventArgs
-    {
-    }
-
-    /// <summary>
-    ///     Event raised when a person enters a mech, on both success and failure
-    /// </summary>
-    private sealed class MechEntryEvent : EntityEventArgs
-    {
-    }
-
-    /// <summary>
-    ///     Event raised when a person removes someone from a mech,
-    ///     on both success and failure
-    /// </summary>
-    private sealed class MechExitEvent : EntityEventArgs
-    {
-    }
 }
index b418e6a6e3337f6fd884d57d018d54dc0ffe8e1b..a36bc01ed665b028ade5aa82a34f15735d1bd04d 100644 (file)
@@ -1,4 +1,3 @@
-using System.Threading;
 using Content.Shared.Interaction;
 using Content.Shared.Audio;
 using Content.Shared.Jittering;
@@ -14,7 +13,6 @@ using Content.Server.Fluids.EntitySystems;
 using Content.Server.Body.Components;
 using Content.Server.Climbing;
 using Content.Server.Construction;
-using Content.Server.DoAfter;
 using Content.Server.Materials;
 using Content.Server.Mind.Components;
 using Content.Shared.DoAfter;
@@ -27,7 +25,7 @@ using Robust.Shared.Random;
 using Robust.Shared.Configuration;
 using Robust.Server.Player;
 using Robust.Shared.Physics.Components;
-using Content.Shared.Humanoid;
+using Content.Shared.Medical;
 
 namespace Content.Server.Medical.BiomassReclaimer
 {
@@ -43,7 +41,7 @@ namespace Content.Server.Medical.BiomassReclaimer
         [Dependency] private readonly ThrowingSystem _throwing = default!;
         [Dependency] private readonly IRobustRandom _robustRandom = default!;
         [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly IPlayerManager _playerManager = default!;
         [Dependency] private readonly MaterialStorageSystem _material = default!;
 
@@ -97,7 +95,7 @@ namespace Content.Server.Medical.BiomassReclaimer
             SubscribeLocalEvent<BiomassReclaimerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
             SubscribeLocalEvent<BiomassReclaimerComponent, PowerChangedEvent>(OnPowerChanged);
             SubscribeLocalEvent<BiomassReclaimerComponent, SuicideEvent>(OnSuicide);
-            SubscribeLocalEvent<BiomassReclaimerComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<BiomassReclaimerComponent, ReclaimerDoAfterEvent>(OnDoAfter);
         }
 
         private void OnSuicide(EntityUid uid, BiomassReclaimerComponent component, SuicideEvent args)
@@ -152,11 +150,10 @@ namespace Content.Server.Medical.BiomassReclaimer
             if (!HasComp<MobStateComponent>(args.Used) || !CanGib(uid, args.Used, component))
                 return;
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, 7f, target:args.Target, used:args.Used)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, 7f, new ReclaimerDoAfterEvent(), uid, target: args.Target, used: args.Used)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index 5f3120cff01a00827195aeac5623b1f3e7d58e72..adb5857b321c6dbe24805f20c522f04583ddfe81 100644 (file)
@@ -1,6 +1,6 @@
-using System.Threading;
 using Content.Shared.Damage;
 using Content.Shared.Damage.Prototypes;
+using Content.Shared.DoAfter;
 using Robust.Shared.Audio;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
@@ -39,12 +39,6 @@ namespace Content.Server.Medical.Components
         [DataField("delay")]
         public float Delay = 3f;
 
-        /// <summary>
-        /// Cancel token to prevent rapid healing
-        /// </summary>
-        [DataField("cancelToken")]
-        public CancellationTokenSource? CancelToken;
-
         /// <summary>
         /// Delay multiplier when healing yourself.
         /// </summary>
index 886922986bc55d1415706f1ffd58ba8b0268e01d..62c168c3e7ac1438df2a08cd1eef7334af98d846 100644 (file)
@@ -7,7 +7,6 @@ using Content.Server.Body.Systems;
 using Content.Server.Chemistry.Components.SolutionManager;
 using Content.Server.Chemistry.EntitySystems;
 using Content.Server.Climbing;
-using Content.Server.DoAfter;
 using Content.Server.Medical.Components;
 using Content.Server.NodeContainer;
 using Content.Server.NodeContainer.NodeGroups;
@@ -26,7 +25,6 @@ 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;
 using Robust.Shared.Timing;
@@ -42,7 +40,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
     [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
     [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
     [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
     [Dependency] private readonly SharedToolSystem _toolSystem = default!;
     [Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -56,9 +54,8 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
         SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
         SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
         SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
-        SubscribeLocalEvent<CryoPodComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<CryoPodComponent, CryoPodDragFinished>(OnDragFinished);
         SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
-        SubscribeLocalEvent<CryoPodComponent, CryoPodPryInterrupted>(OnCryoPodPryInterrupted);
 
         SubscribeLocalEvent<CryoPodComponent, AtmosDeviceUpdateEvent>(OnCryoPodUpdateAtmosphere);
         SubscribeLocalEvent<CryoPodComponent, DragDropTargetEvent>(HandleDragDropOn);
@@ -130,25 +127,23 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
         if (cryoPodComponent.BodyContainer.ContainedEntity != null)
             return;
 
-        var doAfterArgs = new DoAfterEventArgs(args.User, cryoPodComponent.EntryDelay, target:args.Dragged, used:uid)
+        var doAfterArgs = new DoAfterArgs(args.User, cryoPodComponent.EntryDelay, new CryoPodDragFinished(), uid, target: args.Dragged, used: uid)
         {
             BreakOnDamage = true,
-            BreakOnStun = true,
             BreakOnTargetMove = true,
             BreakOnUserMove = true,
             NeedHand = false,
         };
-        _doAfterSystem.DoAfter(doAfterArgs);
+        _doAfterSystem.TryStartDoAfter(doAfterArgs);
         args.Handled = true;
     }
 
-    private void OnDoAfter(EntityUid uid, CryoPodComponent component, DoAfterEvent args)
+    private void OnDragFinished(EntityUid uid, CryoPodComponent component, CryoPodDragFinished args)
     {
         if (args.Cancelled || args.Handled || args.Args.Target == null)
             return;
 
         InsertBody(uid, args.Args.Target.Value, component);
-
         args.Handled = true;
     }
 
@@ -179,18 +174,7 @@ public sealed partial class CryoPodSystem: SharedCryoPodSystem
         if (args.Handled || !cryoPodComponent.Locked || cryoPodComponent.BodyContainer.ContainedEntity == null)
             return;
 
-        if (TryComp(args.Used, out ToolComponent? tool)
-            && tool.Qualities.Contains("Prying")) // Why aren't those enums?
-        {
-            if (cryoPodComponent.IsPrying)
-                return;
-            cryoPodComponent.IsPrying = true;
-
-            var toolEvData = new ToolEventData(new CryoPodPryFinished(), targetEntity:uid);
-            _toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, new [] {"Prying"}, toolEvData);
-
-            args.Handled = true;
-        }
+        args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cryoPodComponent.PryDelay, "Prying", new CryoPodPryFinished());
     }
 
     private void OnExamined(EntityUid uid, CryoPodComponent component, ExaminedEvent args)
index 9b4d9e77ed5bc49c52c6c8baff73f5883707fe2c..d4382848f07d64e454e34621a4485d707715661a 100644 (file)
@@ -1,15 +1,15 @@
-using System.Threading;
 using Content.Server.Administration.Logs;
 using Content.Server.Body.Systems;
-using Content.Server.DoAfter;
 using Content.Server.Medical.Components;
 using Content.Server.Stack;
 using Content.Shared.Audio;
 using Content.Shared.Damage;
 using Content.Shared.Database;
 using Content.Shared.DoAfter;
+using Content.Shared.FixedPoint;
 using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Medical;
 using Content.Shared.Mobs;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
@@ -24,7 +24,7 @@ public sealed class HealingSystem : EntitySystem
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
     [Dependency] private readonly DamageableSystem _damageable = default!;
     [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly StackSystem _stacks = default!;
     [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
@@ -36,45 +36,43 @@ public sealed class HealingSystem : EntitySystem
         base.Initialize();
         SubscribeLocalEvent<HealingComponent, UseInHandEvent>(OnHealingUse);
         SubscribeLocalEvent<HealingComponent, AfterInteractEvent>(OnHealingAfterInteract);
-        SubscribeLocalEvent<DamageableComponent, DoAfterEvent<HealingData>>(OnDoAfter);
+        SubscribeLocalEvent<DamageableComponent, HealingDoAfterEvent>(OnDoAfter);
     }
 
-    private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent<HealingData> args)
+    private void OnDoAfter(EntityUid uid, DamageableComponent component, HealingDoAfterEvent args)
     {
-        if (args.Cancelled)
-        {
-            args.AdditionalData.HealingComponent.CancelToken = null;
+        if (!TryComp(args.Used, out HealingComponent? healing))
             return;
-        }
 
-        if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null)
+        if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid))
             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 (healing.BloodlossModifier != 0)
+            _bloodstreamSystem.TryModifyBleedAmount(uid, healing.BloodlossModifier);
 
-        var healed = _damageable.TryChangeDamage(uid, args.AdditionalData.HealingComponent.Damage, true, origin: args.Args.User);
+        var healed = _damageable.TryChangeDamage(uid, healing.Damage, true, origin: args.Args.User);
 
-        if (healed == null)
+        if (healed == null && healing.BloodlossModifier != 0)
             return;
 
-        // Reverify that we can heal the damage.
-        _stacks.Use(args.Args.Used.Value, 1, args.AdditionalData.Stack);
+        var total = healed?.Total ?? FixedPoint2.Zero;
 
-        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");
+        // Reverify that we can heal the damage.
+        _stacks.Use(args.Used.Value, 1);
 
+        if (uid != args.User)
+            _adminLogger.Add(LogType.Healed,
+                $"{EntityManager.ToPrettyString(args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {total:damage} damage");
         else
-            _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed themselves for {healed.Total:damage} damage");
+            _adminLogger.Add(LogType.Healed,
+                $"{EntityManager.ToPrettyString(args.User):user} healed themselves for {total:damage} damage");
 
-        if (args.AdditionalData.HealingComponent.HealingEndSound != null)
-            _audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
+        _audio.PlayPvs(healing.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
 
-        args.AdditionalData.HealingComponent.CancelToken = null;
         args.Handled = true;
     }
 
@@ -98,13 +96,14 @@ public sealed class HealingSystem : EntitySystem
 
     private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
     {
-        if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage) || component.CancelToken != null)
+        if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage))
             return false;
 
         if (targetDamage.TotalDamage == 0)
             return false;
 
-        if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(targetDamage.DamageContainerID))
+        if (component.DamageContainerID is not null &&
+            !component.DamageContainerID.Equals(targetDamage.DamageContainerID))
             return false;
 
         if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
@@ -114,7 +113,8 @@ public sealed class HealingSystem : EntitySystem
             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 isNotSelf = user != target;
 
@@ -122,27 +122,18 @@ public sealed class HealingSystem : EntitySystem
             ? component.Delay
             : component.Delay * GetScaledHealingPenalty(user, component);
 
-        component.CancelToken = new CancellationTokenSource();
-
-        var healingData = new HealingData(component, stack);
-
-        var doAfterEventArgs = new DoAfterEventArgs(user, delay, cancelToken: component.CancelToken.Token,target: target, used: uid)
-        {
-            //Raise the event on the target if it's not self, otherwise raise it on self.
-            RaiseOnTarget = isNotSelf,
-            RaiseOnUser = !isNotSelf,
-            BreakOnUserMove = true,
-            BreakOnTargetMove = true,
-            // Didn't break on damage as they may be trying to prevent it and
-            // not being able to heal your own ticking damage would be frustrating.
-            BreakOnStun = true,
-            NeedHand = true,
-            // Juusstt in case damageble gets removed it avoids having to re-cancel the token. Won't need this when DoAfterEvent<T> gets added.
-            PostCheck = () => true
-        };
-
-        _doAfter.DoAfter(doAfterEventArgs, healingData);
-
+        var doAfterEventArgs =
+            new DoAfterArgs(user, delay, new HealingDoAfterEvent(), target, target: target, used: uid)
+            {
+                //Raise the event on the target if it's not self, otherwise raise it on self.
+                BreakOnUserMove = true,
+                BreakOnTargetMove = true,
+                // Didn't break on damage as they may be trying to prevent it and
+                // not being able to heal your own ticking damage would be frustrating.
+                NeedHand = true,
+            };
+
+        _doAfter.TryStartDoAfter(doAfterEventArgs);
         return true;
     }
 
@@ -155,7 +146,8 @@ public sealed class HealingSystem : EntitySystem
     public float GetScaledHealingPenalty(EntityUid uid, HealingComponent component)
     {
         var output = component.Delay;
-        if (!TryComp<MobThresholdsComponent>(uid, out var mobThreshold) || !TryComp<DamageableComponent>(uid, out var damageable))
+        if (!TryComp<MobThresholdsComponent>(uid, out var mobThreshold) ||
+            !TryComp<DamageableComponent>(uid, out var damageable))
             return output;
         if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold))
             return 1;
@@ -165,10 +157,4 @@ public sealed class HealingSystem : EntitySystem
         var modifier = percentDamage * (component.SelfHealPenaltyMultiplier - 1) + 1;
         return Math.Max(modifier, 1);
     }
-
-    private record struct HealingData(HealingComponent HealingComponent, StackComponent Stack)
-    {
-        public HealingComponent HealingComponent = HealingComponent;
-        public StackComponent Stack = Stack;
-    }
 }
index 0ba0770036384a191659cf9afcd4309fbe7b6dfc..67fa2ebac8bf6a1788f8d4eda03540f1d70467a8 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.DoAfter;
 using Content.Server.Medical.Components;
 using Content.Server.Disease;
 using Content.Server.Popups;
@@ -6,6 +5,7 @@ using Content.Shared.Damage;
 using Content.Shared.DoAfter;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
+using Content.Shared.MedicalScanner;
 using Content.Shared.Mobs.Components;
 using Robust.Server.GameObjects;
 using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent;
@@ -16,7 +16,7 @@ namespace Content.Server.Medical
     {
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly DiseaseSystem _disease = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
 
@@ -25,7 +25,7 @@ namespace Content.Server.Medical
             base.Initialize();
             SubscribeLocalEvent<HealthAnalyzerComponent, ActivateInWorldEvent>(HandleActivateInWorld);
             SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
-            SubscribeLocalEvent<HealthAnalyzerComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
         }
 
         private void HandleActivateInWorld(EntityUid uid, HealthAnalyzerComponent healthAnalyzer, ActivateInWorldEvent args)
@@ -40,11 +40,10 @@ namespace Content.Server.Medical
 
             _audio.PlayPvs(healthAnalyzer.ScanningBeginSound, uid);
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, healthAnalyzer.ScanDelay, target: args.Target, used:uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, healthAnalyzer.ScanDelay, new HealthAnalyzerDoAfterEvent(), uid, target: args.Target, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index af9589d01666891fefe1933b2515beb48ae8730b..b89c463dff0bc4369ada8b6a17208e86ae375108 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Body.Components;
-using Content.Server.DoAfter;
 using Content.Server.Medical.Components;
 using Content.Server.Popups;
 using Content.Shared.Actions;
@@ -11,6 +10,7 @@ using Content.Shared.Verbs;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
 using Content.Shared.DoAfter;
+using Content.Shared.Medical;
 using Robust.Shared.Utility;
 
 namespace Content.Server.Medical
@@ -18,7 +18,7 @@ namespace Content.Server.Medical
     public sealed class StethoscopeSystem : EntitySystem
     {
         [Dependency] private readonly PopupSystem _popupSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
 
         public override void Initialize()
@@ -29,7 +29,7 @@ namespace Content.Server.Medical
             SubscribeLocalEvent<WearingStethoscopeComponent, GetVerbsEvent<InnateVerb>>(AddStethoscopeVerb);
             SubscribeLocalEvent<StethoscopeComponent, GetItemActionsEvent>(OnGetActions);
             SubscribeLocalEvent<StethoscopeComponent, StethoscopeActionEvent>(OnStethoscopeAction);
-            SubscribeLocalEvent<StethoscopeComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<StethoscopeComponent, StethoscopeDoAfterEvent>(OnDoAfter);
         }
 
         /// <summary>
@@ -103,11 +103,10 @@ namespace Content.Server.Medical
         // construct the doafter and start it
         private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp)
         {
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(user, comp.Delay, target: target, used:scope)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, comp.Delay, new StethoscopeDoAfterEvent(), scope, target: target, used: scope)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index 06aa9f7ca92648d4679f845aefd18cc088469f07..526eaab55af6da60d9ada43948ae3ef916a8dd76 100644 (file)
@@ -99,7 +99,8 @@ public sealed partial class NPCSteeringSystem
                     if (doorQuery.TryGetComponent(ent, out var door) && door.State != DoorState.Open)
                     {
                         // TODO: Use the verb.
-                        if (door.State != DoorState.Opening && !door.BeingPried)
+
+                        if (door.State != DoorState.Opening)
                             _doors.TryPryDoor(ent, uid, uid, door, true);
 
                         return SteeringObstacleStatus.Continuing;
index 47e10929a30ccc95d26d05e41971897bf7ba728a..fa18297a646cc51e2e7bfbf2d922f431689f0723 100644 (file)
@@ -2,7 +2,6 @@ using Content.Server.AlertLevel;
 using Content.Server.Audio;
 using Content.Server.Chat.Systems;
 using Content.Server.Coordinates.Helpers;
-using Content.Server.DoAfter;
 using Content.Server.Explosion.EntitySystems;
 using Content.Server.Popups;
 using Content.Server.Station.Systems;
@@ -28,7 +27,7 @@ namespace Content.Server.Nuke
         [Dependency] private readonly StationSystem _stationSystem = default!;
         [Dependency] private readonly ServerGlobalSoundSystem _soundSystem = default!;
         [Dependency] private readonly ChatSystem _chatSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly UserInterfaceSystem _ui = default!;
@@ -67,7 +66,7 @@ namespace Content.Server.Nuke
             SubscribeLocalEvent<NukeComponent, NukeKeypadEnterMessage>(OnEnterButtonPressed);
 
             // Doafter events
-            SubscribeLocalEvent<NukeComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<NukeComponent, NukeDisarmDoAfterEvent>(OnDoAfter);
         }
 
         private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
@@ -559,16 +558,17 @@ namespace Content.Server.Nuke
 
         private void DisarmBombDoafter(EntityUid uid, EntityUid user, NukeComponent nuke)
         {
-            var doafter = new DoAfterEventArgs(user, nuke.DisarmDoafterLength, target: uid)
+            var doafter = new DoAfterArgs(user, nuke.DisarmDoafterLength, new NukeDisarmDoAfterEvent(), uid, target: uid)
             {
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 NeedHand = true
             };
 
-            _doAfterSystem.DoAfter(doafter);
+            if (!_doAfterSystem.TryStartDoAfter(doafter))
+                return;
+
             _popups.PopupEntity(Loc.GetString("nuke-component-doafter-warning"), user,
                 user, PopupType.LargeCaution);
         }
index 60784ac4123b600b1199c0c04aff4a841e348c99..7bc1913fe2ce1601accfd12c7809735516b37b1f 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.DoAfter;
 using Content.Shared.FixedPoint;
 using JetBrains.Annotations;
 using Robust.Shared.Audio;
@@ -34,19 +35,6 @@ namespace Content.Server.Nutrition.Components
         [DataField("burstSound")]
         public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg");
 
-        /// <summary>
-        /// Is this drink being forced on someone else?
-        /// </summary>
-        /// <returns></returns>
-        [DataField("forceDrink")]
-        public bool ForceDrink;
-
-        /// <summary>
-        /// Is the entity currently drinking or trying to make someone else drink?
-        /// </summary>
-        [DataField("drinking")]
-        public bool Drinking;
-
         /// <summary>
         /// How long it takes to drink this yourself.
         /// </summary>
index 7e9effdfda6d20f6b896242ca59df0838bcb6aac..eb76cbbf55a6be8aa6c7d6513503a418a46abe65 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chemistry.EntitySystems;
 using Content.Server.Nutrition.EntitySystems;
+using Content.Shared.DoAfter;
 using Content.Shared.FixedPoint;
 using Robust.Shared.Audio;
 using Robust.Shared.Prototypes;
@@ -40,18 +41,6 @@ namespace Content.Server.Nutrition.Components
         [DataField("eatMessage")]
         public string EatMessage = "food-nom";
 
-        /// <summary>
-        /// Is this entity being forcefed?
-        /// </summary>
-        [DataField("forceFeed")]
-        public bool ForceFeed;
-
-        /// <summary>
-        /// Is this entity eating or being fed?
-        /// </summary>
-        [DataField(("eating"))]
-        public bool Eating;
-
         /// <summary>
         /// How long it takes to eat the food personally.
         /// </summary>
index 933efc268a54fd521160ec54d44b4480cd09573c..21ab837b48160ca040b3c68c7852b78cb68d411a 100644 (file)
@@ -2,7 +2,6 @@ using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
 using Content.Server.Chemistry.Components.SolutionManager;
 using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Forensics;
 using Content.Server.Nutrition.Components;
@@ -10,7 +9,6 @@ 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;
@@ -21,6 +19,7 @@ using Content.Shared.Interaction;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Throwing;
 using Content.Shared.Verbs;
@@ -42,7 +41,7 @@ namespace Content.Server.Nutrition.EntitySystems
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly BodySystem _bodySystem = default!;
         [Dependency] private readonly StomachSystem _stomachSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
         [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
         [Dependency] private readonly SpillableSystem _spillableSystem = default!;
@@ -55,6 +54,7 @@ namespace Content.Server.Nutrition.EntitySystems
         {
             base.Initialize();
 
+            // TODO add InteractNoHandEvent for entities like mice.
             SubscribeLocalEvent<DrinkComponent, SolutionChangedEvent>(OnSolutionChange);
             SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit);
             SubscribeLocalEvent<DrinkComponent, LandEvent>(HandleLand);
@@ -63,7 +63,7 @@ namespace Content.Server.Nutrition.EntitySystems
             SubscribeLocalEvent<DrinkComponent, GetVerbsEvent<AlternativeVerb>>(AddDrinkVerb);
             SubscribeLocalEvent<DrinkComponent, ExaminedEvent>(OnExamined);
             SubscribeLocalEvent<DrinkComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
-            SubscribeLocalEvent<DrinkComponent, DoAfterEvent<DrinkData>>(OnDoAfter);
+            SubscribeLocalEvent<DrinkComponent, ConsumeDoAfterEvent>(OnDoAfter);
         }
 
         public bool IsEmpty(EntityUid uid, DrinkComponent? component = null)
@@ -218,7 +218,7 @@ namespace Content.Server.Nutrition.EntitySystems
 
         private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
         {
-            if (!EntityManager.HasComponent<BodyComponent>(target) || drink.Drinking)
+            if (!EntityManager.HasComponent<BodyComponent>(target))
                 return false;
 
             if (!drink.Opened)
@@ -236,16 +236,18 @@ namespace Content.Server.Nutrition.EntitySystems
                 return true;
             }
 
+            if (drinkSolution.Name == null)
+                return false;
+
             if (_foodSystem.IsMouthBlocked(target, user))
                 return true;
 
             if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true))
                 return true;
 
-            drink.Drinking = true;
-            drink.ForceDrink = user != target;
+            var forceDrink = user != target;
 
-            if (drink.ForceDrink)
+            if (forceDrink)
             {
                 var userName = Identity.Entity(user, EntityManager);
 
@@ -263,53 +265,51 @@ namespace Content.Server.Nutrition.EntitySystems
 
             var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(user, drinkSolution);
 
-            var drinkData = new DrinkData(drinkSolution, flavors);
-
-            var doAfterEventArgs = new DoAfterEventArgs(user, drink.ForceDrink ? drink.ForceFeedDelay : drink.Delay,
-                target: target, used: item)
+            var doAfterEventArgs = new DoAfterArgs(
+                user,
+                forceDrink ? drink.ForceFeedDelay : drink.Delay,
+                new ConsumeDoAfterEvent(drinkSolution.Name, flavors),
+                eventTarget: item,
+                target: target,
+                used: item)
             {
-                RaiseOnTarget = user != target,
-                RaiseOnUser = false,
-                BreakOnUserMove = drink.ForceDrink,
+                BreakOnUserMove = forceDrink,
                 BreakOnDamage = true,
-                BreakOnStun = true,
-                BreakOnTargetMove = drink.ForceDrink,
+                BreakOnTargetMove = forceDrink,
                 MovementThreshold = 0.01f,
                 DistanceThreshold = 1.0f,
-                NeedHand = true
+                // Mice and the like can eat without hands.
+                // TODO maybe set this based on some CanEatWithoutHands event or component?
+                NeedHand = forceDrink,
             };
 
-            _doAfterSystem.DoAfter(doAfterEventArgs, drinkData);
-
+            _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
             return true;
         }
 
         /// <summary>
         ///     Raised directed at a victim when someone has force fed them a drink.
         /// </summary>
-        private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent<DrinkData> args)
+        private void OnDoAfter(EntityUid uid, DrinkComponent component, ConsumeDoAfterEvent args)
         {
-            if (args.Cancelled)
-            {
-                component.ForceDrink = false;
-                component.Drinking = false;
+            if (args.Handled || args.Cancelled || component.Deleted)
                 return;
-            }
 
-            if (args.Handled || component.Deleted)
+            if (!TryComp<BodyComponent>(args.Args.Target, out var body))
                 return;
 
-            if (!TryComp<BodyComponent>(args.Args.Target, out var body))
+            if (!_solutionContainerSystem.TryGetSolution(args.Used, args.Solution, out var solution))
                 return;
 
-            component.Drinking = false;
+            var transferAmount = FixedPoint2.Min(component.TransferAmount, solution.Volume);
+            var drained = _solutionContainerSystem.Drain(uid, solution, transferAmount);
+            var forceDrink = args.User != args.Target;
 
-            var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume);
-            var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount);
+            //var forceDrink = args.Args.Target.Value != args.Args.User;
 
             if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
             {
-                _popupSystem.PopupEntity(component.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);
+                _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 (HasComp<RefillableSolutionComponent>(args.Args.Target.Value))
                 {
@@ -318,7 +318,7 @@ namespace Content.Server.Nutrition.EntitySystems
                     return;
                 }
 
-                _solutionContainerSystem.Refill(args.Args.Target.Value, args.AdditionalData.DrinkSolution, drained);
+                _solutionContainerSystem.Refill(args.Args.Target.Value, solution, drained);
                 args.Handled = true;
                 return;
             }
@@ -330,21 +330,21 @@ namespace Content.Server.Nutrition.EntitySystems
             {
                 _popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value);
 
-                if (component.ForceDrink)
+                if (forceDrink)
                 {
                     _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(uid, args.AdditionalData.DrinkSolution, drained);
+                    _solutionContainerSystem.TryAddSolution(uid, solution, drained);
 
                 args.Handled = true;
                 return;
             }
 
-            var flavors = args.AdditionalData.FlavorMessage;
+            var flavors = args.FlavorMessage;
 
-            if (component.ForceDrink)
+            if (forceDrink)
             {
                 var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
                 var userName = Identity.Entity(args.Args.User, EntityManager);
@@ -372,11 +372,9 @@ namespace Content.Server.Nutrition.EntitySystems
 
             _audio.PlayPvs(_audio.GetSound(component.UseSound), args.Args.Target.Value, AudioParams.Default.WithVolume(-2f));
 
-            _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.DrinkSolution, ReactionMethod.Ingestion);
+            _reaction.DoEntityReaction(args.Args.Target.Value, solution, ReactionMethod.Ingestion);
             //TODO: Grab the stomach UIDs somehow without using Owner
             _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
-
-            component.ForceDrink = false;
             args.Handled = true;
 
             var comp = EnsureComp<ForensicsComponent>(uid);
@@ -421,11 +419,5 @@ 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;
-        }
     }
 }
index f7f9358ceead0cd2f8b032dfb5f115f6fa52d8b7..30cd1c8ba6c6246445d3c7a78eee804b11c0c9fc 100644 (file)
@@ -1,14 +1,12 @@
 using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
 using Content.Server.Chemistry.EntitySystems;
-using Content.Server.DoAfter;
 using Content.Server.Hands.Components;
 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;
@@ -20,6 +18,7 @@ using Content.Shared.Interaction.Events;
 using Content.Shared.Inventory;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
+using Content.Shared.Nutrition;
 using Content.Shared.Verbs;
 using Robust.Shared.Audio;
 using Robust.Shared.Player;
@@ -39,7 +38,7 @@ namespace Content.Server.Nutrition.EntitySystems
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
         [Dependency] private readonly UtensilSystem _utensilSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
         [Dependency] private readonly InventorySystem _inventorySystem = default!;
         [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
@@ -51,10 +50,11 @@ namespace Content.Server.Nutrition.EntitySystems
         {
             base.Initialize();
 
+            // TODO add InteractNoHandEvent for entities like mice.
             SubscribeLocalEvent<FoodComponent, UseInHandEvent>(OnUseFoodInHand);
             SubscribeLocalEvent<FoodComponent, AfterInteractEvent>(OnFeedFood);
             SubscribeLocalEvent<FoodComponent, GetVerbsEvent<AlternativeVerb>>(AddEatVerb);
-            SubscribeLocalEvent<FoodComponent, DoAfterEvent<FoodData>>(OnDoAfter);
+            SubscribeLocalEvent<FoodComponent, ConsumeDoAfterEvent>(OnDoAfter);
             SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
         }
 
@@ -66,7 +66,8 @@ namespace Content.Server.Nutrition.EntitySystems
             if (ev.Handled)
                 return;
 
-            ev.Handled = TryFeed(ev.User, ev.User, uid, foodComponent);
+            ev.Handled = true;
+            TryFeed(ev.User, ev.User, uid, foodComponent);
         }
 
         /// <summary>
@@ -77,7 +78,8 @@ namespace Content.Server.Nutrition.EntitySystems
             if (args.Handled || args.Target == null || !args.CanReach)
                 return;
 
-            args.Handled = TryFeed(args.User, args.Target.Value, uid, foodComponent);
+            args.Handled = true;
+            TryFeed(args.User, args.Target.Value, uid, foodComponent);
         }
 
         public bool TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
@@ -87,10 +89,10 @@ namespace Content.Server.Nutrition.EntitySystems
                 return false;
 
             // Target can't be fed or they're already eating
-            if (!EntityManager.HasComponent<BodyComponent>(target) || foodComp.Eating)
+            if (!EntityManager.HasComponent<BodyComponent>(target))
                 return false;
 
-            if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution))
+            if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution) || foodSolution.Name == null)
                 return false;
 
             var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food, user, foodSolution);
@@ -105,16 +107,15 @@ namespace Content.Server.Nutrition.EntitySystems
             if (IsMouthBlocked(target, user))
                 return false;
 
-            if (!TryGetRequiredUtensils(user, foodComp, out var utensils))
-                return false;
-
             if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true))
                 return true;
 
-            foodComp.Eating = true;
-            foodComp.ForceFeed = user != target;
+            if (!TryGetRequiredUtensils(user, foodComp, out _))
+                return true;
 
-            if (foodComp.ForceFeed)
+            var forceFeed = user != target;
+
+            if (forceFeed)
             {
                 var userName = Identity.Entity(user, EntityManager);
                 _popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
@@ -129,38 +130,31 @@ namespace Content.Server.Nutrition.EntitySystems
                 _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
             }
 
-            var foodData = new FoodData(foodSolution, flavors, utensils);
-
-            var doAfterEventArgs = new DoAfterEventArgs(user, foodComp.ForceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
+            var doAfterEventArgs = new DoAfterArgs(
+                user,
+                forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay,
+                new ConsumeDoAfterEvent(foodSolution.Name, flavors),
+                eventTarget: food,
+                target: target,
+                used: food)
             {
-                RaiseOnTarget = foodComp.ForceFeed,
-                RaiseOnUser = false, //causes a crash if mice eat if true
-                BreakOnUserMove = foodComp.ForceFeed,
+                BreakOnUserMove = forceFeed,
                 BreakOnDamage = true,
-                BreakOnStun = true,
-                BreakOnTargetMove = foodComp.ForceFeed,
+                BreakOnTargetMove = forceFeed,
                 MovementThreshold = 0.01f,
                 DistanceThreshold = 1.0f,
-                NeedHand = true
+                // Mice and the like can eat without hands.
+                // TODO maybe set this based on some CanEatWithoutHands event or component?
+                NeedHand = forceFeed,
             };
 
-            _doAfterSystem.DoAfter(doAfterEventArgs, foodData);
-
+            _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
             return true;
-
         }
 
-        private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent<FoodData> args)
+        private void OnDoAfter(EntityUid uid, FoodComponent component, ConsumeDoAfterEvent args)
         {
-            //Prevents the target from being force fed food but allows the user to chow down
-            if (args.Cancelled)
-            {
-                component.Eating = false;
-                component.ForceFeed = false;
-                return;
-            }
-
-            if (args.Handled || component.Deleted || args.Args.Target == null)
+            if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null)
                 return;
 
             if (!TryComp<BodyComponent>(args.Args.Target.Value, out var body))
@@ -169,29 +163,36 @@ namespace Content.Server.Nutrition.EntitySystems
             if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
                 return;
 
-            component.Eating = false;
+            if (!_solutionContainerSystem.TryGetSolution(args.Used, args.Solution, out var solution))
+                return;
 
-            var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, args.AdditionalData.FoodSolution.Volume) : args.AdditionalData.FoodSolution.Volume;
+            if (!TryGetRequiredUtensils(args.User, component, out var utensils))
+                return;
+
+            args.Handled = true;
+
+            var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, solution.Volume) : solution.Volume;
 
-            var split = _solutionContainerSystem.SplitSolution(uid, args.AdditionalData.FoodSolution, transferAmount);
+            var split = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount);
             //TODO: Get the stomach UID somehow without nabbing owner
             var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
 
+            var forceFeed = args.User != args.Target;
+
             // No stomach so just popup a message that they can't eat.
             if (firstStomach == null)
             {
-                _solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.FoodSolution, split);
-                _popupSystem.PopupEntity(component.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;
+                _solutionContainerSystem.TryAddSolution(uid, solution, 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);
                 return;
             }
 
-            _reaction.DoEntityReaction(args.Args.Target.Value, args.AdditionalData.FoodSolution, ReactionMethod.Ingestion);
+            _reaction.DoEntityReaction(args.Args.Target.Value, solution, ReactionMethod.Ingestion);
             _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp);
 
-            var flavors = args.AdditionalData.FlavorMessage;
+            var flavors = args.FlavorMessage;
 
-            if (component.ForceFeed)
+            if (forceFeed)
             {
                 var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
                 var userName = Identity.Entity(args.Args.User, EntityManager);
@@ -202,7 +203,6 @@ namespace Content.Server.Nutrition.EntitySystems
 
                 // log successful force feed
                 _adminLogger.Add(LogType.ForceFeed, LogImpact.Medium, $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to eat {ToPrettyString(uid):food}");
-                component.ForceFeed = false;
             }
             else
             {
@@ -215,26 +215,19 @@ namespace Content.Server.Nutrition.EntitySystems
             _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
-            //TODO: Replace utensil owner with actual UID
-            foreach (var utensil in args.AdditionalData.Utensils)
+            foreach (var utensil in utensils)
             {
-                _utensilSystem.TryBreak(utensil.Owner, args.Args.User);
+                _utensilSystem.TryBreak(utensil, args.Args.User);
             }
 
             if (component.UsesRemaining > 0)
-            {
-                args.Handled = true;
                 return;
-            }
-
 
             if (string.IsNullOrEmpty(component.TrashPrototype))
                 EntityManager.QueueDeleteEntity(uid);
 
             else
                 DeleteAndSpawnTrash(component, uid, args.Args.User);
-
-            args.Handled = true;
         }
 
         private void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid? user = null)
@@ -329,9 +322,9 @@ namespace Content.Server.Nutrition.EntitySystems
         }
 
         private bool TryGetRequiredUtensils(EntityUid user, FoodComponent component,
-            out List<UtensilComponent> utensils, HandsComponent? hands = null)
+            out List<EntityUid> utensils, HandsComponent? hands = null)
         {
-            utensils = new List<UtensilComponent>();
+            utensils = new List<EntityUid>();
 
             if (component.Utensil != UtensilType.None)
                 return true;
@@ -352,7 +345,7 @@ namespace Content.Server.Nutrition.EntitySystems
                 {
                     // Add to used list
                     usedTypes |= utensil.Types;
-                    utensils.Add(utensil);
+                    utensils.Add(item);
                 }
             }
 
@@ -415,12 +408,5 @@ namespace Content.Server.Nutrition.EntitySystems
 
             return attempt.Cancelled;
         }
-
-        private record struct FoodData(Solution FoodSolution, string FlavorMessage, List<UtensilComponent> Utensils)
-        {
-            public readonly Solution FoodSolution = FoodSolution;
-            public readonly string FlavorMessage = FlavorMessage;
-            public readonly List<UtensilComponent> Utensils = Utensils;
-        }
     }
 }
index 691ba32d8a2733895b2a01fa0418af7e053cb58a..183422aa21ef7744f4ffe760f3e3bd0e8414152a 100644 (file)
@@ -4,11 +4,13 @@ using Content.Server.Power.Components;
 using Content.Shared.Access.Components;
 using Content.Shared.Access.Systems;
 using Content.Shared.APC;
+using Content.Shared.DoAfter;
 using Content.Shared.Emag.Components;
 using Content.Shared.Emag.Systems;
 using Content.Shared.Examine;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
+using Content.Shared.Power;
 using Content.Shared.Tools;
 using Content.Shared.Tools.Components;
 using JetBrains.Annotations;
@@ -28,6 +30,7 @@ namespace Content.Server.Power.EntitySystems
         [Dependency] private readonly IGameTiming _gameTiming = default!;
         [Dependency] private readonly SharedToolSystem _toolSystem = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+        [Dependency] private readonly SharedAudioSystem _audio = default!;
 
         private const float ScrewTime = 2f;
 
@@ -43,7 +46,7 @@ namespace Content.Server.Power.EntitySystems
             SubscribeLocalEvent<ApcComponent, ApcToggleMainBreakerMessage>(OnToggleMainBreaker);
             SubscribeLocalEvent<ApcComponent, GotEmaggedEvent>(OnEmagged);
 
-            SubscribeLocalEvent<ApcToolFinishedEvent>(OnToolFinished);
+            SubscribeLocalEvent<ApcComponent, ApcToolFinishedEvent>(OnToolFinished);
             SubscribeLocalEvent<ApcComponent, InteractUsingEvent>(OnInteractUsing);
             SubscribeLocalEvent<ApcComponent, ExaminedEvent>(OnExamine);
 
@@ -220,27 +223,21 @@ namespace Content.Server.Power.EntitySystems
             if (!EntityManager.TryGetComponent(args.Used, out ToolComponent? tool))
                 return;
 
-            var toolEvData = new ToolEventData(new ApcToolFinishedEvent(uid), fuel: 0f);
-
-            if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new [] { "Screwing" }, toolEvData, toolComponent:tool))
+            if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, "Screwing", new ApcToolFinishedEvent(), toolComponent:tool))
                 args.Handled = true;
         }
 
-        private void OnToolFinished(ApcToolFinishedEvent args)
+        private void OnToolFinished(EntityUid uid, ApcComponent component, ApcToolFinishedEvent args)
         {
-            if (!EntityManager.TryGetComponent(args.Target, out ApcComponent? component))
+            if (!args.Cancelled)
                 return;
-            component.IsApcOpen = !component.IsApcOpen;
 
-            if (TryComp(args.Target, out AppearanceComponent? appearance))
-            {
-                UpdatePanelAppearance(args.Target, appearance);
-            }
+            component.IsApcOpen = !component.IsApcOpen;
+            UpdatePanelAppearance(uid, apc: component);
 
-            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);
+            // this will play on top of the normal screw driver tool sound.
+            var sound = component.IsApcOpen ? component.ScrewdriverOpenSound : component.ScrewdriverCloseSound;
+            _audio.PlayPvs(sound, uid);
         }
 
         private void UpdatePanelAppearance(EntityUid uid, AppearanceComponent? appearance = null, ApcComponent? apc = null)
@@ -251,16 +248,6 @@ namespace Content.Server.Power.EntitySystems
             _appearance.SetData(uid, ApcVisuals.PanelState, GetPanelState(apc), appearance);
         }
 
-        private sealed class ApcToolFinishedEvent : EntityEventArgs
-        {
-            public EntityUid Target { get; }
-
-            public ApcToolFinishedEvent(EntityUid target)
-            {
-                Target = target;
-            }
-        }
-
         private void OnExamine(EntityUid uid, ApcComponent component, ExaminedEvent args)
         {
             args.PushMarkup(Loc.GetString(component.IsApcOpen
index eead07f34d54613709e5a9ee1f2c26df6c268017..8dd09fab3531cb09da5bd80df3458acc4a0f10f7 100644 (file)
@@ -3,6 +3,7 @@ using Content.Server.Electrocution;
 using Content.Server.Power.Components;
 using Content.Server.Stack;
 using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Tools;
 using Content.Shared.Tools.Components;
@@ -26,7 +27,7 @@ public sealed partial class CableSystem : EntitySystem
         InitializeCablePlacer();
 
         SubscribeLocalEvent<CableComponent, InteractUsingEvent>(OnInteractUsing);
-        SubscribeLocalEvent<CableComponent, CuttingFinishedEvent>(OnCableCut);
+        SubscribeLocalEvent<CableComponent, CableCuttingFinishedEvent>(OnCableCut);
         // Shouldn't need re-anchoring.
         SubscribeLocalEvent<CableComponent, AnchorStateChangedEvent>(OnAnchorChanged);
     }
@@ -36,12 +37,14 @@ public sealed partial class CableSystem : EntitySystem
         if (args.Handled)
             return;
 
-        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);
+        args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, cable.CuttingDelay, cable.CuttingQuality, new CableCuttingFinishedEvent());
     }
 
-    private void OnCableCut(EntityUid uid, CableComponent cable, CuttingFinishedEvent args)
+    private void OnCableCut(EntityUid uid, CableComponent cable, DoAfterEvent args)
     {
+        if (args.Cancelled)
+            return;
+
         if (_electrocutionSystem.TryDoElectrifiedAct(uid, args.User))
             return;
 
@@ -67,13 +70,3 @@ public sealed partial class CableSystem : EntitySystem
         QueueDel(uid);
     }
 }
-
-public sealed class CuttingFinishedEvent : EntityEventArgs
-{
-    public EntityUid User;
-
-    public CuttingFinishedEvent(EntityUid user)
-    {
-        User = user;
-    }
-}
index 23c8009d562d271f50dffd2f3535c781fd2cca9a..5375ae71b7888dd3c9d509a41632ae5caddf861a 100644 (file)
@@ -39,7 +39,5 @@ namespace Content.Server.RCD.Components
         /// </summary>
         [ViewVariables(VVAccess.ReadWrite)] [DataField("ammo")]
         public int CurrentAmmo = DefaultAmmoCount;
-
-        public CancellationTokenSource? CancelToken = null;
     }
 }
index 38c4e0f36aef64e9fb5b9c92eed316ed82353510..b1b674a0aa39738f9ac5588d016cab584b1977b6 100644 (file)
@@ -1,6 +1,4 @@
-using System.Threading;
 using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
 using Content.Server.Popups;
 using Content.Server.RCD.Components;
 using Content.Shared.Database;
@@ -23,7 +21,7 @@ namespace Content.Server.RCD.Systems
         [Dependency] private readonly IMapManager _mapManager = default!;
 
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
         [Dependency] private readonly PopupSystem _popup = default!;
         [Dependency] private readonly TagSystem _tagSystem = default!;
@@ -59,14 +57,6 @@ namespace Content.Server.RCD.Systems
             if (args.Handled || !args.CanReach)
                 return;
 
-            if (rcd.CancelToken != null)
-            {
-                rcd.CancelToken?.Cancel();
-                rcd.CancelToken = null;
-                args.Handled = true;
-                return;
-            }
-
             if (!args.ClickLocation.IsValid(EntityManager)) return;
 
             var clickLocationMod = args.ClickLocation;
@@ -96,19 +86,15 @@ namespace Content.Server.RCD.Systems
             var user = args.User;
 
             //Using an RCD isn't instantaneous
-            rcd.CancelToken = new CancellationTokenSource();
-            var doAfterEventArgs = new DoAfterEventArgs(user, rcd.Delay, rcd.CancelToken.Token, args.Target)
+            var doAfterEventArgs = new DoAfterArgs(user, rcd.Delay, new AwaitedDoAfterEvent(), null, target: args.Target)
             {
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 NeedHand = true,
                 ExtraCheck = () => IsRCDStillValid(rcd, args, mapGrid, tile, startingMode) //All of the sanity checks are here
             };
 
             var result = await _doAfterSystem.WaitDoAfter(doAfterEventArgs);
 
-            rcd.CancelToken = null;
-
             if (result == DoAfterStatus.Cancelled)
                 return;
 
index bf7593185d21423d117745690f6b1e4dcb0ead2e..56f1eb0903e87fc9270ead88a34db5e50f05a3a7 100644 (file)
@@ -1,14 +1,16 @@
 using Content.Server.Administration.Logs;
 using Content.Shared.Damage;
 using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
+using Content.Shared.Repairable;
 using Content.Shared.Tools;
 using Content.Shared.Tools.Components;
 
 namespace Content.Server.Repairable
 {
-    public sealed class RepairableSystem : EntitySystem
+    public sealed class RepairableSystem : SharedRepairableSystem
     {
         [Dependency] private readonly SharedToolSystem _toolSystem = default!;
         [Dependency] private readonly DamageableSystem _damageableSystem = default!;
@@ -22,6 +24,9 @@ namespace Content.Server.Repairable
 
         private void OnRepairFinished(EntityUid uid, RepairableComponent component, RepairFinishedEvent args)
         {
+            if (args.Cancelled)
+                return;
+
             if (!EntityManager.TryGetComponent(uid, out DamageableComponent? damageable) || damageable.TotalDamage == 0)
                 return;
 
@@ -38,15 +43,17 @@ namespace Content.Server.Repairable
                 _adminLogger.Add(LogType.Healed, $"{ToPrettyString(args.User):user} repaired {ToPrettyString(uid):target} back to full health");
             }
 
-
             uid.PopupMessage(args.User,
                 Loc.GetString("comp-repairable-repair",
                     ("target", uid),
-                    ("tool", args.Used)));
+                    ("tool", args.Used!)));
         }
 
         public async void Repair(EntityUid uid, RepairableComponent component, InteractUsingEvent args)
         {
+            if (args.Handled)
+                return;
+
             // Only try repair the target if it is damaged
             if (!EntityManager.TryGetComponent(uid, out DamageableComponent? damageable) || damageable.TotalDamage == 0)
                 return;
@@ -57,25 +64,8 @@ namespace Content.Server.Repairable
             if (args.User == args.Target)
                 delay *= component.SelfRepairPenalty;
 
-            var toolEvData = new ToolEventData(new RepairFinishedEvent(args.User, args.Used), component.FuelCost, targetEntity:uid);
-
             // Can the tool actually repair this, does it have enough fuel?
-            if (!_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, toolEvData, component.FuelCost))
-                return;
-
-            args.Handled = true;
-        }
-    }
-
-    public sealed class RepairFinishedEvent : EntityEventArgs
-    {
-        public EntityUid User;
-        public EntityUid Used;
-
-        public RepairFinishedEvent(EntityUid user, EntityUid used)
-        {
-            User = user;
-            Used = used;
+            args.Handled = !_toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent(), component.FuelCost);
         }
     }
 }
index b67adfc203d3c8a1d8d46fc7b81b9a9ca42f3524..6572eca330b182b89494237ed96481c9a6924fb6 100644 (file)
@@ -1,4 +1,4 @@
-using System.Threading;
+using Content.Shared.DoAfter;
 
 namespace Content.Server.Resist;
 
@@ -11,8 +11,8 @@ public sealed class CanEscapeInventoryComponent : Component
     [DataField("baseResistTime")]
     public float BaseResistTime = 5f;
 
-    [DataField("isEscaping")]
-    public bool IsEscaping;
+    public bool IsEscaping => DoAfter != null;
 
-    public CancellationTokenSource? CancelToken;
+    [DataField("doAfter")]
+    public DoAfterId? DoAfter;
 }
index 6230b16fc6ab4e5e9ed00f5f2a3d578894f3a311..43012d8b7e48f8faceac39e8825b9c07089f6f8f 100644 (file)
@@ -1,5 +1,3 @@
-using System.Threading;
-using Content.Server.DoAfter;
 using Content.Server.Contests;
 using Robust.Shared.Containers;
 using Content.Server.Popups;
@@ -10,12 +8,13 @@ using Content.Shared.ActionBlocker;
 using Content.Shared.DoAfter;
 using Content.Shared.Movement.Events;
 using Content.Shared.Interaction.Events;
+using Content.Shared.Resist;
 
 namespace Content.Server.Resist;
 
 public sealed class EscapeInventorySystem : EntitySystem
 {
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
     [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
@@ -32,7 +31,7 @@ public sealed class EscapeInventorySystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<CanEscapeInventoryComponent, MoveInputEvent>(OnRelayMovement);
-        SubscribeLocalEvent<CanEscapeInventoryComponent, DoAfterEvent<EscapeInventoryEvent>>(OnEscape);
+        SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeInventoryEvent>(OnEscape);
         SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
     }
 
@@ -69,50 +68,37 @@ public sealed class EscapeInventorySystem : EntitySystem
         if (component.IsEscaping)
             return;
 
-        component.CancelToken = new CancellationTokenSource();
-        component.IsEscaping = true;
-        var escapeEvent = new EscapeInventoryEvent();
-        var doAfterEventArgs = new DoAfterEventArgs(user, component.BaseResistTime * multiplier, cancelToken: component.CancelToken.Token, target:container)
+        var doAfterEventArgs = new DoAfterArgs(user, component.BaseResistTime * multiplier, new EscapeInventoryEvent(), user, target: container)
         {
             BreakOnTargetMove = false,
             BreakOnUserMove = true,
             BreakOnDamage = true,
-            BreakOnStun = true,
             NeedHand = false
         };
 
+        if (!_doAfterSystem.TryStartDoAfter(doAfterEventArgs, out component.DoAfter))
+            return;
+
+        Dirty(component);
         _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, escapeEvent);
     }
 
-    private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, DoAfterEvent<EscapeInventoryEvent> args)
+    private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, EscapeInventoryEvent args)
     {
-        if (args.Cancelled)
-        {
-            component.CancelToken = null;
-            component.IsEscaping = false;
-            return;
-        }
+        component.DoAfter = null;
+        Dirty(component);
 
-        if (args.Handled)
+        if (args.Handled || args.Cancelled)
             return;
 
         Transform(uid).AttachParentToContainerOrGrid(EntityManager);
-
-        component.CancelToken = null;
-        component.IsEscaping = false;
         args.Handled = true;
     }
 
     private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, DroppedEvent args)
     {
-        component.CancelToken?.Cancel();
-        component.CancelToken = null;
-    }
-
-    private sealed class EscapeInventoryEvent : EntityEventArgs
-    {
-
+        if (component.DoAfter != null)
+            _doAfterSystem.Cancel(component.DoAfter);
     }
 }
index 7cb2149100d002b38466af2a4ae2df5538cf96ef..52a4683abf110ce9344575b79fbe8df1c4de69a3 100644 (file)
@@ -17,6 +17,4 @@ public sealed class ResistLockerComponent : Component
     /// </summary>
     [ViewVariables]
     public bool IsResisting = false;
-
-    public CancellationTokenSource? CancelToken;
 }
index 78da7c012a8b384b320eebe989066fcf822da8b2..4b433073ca8ac6fbc6446a601e2a571f3f877f66 100644 (file)
@@ -1,5 +1,3 @@
-using System.Threading;
-using Content.Server.DoAfter;
 using Content.Server.Popups;
 using Content.Server.Storage.Components;
 using Content.Server.Storage.EntitySystems;
@@ -7,13 +5,13 @@ using Content.Shared.DoAfter;
 using Content.Shared.Lock;
 using Content.Shared.Movement.Events;
 using Content.Shared.Popups;
-using Robust.Shared.Containers;
+using Content.Shared.Resist;
 
 namespace Content.Server.Resist;
 
 public sealed class ResistLockerSystem : EntitySystem
 {
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly LockSystem _lockSystem = default!;
     [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
@@ -22,8 +20,7 @@ public sealed class ResistLockerSystem : EntitySystem
     {
         base.Initialize();
         SubscribeLocalEvent<ResistLockerComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
-        SubscribeLocalEvent<ResistLockerComponent, DoAfterEvent<LockerDoAfterData>>(OnDoAfter);
-        SubscribeLocalEvent<ResistLockerComponent, EntRemovedFromContainerMessage>(OnRemoved);
+        SubscribeLocalEvent<ResistLockerComponent, ResistLockerDoAfterEvent>(OnDoAfter);
     }
 
     private void OnRelayMovement(EntityUid uid, ResistLockerComponent component, ref ContainerRelayMovementEntityEvent args)
@@ -45,34 +42,24 @@ public sealed class ResistLockerSystem : EntitySystem
         if (!Resolve(target, ref storageComponent, ref resistLockerComponent))
             return;
 
-        resistLockerComponent.CancelToken = new CancellationTokenSource();
-
-        var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, cancelToken:resistLockerComponent.CancelToken.Token, target:target)
+        var doAfterEventArgs = new DoAfterArgs(user, resistLockerComponent.ResistTime, new ResistLockerDoAfterEvent(), target, target: target)
         {
             BreakOnTargetMove = false,
             BreakOnUserMove = true,
             BreakOnDamage = true,
-            BreakOnStun = true,
             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, new LockerDoAfterData());
-    }
-
-    private void OnRemoved(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage args)
-    {
-        component.CancelToken?.Cancel();
-        component.CancelToken = null;
+        _doAfterSystem.TryStartDoAfter(doAfterEventArgs);
     }
 
-    private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent<LockerDoAfterData> args)
+    private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent args)
     {
         if (args.Cancelled)
         {
             component.IsResisting = false;
-            component.CancelToken = null;
             _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), args.Args.User, args.Args.User, PopupType.Medium);
             return;
         }
@@ -93,11 +80,6 @@ public sealed class ResistLockerSystem : EntitySystem
             _entityStorage.TryOpenStorage(args.Args.User, uid);
         }
 
-        component.CancelToken = null;
         args.Handled = true;
     }
-
-    private struct LockerDoAfterData
-    {
-    }
 }
index 7c3b1508e7288d82dcdb6927aaa2d32c66582d7f..44313457f8351d76c1aff55d68f0f8d568cb3d09 100644 (file)
@@ -26,6 +26,7 @@ using Content.Shared.Mobs;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Mobs.Systems;
 using Content.Shared.Revenant.Components;
+using Content.Shared.Revenant.EntitySystems;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Utility;
 
@@ -46,8 +47,8 @@ public sealed partial class RevenantSystem
     private void InitializeAbilities()
     {
         SubscribeLocalEvent<RevenantComponent, InteractNoHandEvent>(OnInteract);
-        SubscribeLocalEvent<RevenantComponent, DoAfterEvent<SoulEvent>>(OnSoulSearch);
-        SubscribeLocalEvent<RevenantComponent, DoAfterEvent<HarvestEvent>>(OnHarvest);
+        SubscribeLocalEvent<RevenantComponent, SoulEvent>(OnSoulSearch);
+        SubscribeLocalEvent<RevenantComponent, HarvestEvent>(OnHarvest);
 
         SubscribeLocalEvent<RevenantComponent, RevenantDefileActionEvent>(OnDefileAction);
         SubscribeLocalEvent<RevenantComponent, RevenantOverloadLightsActionEvent>(OnOverloadLightsAction);
@@ -84,17 +85,19 @@ public sealed partial class RevenantSystem
 
     private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant)
     {
-        _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium);
-        var soulSearchEvent = new SoulEvent();
-        var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, target:target)
+        var searchDoAfter = new DoAfterArgs(uid, revenant.SoulSearchDuration, new SoulEvent(), uid, target: target)
         {
             BreakOnUserMove = true,
             DistanceThreshold = 2
         };
-        _doAfter.DoAfter(searchDoAfter, soulSearchEvent);
+
+        if (!_doAfter.TryStartDoAfter(searchDoAfter))
+            return;
+
+        _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium);
     }
 
-    private void OnSoulSearch(EntityUid uid, RevenantComponent component, DoAfterEvent<SoulEvent> args)
+    private void OnSoulSearch(EntityUid uid, RevenantComponent component, SoulEvent args)
     {
         if (args.Handled || args.Cancelled)
             return;
@@ -135,25 +138,25 @@ public sealed partial class RevenantSystem
             return;
         }
 
-        var harvestEvent = new HarvestEvent();
-
-        var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, target:target)
+        var doAfter = new DoAfterArgs(uid, revenant.HarvestDebuffs.X, new HarvestEvent(), uid, target: target)
         {
             DistanceThreshold = 2,
             BreakOnUserMove = true,
-            NeedHand = false
+            RequireCanInteract = false, // stuns itself
         };
 
+        if (!_doAfter.TryStartDoAfter(doAfter))
+            return;
+
         _appearance.SetData(uid, RevenantVisuals.Harvesting, true);
 
         _popup.PopupEntity(Loc.GetString("revenant-soul-begin-harvest", ("target", target)),
             target, PopupType.Large);
 
         TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs);
-        _doAfter.DoAfter(doAfter, harvestEvent);
     }
 
-    private void OnHarvest(EntityUid uid, RevenantComponent component, DoAfterEvent<HarvestEvent> args)
+    private void OnHarvest(EntityUid uid, RevenantComponent component, HarvestEvent args)
     {
         if (args.Cancelled)
         {
@@ -327,14 +330,4 @@ public sealed partial class RevenantSystem
             _emag.DoEmagEffect(ent, ent); //it emags itself. spooky.
         }
     }
-
-    private sealed class SoulEvent : EntityEventArgs
-    {
-
-    }
-
-    private sealed class HarvestEvent : EntityEventArgs
-    {
-
-    }
 }
index b8d78f5803b6d57aed401a84c8e10c9fd2a910d4..968da8c269a3d300a197dd7c0c6b1b4b94f5f3f1 100644 (file)
@@ -3,7 +3,6 @@ using Content.Shared.Popups;
 using Content.Shared.Alert;
 using Content.Shared.Damage;
 using Content.Shared.Interaction;
-using Content.Server.DoAfter;
 using Content.Server.GameTicking;
 using Content.Shared.Stunnable;
 using Content.Shared.Revenant;
@@ -17,6 +16,7 @@ using Content.Shared.Actions.ActionTypes;
 using Content.Shared.Tag;
 using Content.Server.Store.Components;
 using Content.Server.Store.Systems;
+using Content.Shared.DoAfter;
 using Content.Shared.FixedPoint;
 using Content.Shared.Maps;
 using Content.Shared.Mobs.Systems;
@@ -32,7 +32,7 @@ public sealed partial class RevenantSystem : EntitySystem
     [Dependency] private readonly ActionsSystem _action = default!;
     [Dependency] private readonly AlertsSystem _alerts = default!;
     [Dependency] private readonly DamageableSystem _damage = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
     [Dependency] private readonly MobStateSystem _mobState = default!;
     [Dependency] private readonly PhysicsSystem _physics = default!;
index 9437ebbd004e30cc9235cbf62fd99b757c7dccbf..fb9c5fab370410cfc393aa69e79b1ee2e220242d 100644 (file)
@@ -1,22 +1,20 @@
-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;
 using Content.Shared.Sticky.Components;
 using Content.Shared.Verbs;
-using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
-using Robust.Shared.Player;
 using Robust.Shared.Utility;
 
 namespace Content.Server.Sticky.Systems;
 
 public sealed class StickySystem : EntitySystem
 {
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
     [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
@@ -28,7 +26,7 @@ public sealed class StickySystem : EntitySystem
     public override void Initialize()
     {
         base.Initialize();
-        SubscribeLocalEvent<StickyComponent, DoAfterEvent>(OnStickSuccessful);
+        SubscribeLocalEvent<StickyComponent, StickyDoAfterEvent>(OnStickFinished);
         SubscribeLocalEvent<StickyComponent, AfterInteractEvent>(OnAfterInteract);
         SubscribeLocalEvent<StickyComponent, GetVerbsEvent<Verb>>(AddUnstickVerb);
     }
@@ -88,9 +86,8 @@ public sealed class StickySystem : EntitySystem
             component.Stick = true;
 
             // start sticking object to target
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: target, used: uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, delay, new StickyDoAfterEvent(), uid, target: target, used: uid)
             {
-                BreakOnStun = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 NeedHand = true
@@ -105,14 +102,13 @@ public sealed class StickySystem : EntitySystem
         return true;
     }
 
-    private void OnStickSuccessful(EntityUid uid, StickyComponent component, DoAfterEvent args)
+    private void OnStickFinished(EntityUid uid, StickyComponent component, DoAfterEvent args)
     {
         if (args.Handled || args.Cancelled || args.Args.Target == null)
             return;
 
         if (component.Stick)
             StickToEntity(uid, args.Args.Target.Value, args.Args.User, component);
-
         else
             UnstickFromEntity(uid, args.Args.User, component);
 
@@ -137,9 +133,8 @@ public sealed class StickySystem : EntitySystem
             component.Stick = false;
 
             // start unsticking object
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(user, delay, target: uid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(user, delay, new StickyDoAfterEvent(), uid, target: uid)
             {
-                BreakOnStun = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 NeedHand = true
index 8c2e06cf48a9709d79e288dcec0eb36da95b7cdf..5050b6dd013763f397c1e49e9526a53cf8a4ee85 100644 (file)
@@ -1,5 +1,4 @@
 using System.Linq;
-using Content.Server.DoAfter;
 using Content.Server.Explosion.EntitySystems;
 using Content.Server.Mind.Components;
 using Content.Server.Resist;
@@ -11,6 +10,7 @@ using Content.Shared.Coordinates;
 using Content.Shared.DoAfter;
 using Content.Shared.Lock;
 using Content.Shared.Storage.Components;
+using Content.Shared.Storage.EntitySystems;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 
@@ -23,7 +23,7 @@ public sealed class BluespaceLockerSystem : EntitySystem
     [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
     [Dependency] private readonly WeldableSystem _weldableSystem = default!;
     [Dependency] private readonly LockSystem _lockSystem = default!;
-    [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
 
     public override void Initialize()
@@ -33,7 +33,7 @@ public sealed class BluespaceLockerSystem : EntitySystem
         SubscribeLocalEvent<BluespaceLockerComponent, ComponentStartup>(OnStartup);
         SubscribeLocalEvent<BluespaceLockerComponent, StorageBeforeOpenEvent>(PreOpen);
         SubscribeLocalEvent<BluespaceLockerComponent, StorageAfterCloseEvent>(PostClose);
-        SubscribeLocalEvent<BluespaceLockerComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<BluespaceLockerComponent, BluespaceLockerDoAfterEvent>(OnDoAfter);
     }
 
     private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args)
@@ -284,7 +284,7 @@ public sealed class BluespaceLockerSystem : EntitySystem
         {
             EnsureComp<DoAfterComponent>(uid);
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.BehaviorProperties.Delay));
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.BehaviorProperties.Delay, new BluespaceLockerDoAfterEvent(), uid));
             return;
         }
 
index a59e439bec2c4609ca6f411dacc69415968feea8..69a27425429db8aefcddd9dbdb84a188f6218790 100644 (file)
@@ -1,11 +1,10 @@
-using System.Threading;
 using Content.Shared.Interaction;
 using Content.Server.Storage.Components;
 using Content.Shared.Storage.Components;
 using Content.Shared.Verbs;
 using Content.Server.Disposal.Unit.Components;
 using Content.Server.Disposal.Unit.EntitySystems;
-using Content.Server.DoAfter;
+using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
 using Content.Shared.DoAfter;
 using Content.Shared.Placeable;
 using Content.Shared.Storage;
@@ -17,7 +16,7 @@ namespace Content.Server.Storage.EntitySystems
 {
     public sealed class DumpableSystem : EntitySystem
     {
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly SharedContainerSystem _container = default!;
@@ -28,16 +27,19 @@ namespace Content.Server.Storage.EntitySystems
             SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract, after: new[]{ typeof(StorageSystem) });
             SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<AlternativeVerb>>(AddDumpVerb);
             SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<UtilityVerb>>(AddUtilityVerbs);
-            SubscribeLocalEvent<DumpableComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<DumpableComponent, DumpableDoAfterEvent>(OnDoAfter);
         }
 
         private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args)
         {
-            if (!args.CanReach)
+            if (!args.CanReach || args.Handled)
                 return;
 
-            if (HasComp<DisposalUnitComponent>(args.Target) || HasComp<PlaceableSurfaceComponent>(args.Target))
-                StartDoAfter(uid, args.Target.Value, args.User, component);
+            if (!HasComp<DisposalUnitComponent>(args.Target) && !HasComp<PlaceableSurfaceComponent>(args.Target))
+                return;
+
+            StartDoAfter(uid, args.Target.Value, args.User, component);
+            args.Handled = true;
         }
 
         private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<AlternativeVerb> args)
@@ -52,7 +54,7 @@ namespace Content.Server.Storage.EntitySystems
             {
                 Act = () =>
                 {
-                    StartDoAfter(uid, null, args.User, dumpable);//Had multiplier of 0.6f
+                    StartDoAfter(uid, args.Target, args.User, dumpable);//Had multiplier of 0.6f
                 },
                 Text = Loc.GetString("dump-verb-name"),
                 Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/drop.svg.192dpi.png")),
@@ -104,12 +106,10 @@ namespace Content.Server.Storage.EntitySystems
 
             float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * dumpable.Multiplier;
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, target: targetUid, used: storageUid)
+            _doAfterSystem.TryStartDoAfter(new DoAfterArgs(userUid, delay, new DumpableDoAfterEvent(), storageUid, target: targetUid, used: storageUid)
             {
-                RaiseOnTarget = false,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 NeedHand = true
             });
         }
index c8adcec54bdaf4a10060fd4fbf48468174079c99..b09e436dc19185767858d035c964100df08348b3 100644 (file)
@@ -12,8 +12,6 @@ using Robust.Shared.Containers;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
-using System.Threading;
-using Content.Server.DoAfter;
 using Content.Server.Interaction;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Item;
@@ -33,7 +31,6 @@ using Content.Shared.Containers.ItemSlots;
 using Content.Shared.DoAfter;
 using Content.Shared.Implants.Components;
 using Content.Shared.Lock;
-using Content.Shared.Movement.Events;
 using Content.Server.Ghost.Components;
 using Content.Server.Administration.Managers;
 using Content.Shared.Administration;
@@ -47,7 +44,7 @@ namespace Content.Server.Storage.EntitySystems
         [Dependency] private readonly IRobustRandom _random = default!;
         [Dependency] private readonly IAdminManager _admin = default!;
         [Dependency] private readonly ContainerSystem _containerSystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
         [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
         [Dependency] private readonly InteractionSystem _interactionSystem = default!;
@@ -81,7 +78,7 @@ namespace Content.Server.Storage.EntitySystems
             SubscribeLocalEvent<ServerStorageComponent, BoundUIClosedEvent>(OnBoundUIClosed);
             SubscribeLocalEvent<ServerStorageComponent, EntRemovedFromContainerMessage>(OnStorageItemRemoved);
 
-            SubscribeLocalEvent<ServerStorageComponent, DoAfterEvent<StorageData>>(OnDoAfter);
+            SubscribeLocalEvent<ServerStorageComponent, AreaPickupDoAfterEvent>(OnDoAfter);
 
             SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
         }
@@ -234,16 +231,14 @@ namespace Content.Server.Storage.EntitySystems
                 //If there's only one then let's be generous
                 if (validStorables.Count > 1)
                 {
-                    var storageData = new StorageData(validStorables);
-                    var doAfterArgs = new DoAfterEventArgs(args.User, 0.2f * validStorables.Count, target: uid)
+                    var doAfterArgs = new DoAfterArgs(args.User, 0.2f * validStorables.Count, new AreaPickupDoAfterEvent(validStorables), uid, target: uid)
                     {
-                        BreakOnStun = true,
                         BreakOnDamage = true,
                         BreakOnUserMove = true,
                         NeedHand = true
                     };
 
-                    _doAfterSystem.DoAfter(doAfterArgs, storageData);
+                    _doAfterSystem.TryStartDoAfter(doAfterArgs);
                 }
 
                 return;
@@ -278,7 +273,7 @@ namespace Content.Server.Storage.EntitySystems
             }
         }
 
-        private void OnDoAfter(EntityUid uid, ServerStorageComponent component, DoAfterEvent<StorageData> args)
+        private void OnDoAfter(EntityUid uid, ServerStorageComponent component, AreaPickupDoAfterEvent args)
         {
             if (args.Handled || args.Cancelled)
                 return;
@@ -289,7 +284,7 @@ namespace Content.Server.Storage.EntitySystems
             var xformQuery = GetEntityQuery<TransformComponent>();
             xformQuery.TryGetComponent(uid, out var xform);
 
-            foreach (var entity in args.AdditionalData.ValidStorables)
+            foreach (var entity in args.Entities)
             {
                 // Check again, situation may have changed for some entities, but we'll still pick up any that are valid
                 if (_containerSystem.IsEntityInContainer(entity)
@@ -667,10 +662,5 @@ namespace Content.Server.Storage.EntitySystems
 
             _popupSystem.PopupEntity(Loc.GetString(message), player, player);
         }
-
-        private record struct StorageData(List<EntityUid> validStorables)
-        {
-            public List<EntityUid> ValidStorables = validStorables;
-        }
     }
 }
index 282e197d83e52a6abeff813aa780856164e95c83..b0c9322ba7ba7004abfa82c85f27b3283891c1a3 100644 (file)
@@ -1,5 +1,4 @@
 using System.Linq;
-using Content.Server.DoAfter;
 using Content.Server.Ensnaring;
 using Content.Server.Hands.Components;
 using Content.Shared.CombatMode;
@@ -12,7 +11,6 @@ using Content.Shared.Popups;
 using Content.Shared.Strip.Components;
 using Content.Shared.Verbs;
 using Robust.Server.GameObjects;
-using System.Threading;
 using Content.Server.Administration.Logs;
 using Content.Shared.Cuffs;
 using Content.Shared.Cuffs.Components;
@@ -30,8 +28,8 @@ namespace Content.Server.Strip
         [Dependency] private readonly SharedCuffableSystem _cuffable = default!;
         [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
         [Dependency] private readonly InventorySystem _inventorySystem = default!;
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
-        [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+        [Dependency] private readonly SharedPopupSystem _popup = default!;
         [Dependency] private readonly EnsnareableSystem _ensnaring = default!;
         [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
@@ -61,12 +59,12 @@ namespace Content.Server.Strip
                 if (!TryComp<EnsnaringComponent>(entity, out var ensnaring))
                     continue;
 
-                _ensnaring.TryFree(uid, entity, ensnaring, user);
+                _ensnaring.TryFree(uid, user, entity, ensnaring);
                 return;
             }
         }
 
-        private void OnStripButtonPressed(EntityUid uid, StrippableComponent component, StrippingSlotButtonPressed args)
+        private void OnStripButtonPressed(EntityUid target, StrippableComponent component, StrippingSlotButtonPressed args)
         {
             if (args.Session.AttachedEntity is not {Valid: true} user ||
                 !TryComp<HandsComponent>(user, out var userHands))
@@ -74,19 +72,19 @@ namespace Content.Server.Strip
 
             if (args.IsHand)
             {
-                StripHand(uid, user, args.Slot, component,  userHands);
+                StripHand(target, user, args.Slot, component,  userHands);
                 return;
             }
 
-            if (!TryComp<InventoryComponent>(component.Owner, out var inventory))
+            if (!TryComp<InventoryComponent>(target, out var inventory))
                 return;
 
-            var hasEnt = _inventorySystem.TryGetSlotEntity(component.Owner, args.Slot, out _, inventory);
+            var hasEnt = _inventorySystem.TryGetSlotEntity(target, args.Slot, out var held, inventory);
 
             if (userHands.ActiveHandEntity != null && !hasEnt)
-                PlaceActiveHandItemInInventory(user, args.Slot, component);
+                PlaceActiveHandItemInInventory(user, target, userHands.ActiveHandEntity.Value, args.Slot, component);
             else if (userHands.ActiveHandEntity == null && hasEnt)
-                TakeItemFromInventory(user, args.Slot, component);
+                TakeItemFromInventory(user, target, held!.Value, args.Slot, component);
         }
 
         private void StripHand(EntityUid target, EntityUid user, string handId, StrippableComponent component, HandsComponent userHands)
@@ -104,10 +102,10 @@ namespace Content.Server.Strip
                 return;
             }
 
-            if (hand.IsEmpty && userHands.ActiveHandEntity != null)
-                PlaceActiveHandItemInHands(user, handId, component);
-            else if (!hand.IsEmpty && userHands.ActiveHandEntity == null)
-                TakeItemFromHands(user, handId, component);
+            if (userHands.ActiveHandEntity != null && hand.HeldEntity == null)
+                PlaceActiveHandItemInHands(user, target, userHands.ActiveHandEntity.Value, handId, component);
+            else if (userHands.ActiveHandEntity == null && hand.HeldEntity != null)
+                TakeItemFromHands(user,target, hand.HeldEntity.Value, handId, component);
         }
 
         public override void StartOpeningStripper(EntityUid user, StrippableComponent component, bool openInCombat = false)
@@ -166,287 +164,291 @@ namespace Content.Server.Strip
             if (args.Target == args.User)
                 return;
 
-            if (!HasComp<ActorComponent>(args.User))
+            if (!TryComp<ActorComponent>(args.User, out var actor))
                 return;
 
-            args.Handled = true;
             StartOpeningStripper(args.User, component);
         }
 
         /// <summary>
         ///     Places item in user's active hand to an inventory slot.
         /// </summary>
-        private async void PlaceActiveHandItemInInventory(EntityUid user, string slot, StrippableComponent component)
+        private async void PlaceActiveHandItemInInventory(
+            EntityUid user,
+            EntityUid target,
+            EntityUid held,
+            string slot,
+            StrippableComponent component)
         {
             var userHands = Comp<HandsComponent>(user);
 
             bool Check()
             {
-                if (userHands.ActiveHand?.HeldEntity is not { } held)
-                {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything"));
+                if (userHands.ActiveHandEntity != held)
                     return false;
-                }
 
-                if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand))
+                if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand!))
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop"));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user);
                     return false;
                 }
 
-                if (!_inventorySystem.HasSlot(component.Owner, slot))
-                    return false;
-
-                if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _))
+                if (_inventorySystem.TryGetSlotEntity(target, slot, out _))
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", component.Owner)));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", target)), user);
                     return false;
                 }
 
-                if (!_inventorySystem.CanEquip(user, component.Owner, held, slot, out _))
+                if (!_inventorySystem.CanEquip(user, target, held, slot, out _))
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-equip-message",("owner", component.Owner)));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-cannot-equip-message",("owner", target)), user);
                     return false;
                 }
 
                 return true;
             }
 
-            if (!_inventorySystem.TryGetSlot(component.Owner, slot, out var slotDef))
+            if (!_inventorySystem.TryGetSlot(target, slot, out var slotDef))
             {
-                Logger.Error($"{ToPrettyString(user)} attempted to place an item in a non-existent inventory slot ({slot}) on {ToPrettyString(component.Owner)}");
+                Logger.Error($"{ToPrettyString(user)} attempted to place an item in a non-existent inventory slot ({slot}) on {ToPrettyString(target)}");
                 return;
             }
 
-            var (time, stealth) = GetStripTimeModifiers(user, component.Owner, slotDef.StripTime);
+            var userEv = new BeforeStripEvent(slotDef.StripTime);
+            RaiseLocalEvent(user, userEv);
+            var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+            RaiseLocalEvent(target, ev);
 
-            var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+            var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: held)
             {
                 ExtraCheck = Check,
-                BreakOnStun = true,
+                AttemptFrequency = AttemptFrequency.EveryTick,
                 BreakOnDamage = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 NeedHand = true,
+                DuplicateCondition = DuplicateConditions.SameTool // Block any other DoAfters featuring this same entity.
             };
 
-            if (!stealth && Check() && userHands.ActiveHandEntity != null)
+            if (!ev.Stealth && Check() && userHands.ActiveHandEntity != null)
             {
                 var message = Loc.GetString("strippable-component-alert-owner-insert",
                     ("user", Identity.Entity(user, EntityManager)), ("item", userHands.ActiveHandEntity));
-                _popupSystem.PopupEntity(message, component.Owner, component.Owner, PopupType.Large);
+                _popup.PopupEntity(message, target, target, PopupType.Large);
             }
 
-            var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+            var result = await _doAfter.WaitDoAfter(doAfterArgs);
             if (result != DoAfterStatus.Finished) return;
 
-            if (userHands.ActiveHand?.HeldEntity is { } held
-                && _handsSystem.TryDrop(user, userHands.ActiveHand, handsComp: userHands))
+            DebugTools.Assert(userHands.ActiveHand?.HeldEntity == held);
+
+            if (_handsSystem.TryDrop(user, handsComp: userHands))
             {
-                _inventorySystem.TryEquip(user, component.Owner, held, slot);
+                _inventorySystem.TryEquip(user, target, held, slot);
 
-                _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s {slot} slot");
+                _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");
             }
         }
 
         /// <summary>
         ///     Places item in user's active hand in one of the entity's hands.
         /// </summary>
-        private async void PlaceActiveHandItemInHands(EntityUid user, string handName, StrippableComponent component)
+        private async void PlaceActiveHandItemInHands(
+            EntityUid user,
+            EntityUid target,
+            EntityUid held,
+            string handName,
+            StrippableComponent component)
         {
-            var hands = Comp<HandsComponent>(component.Owner);
+            var hands = Comp<HandsComponent>(target);
             var userHands = Comp<HandsComponent>(user);
 
             bool Check()
             {
-                if (userHands.ActiveHandEntity == null)
-                {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything"));
+                if (userHands.ActiveHandEntity != held)
                     return false;
-                }
 
                 if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand!))
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop"));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user);
                     return false;
                 }
 
                 if (!hands.Hands.TryGetValue(handName, out var hand)
-                    || !_handsSystem.CanPickupToHand(component.Owner, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands))
+                    || !_handsSystem.CanPickupToHand(target, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands))
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-put-message",("owner", component.Owner)));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-cannot-put-message",("owner", target)), user);
                     return false;
                 }
 
                 return true;
             }
 
-            var (time, stealth) = GetStripTimeModifiers(user, component.Owner, component.HandStripDelay);
+            var userEv = new BeforeStripEvent(component.HandStripDelay);
+            RaiseLocalEvent(user, userEv);
+            var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+            RaiseLocalEvent(target, ev);
 
-            var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+            var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: held)
             {
                 ExtraCheck = Check,
-                BreakOnStun = true,
+                AttemptFrequency = AttemptFrequency.EveryTick,
                 BreakOnDamage = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
                 NeedHand = true,
+                DuplicateCondition = DuplicateConditions.SameTool
             };
 
-            if (!stealth
-                && Check()
-                && userHands.Hands.TryGetValue(handName, out var handSlot)
-                && handSlot.HeldEntity != null)
-            {
-                    _popupSystem.PopupEntity(
-                        Loc.GetString("strippable-component-alert-owner-insert",
-                        ("user", Identity.Entity(user, EntityManager)),
-                        ("item", handSlot.HeldEntity)),
-                        component.Owner, component.Owner, PopupType.Large);
-            }
-
-            var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+            var result = await _doAfter.WaitDoAfter(doAfterArgs);
             if (result != DoAfterStatus.Finished) return;
 
-            if (userHands.ActiveHandEntity is not { } held)
-                return;
-
             _handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: userHands);
-            _handsSystem.TryPickup(component.Owner, held, handName, checkActionBlocker: false, animateUser: true, animate: !stealth, handsComp: hands);
-            _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s hands");
+            _handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: true, handsComp: hands);
+            _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
             // hand update will trigger strippable update
         }
 
         /// <summary>
         ///     Takes an item from the inventory and places it in the user's active hand.
         /// </summary>
-        private async void TakeItemFromInventory(EntityUid user, string slot, StrippableComponent component)
+        private async void TakeItemFromInventory(
+            EntityUid user,
+            EntityUid target,
+            EntityUid item,
+            string slot,
+            StrippableComponent component)
         {
             bool Check()
             {
-                if (!_inventorySystem.HasSlot(component.Owner, slot))
-                    return false;
-
-                if (!_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _))
+                if (!_inventorySystem.TryGetSlotEntity(target, slot, out var ent) && ent == item)
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", component.Owner)));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", target)), user);
                     return false;
                 }
 
-                if (!_inventorySystem.CanUnequip(user, component.Owner, slot, out var reason))
+                if (!_inventorySystem.CanUnequip(user, target, slot, out var reason))
                 {
-                    user.PopupMessageCursor(reason);
+                    _popup.PopupCursor(reason, user);
                     return false;
                 }
 
                 return true;
             }
 
-            if (!_inventorySystem.TryGetSlot(component.Owner, slot, out var slotDef))
+            if (!_inventorySystem.TryGetSlot(target, slot, out var slotDef))
             {
-                Logger.Error($"{ToPrettyString(user)} attempted to take an item from a non-existent inventory slot ({slot}) on {ToPrettyString(component.Owner)}");
+                Logger.Error($"{ToPrettyString(user)} attempted to take an item from a non-existent inventory slot ({slot}) on {ToPrettyString(target)}");
                 return;
             }
 
-            var (time, stealth) = GetStripTimeModifiers(user, component.Owner, slotDef.StripTime);
+            var userEv = new BeforeStripEvent(slotDef.StripTime);
+            RaiseLocalEvent(user, userEv);
+            var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+            RaiseLocalEvent(target, ev);
 
-            var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+            var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: item)
             {
                 ExtraCheck = Check,
-                BreakOnStun = true,
+                AttemptFrequency = AttemptFrequency.EveryTick,
                 BreakOnDamage = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
+                NeedHand = true,
+                BreakOnHandChange = false, // allow simultaneously removing multiple items.
+                DuplicateCondition = DuplicateConditions.SameTool
             };
 
-            if (!stealth && Check())
+            if (!ev.Stealth && Check())
             {
                 if (slotDef.StripHidden)
                 {
-                    _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), component.Owner,
-                        component.Owner, PopupType.Large);
+                    _popup.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target,
+                        target, PopupType.Large);
                 }
                 else if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out var slotItem))
                 {
-                    _popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", slotItem)), component.Owner,
-                        component.Owner, PopupType.Large);
+                    _popup.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", slotItem)), target,
+                        target, PopupType.Large);
                 }
             }
 
-            var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+            var result = await _doAfter.WaitDoAfter(doAfterArgs);
             if (result != DoAfterStatus.Finished) return;
 
-            if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out var item) && _inventorySystem.TryUnequip(user, component.Owner, slot))
-            {
-                // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping
-                RaiseLocalEvent(item.Value, new DroppedEvent(user), true);
+            if (!_inventorySystem.TryUnequip(user, component.Owner, slot))
+                return;
+
+            // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping
+            RaiseLocalEvent(item, new DroppedEvent(user), true);
+
+            _handsSystem.PickupOrDrop(user, item);
+            _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}");
 
-                _handsSystem.PickupOrDrop(user, item.Value, animate: !stealth);
-                _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item.Value):item} from {ToPrettyString(component.Owner):target}");
-            }
         }
 
         /// <summary>
         ///     Takes an item from a hand and places it in the user's active hand.
         /// </summary>
-        private async void TakeItemFromHands(EntityUid user, string handName, StrippableComponent component)
+        private async void TakeItemFromHands(EntityUid user, EntityUid target, EntityUid item, string handName, StrippableComponent component)
         {
-            var hands = Comp<HandsComponent>(component.Owner);
+            var hands = Comp<HandsComponent>(target);
             var userHands = Comp<HandsComponent>(user);
 
             bool Check()
             {
-                if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity == null)
+                if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity != item)
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", component.Owner)));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", target)), user);
                     return false;
                 }
 
                 if (HasComp<HandVirtualItemComponent>(hand.HeldEntity))
                     return false;
 
-                if (!_handsSystem.CanDropHeld(component.Owner, hand, false))
+                if (!_handsSystem.CanDropHeld(target, hand, false))
                 {
-                    user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop-message",("owner", component.Owner)));
+                    _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message",("owner", target)), user);
                     return false;
                 }
 
                 return true;
             }
 
-            var (time, stealth) = GetStripTimeModifiers(user, component.Owner, component.HandStripDelay);
+            var userEv = new BeforeStripEvent(component.HandStripDelay);
+            RaiseLocalEvent(user, userEv);
+            var ev = new BeforeGettingStrippedEvent(userEv.Time, userEv.Stealth);
+            RaiseLocalEvent(target, ev);
 
-            var doAfterArgs = new DoAfterEventArgs(user, time, CancellationToken.None, component.Owner)
+            var doAfterArgs = new DoAfterArgs(user, ev.Time, new AwaitedDoAfterEvent(), null, target: target, used: item)
             {
                 ExtraCheck = Check,
-                BreakOnStun = true,
+                AttemptFrequency = AttemptFrequency.EveryTick,
                 BreakOnDamage = true,
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
+                NeedHand = true,
+                BreakOnHandChange = false, // allow simultaneously removing multiple items.
+                DuplicateCondition = DuplicateConditions.SameTool
             };
 
-            if (!stealth
-                && Check()
-                && hands.Hands.TryGetValue(handName, out var handSlot)
-                && handSlot.HeldEntity != null)
+            if (Check() && hands.Hands.TryGetValue(handName, out var handSlot) && handSlot.HeldEntity != null)
             {
-                _popupSystem.PopupEntity(
+                _popup.PopupEntity(
                     Loc.GetString("strippable-component-alert-owner",
-                    ("user", Identity.Entity(user, EntityManager)),
-                    ("item", handSlot.HeldEntity)),
-                    component.Owner, component.Owner);
+                    ("user", Identity.Entity(user, EntityManager)), ("item", item)),
+                    component.Owner,
+                    component.Owner);
             }
 
-            var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
+            var result = await _doAfter.WaitDoAfter(doAfterArgs);
             if (result != DoAfterStatus.Finished) return;
 
-            if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity is not { } held)
-                return;
-
-            _handsSystem.TryDrop(component.Owner, hand, checkActionBlocker: false, handsComp: hands);
-            _handsSystem.PickupOrDrop(user, held, handsComp: userHands, animate: !stealth);
+            _handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: hands);
+            _handsSystem.PickupOrDrop(user, item, handsComp: userHands);
             // hand update will trigger strippable update
-            _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(held):item} from {ToPrettyString(component.Owner):target}");
+            _adminLogger.Add(LogType.Stripping, LogImpact.Medium,
+                $"{ToPrettyString(user):user} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}");
         }
     }
 }
index 98a2aff1a8fa708106bbaab27b335402982f5bb9..f3a3b986a07226acfd7a806c7d22945cd20cb14e 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
 using Content.Shared.DoAfter;
 using Content.Shared.Database;
 using Content.Shared.Interaction.Events;
@@ -17,14 +16,14 @@ public sealed class HandTeleporterSystem : EntitySystem
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
     [Dependency] private readonly LinkedEntitySystem _link = default!;
     [Dependency] private readonly AudioSystem _audio = default!;
-    [Dependency] private readonly DoAfterSystem _doafter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doafter = default!;
 
     /// <inheritdoc/>
     public override void Initialize()
     {
         SubscribeLocalEvent<HandTeleporterComponent, UseInHandEvent>(OnUseInHand);
 
-        SubscribeLocalEvent<HandTeleporterComponent, DoAfterEvent>(OnDoAfter);
+        SubscribeLocalEvent<HandTeleporterComponent, TeleporterDoAfterEvent>(OnDoAfter);
     }
 
     private void OnDoAfter(EntityUid uid, HandTeleporterComponent component, DoAfterEvent args)
@@ -56,15 +55,14 @@ public sealed class HandTeleporterSystem : EntitySystem
             if (xform.ParentUid != xform.GridUid)
                 return;
 
-            var doafterArgs = new DoAfterEventArgs(args.User, component.PortalCreationDelay, used: uid)
+            var doafterArgs = new DoAfterArgs(args.User, component.PortalCreationDelay, new TeleporterDoAfterEvent(), uid, used: uid)
             {
                 BreakOnDamage = true,
-                BreakOnStun = true,
                 BreakOnUserMove = true,
                 MovementThreshold = 0.5f,
             };
 
-            _doafter.DoAfter(doafterArgs);
+            _doafter.TryStartDoAfter(doafterArgs);
         }
     }
 
index b98c5c9d38f9173b9333dc1bbe0c013fe0bd4e2a..03591433fb81ddaf5c4942973e84c8da47f3cf52 100644 (file)
@@ -6,6 +6,7 @@ using Content.Server.Storage.EntitySystems;
 using Content.Shared.Body.Components;
 using Content.Shared.Body.Part;
 using Content.Shared.Buckle.Components;
+using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
@@ -39,8 +40,7 @@ namespace Content.Server.Toilet
             SubscribeLocalEvent<ToiletComponent, InteractHandEvent>(OnInteractHand, new []{typeof(BuckleSystem)});
             SubscribeLocalEvent<ToiletComponent, ExaminedEvent>(OnExamine);
             SubscribeLocalEvent<ToiletComponent, SuicideEvent>(OnSuicide);
-            SubscribeLocalEvent<ToiletPryFinished>(OnToiletPried);
-            SubscribeLocalEvent<ToiletPryInterrupted>(OnToiletInterrupt);
+            SubscribeLocalEvent<ToiletComponent, ToiletPryDoAfterEvent>(OnToiletPried);
         }
 
         private void OnSuicide(EntityUid uid, ToiletComponent component, SuicideEvent args)
@@ -94,29 +94,15 @@ namespace Content.Server.Toilet
                 return;
 
             // are player trying place or lift of cistern lid?
-            if (EntityManager.TryGetComponent(args.Used, out ToolComponent? tool)
-                && tool.Qualities.Contains(component.PryingQuality))
+            if (_toolSystem.UseTool(args.Used, args.User, uid, component.PryLidTime, component.PryingQuality, new ToiletPryDoAfterEvent()))
             {
-                // check if someone is already prying this toilet
-                if (component.IsPrying)
-                    return;
-                component.IsPrying = true;
-
-                var toolEvData = new ToolEventData(new ToiletPryFinished(uid));
-
-                // try to pry toilet cistern
-                if (!_toolSystem.UseTool(args.Used, args.User, uid, component.PryLidTime, new [] {component.PryingQuality}, toolEvData))
-                {
-                    component.IsPrying = false;
-                    return;
-                }
-
                 args.Handled = true;
             }
             // maybe player trying to hide something inside cistern?
             else if (component.LidOpen)
             {
-                args.Handled = _secretStash.TryHideItem(uid, args.User, args.Used);
+                args.Handled = true;
+                _secretStash.TryHideItem(uid, args.User, args.Used);
             }
         }
 
@@ -160,22 +146,13 @@ namespace Content.Server.Toilet
             }
         }
 
-        private void OnToiletInterrupt(ToiletPryInterrupted ev)
-        {
-            if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet))
-                return;
-
-            toilet.IsPrying = false;
-        }
-
-        private void OnToiletPried(ToiletPryFinished ev)
+        private void OnToiletPried(EntityUid uid, ToiletComponent toilet, ToiletPryDoAfterEvent args)
         {
-            if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet))
+            if (args.Cancelled)
                 return;
 
-            toilet.IsPrying = false;
             toilet.LidOpen = !toilet.LidOpen;
-            UpdateSprite(ev.Uid, toilet);
+            UpdateSprite(uid, toilet);
         }
 
         public void ToggleToiletSeat(EntityUid uid, ToiletComponent? component = null)
@@ -197,24 +174,4 @@ namespace Content.Server.Toilet
             _appearance.SetData(uid, ToiletVisuals.SeatUp, component.IsSeatUp, appearance);
         }
     }
-
-    public sealed class ToiletPryFinished : EntityEventArgs
-    {
-        public EntityUid Uid;
-
-        public ToiletPryFinished(EntityUid uid)
-        {
-            Uid = uid;
-        }
-    }
-
-    public sealed class ToiletPryInterrupted : EntityEventArgs
-    {
-        public EntityUid Uid;
-
-        public ToiletPryInterrupted(EntityUid uid)
-        {
-            Uid = uid;
-        }
-    }
 }
index b1e4e18fec9774f7eb055fa9a110427550d6ef19..bbc782a5f189d2643acfe696fb51f01c69ad172b 100644 (file)
@@ -6,9 +6,6 @@ namespace Content.Server.Tools.Components;
 [RegisterComponent]
 public sealed class LatticeCuttingComponent : Component
 {
-    [DataField("toolComponentNeeded")]
-    public bool ToolComponentNeeded = true;
-
     [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
     public string QualityNeeded = "Cutting";
 
index 9dee4fc00449677ab957ae817da05010dd51c6ce..82566e355930ce8cd0aab7e410c091f3a6a78041 100644 (file)
@@ -15,8 +15,5 @@ namespace Content.Server.Tools.Components
 
         [DataField("delay")]
         public float Delay = 1f;
-
-        [DataField("cancelToken")]
-        public CancellationTokenSource? CancelToken;
     }
 }
index b59f493e23a8fd3007429e39cb46cf937c05440f..46062692d37e4b8572d356bf6cabd33d45b7dd58 100644 (file)
@@ -47,15 +47,9 @@ public sealed class WeldableComponent : SharedWeldableComponent
     [ViewVariables(VVAccess.ReadWrite)]
     public string? WeldedExamineMessage = "weldable-component-examine-is-welded";
 
-    /// <summary>
-    ///     Whether something is currently using a welder on this so DoAfter isn't spammed.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadOnly)]
-    public bool BeingWelded;
-
     /// <summary>
     ///     Is this entity currently welded shut?
     /// </summary>
-    [ViewVariables(VVAccess.ReadOnly)]
+    [DataField("isWelded")]
     public bool IsWelded;
 }
index 780cab43689461f8e0914db7b7d42681a05452aa..c04ab840efdc1548039afdf8b1e1bda8c32b634d 100644 (file)
@@ -1,10 +1,12 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Tools.Components;
 using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.Interaction;
 using Content.Shared.Tools;
 using Content.Shared.Tools.Components;
+using Content.Shared.Tools.Systems;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Systems;
 
@@ -22,7 +24,6 @@ public sealed class WeldableSystem : EntitySystem
         base.Initialize();
         SubscribeLocalEvent<WeldableComponent, InteractUsingEvent>(OnInteractUsing);
         SubscribeLocalEvent<WeldableComponent, WeldFinishedEvent>(OnWeldFinished);
-        SubscribeLocalEvent<WeldableComponent, WeldCancelledEvent>(OnWeldCanceled);
         SubscribeLocalEvent<LayerChangeOnWeldComponent, WeldableChangedEvent>(OnWeldChanged);
         SubscribeLocalEvent<WeldableComponent, ExaminedEvent>(OnExamine);
     }
@@ -47,9 +48,7 @@ public sealed class WeldableSystem : EntitySystem
             return false;
 
         // Basic checks
-        if (!component.Weldable || component.BeingWelded)
-            return false;
-        if (!_toolSystem.HasQuality(tool, component.WeldingQuality))
+        if (!component.Weldable)
             return false;
 
         // Other component systems
@@ -69,8 +68,8 @@ public sealed class WeldableSystem : EntitySystem
         if (!CanWeld(uid, tool, user, component))
             return false;
 
-        var toolEvData = new ToolEventData(new WeldFinishedEvent(user, tool), cancelledEv: new WeldCancelledEvent(),targetEntity: uid);
-        component.BeingWelded = _toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, new[] { component.WeldingQuality }, toolEvData, fuel: component.FuelConsumption);
+        if (!_toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, component.WeldingQuality, new WeldFinishedEvent(), fuel: component.FuelConsumption))
+            return false;
 
         // Log attempt
         _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} is {(component.IsWelded ? "un" : "")}welding {ToPrettyString(uid):target} at {Transform(uid).Coordinates:targetlocation}");
@@ -80,10 +79,11 @@ public sealed class WeldableSystem : EntitySystem
 
     private void OnWeldFinished(EntityUid uid, WeldableComponent component, WeldFinishedEvent args)
     {
-        component.BeingWelded = false;
+        if (args.Cancelled || args.Used == null)
+            return;
 
         // Check if target is still valid
-        if (!CanWeld(uid, args.Tool, args.User, component))
+        if (!CanWeld(uid, args.Used.Value, args.User, component))
             return;
 
         component.IsWelded = !component.IsWelded;
@@ -95,11 +95,6 @@ public sealed class WeldableSystem : EntitySystem
         _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} {(!component.IsWelded ? "un" : "")}welded {ToPrettyString(uid):target}");
     }
 
-    private void OnWeldCanceled(EntityUid uid, WeldableComponent component, WeldCancelledEvent args)
-    {
-        component.BeingWelded = false;
-    }
-
     private void OnWeldChanged(EntityUid uid, LayerChangeOnWeldComponent component, WeldableChangedEvent args)
     {
         if (!TryComp<FixturesComponent>(uid, out var fixtures))
@@ -148,30 +143,6 @@ public sealed class WeldableSystem : EntitySystem
             return;
         component.WeldingTime = time;
     }
-
-    /// <summary>
-    ///     Raised after welding do_after has finished. It doesn't guarantee success,
-    ///     use <see cref="WeldableChangedEvent"/> to get updated status.
-    /// </summary>
-    private sealed class WeldFinishedEvent : EntityEventArgs
-    {
-        public readonly EntityUid User;
-        public readonly EntityUid Tool;
-
-        public WeldFinishedEvent(EntityUid user, EntityUid tool)
-        {
-            User = user;
-            Tool = tool;
-        }
-    }
-
-    /// <summary>
-    ///     Raised when entity welding has failed.
-    /// </summary>
-    private sealed class WeldCancelledEvent : EntityEventArgs
-    {
-
-    }
 }
 
 /// <summary>
index 4a62865561f00feb3f0cb426f682d4912827b25b..c9d08eb29adb6e2a3af7a33793a8911361e5e2ba 100644 (file)
@@ -2,6 +2,7 @@
 using Content.Server.Maps;
 using Content.Server.Tools.Components;
 using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Maps;
 using Content.Shared.Tools.Components;
@@ -22,6 +23,9 @@ public sealed partial class ToolSystem
 
     private void OnLatticeCutComplete(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCompleteEvent args)
     {
+        if (args.Cancelled)
+            return;
+
         var gridUid = args.Coordinates.GetGridUid(EntityManager);
         if (gridUid == null)
             return;
@@ -50,10 +54,6 @@ public sealed partial class ToolSystem
 
     private bool TryCut(EntityUid toolEntity, EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation)
     {
-        ToolComponent? tool = null;
-        if (component.ToolComponentNeeded && !TryComp<ToolComponent?>(toolEntity, out tool))
-            return false;
-
         if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
             return false;
 
@@ -71,24 +71,8 @@ public sealed partial class ToolSystem
             || tile.IsBlockedTurf(true))
             return false;
 
-        var toolEvData = new ToolEventData(new LatticeCuttingCompleteEvent(clickLocation, user), targetEntity: toolEntity);
-
-        if (!UseTool(toolEntity, user, null, component.Delay, new[] {component.QualityNeeded}, toolEvData, toolComponent: tool))
-            return false;
-
-        return true;
-    }
-
-    private sealed class LatticeCuttingCompleteEvent : EntityEventArgs
-    {
-        public EntityCoordinates Coordinates;
-        public EntityUid User;
-
-        public LatticeCuttingCompleteEvent(EntityCoordinates coordinates, EntityUid user)
-        {
-            Coordinates = coordinates;
-            User = user;
-        }
+        var ev = new LatticeCuttingCompleteEvent(clickLocation);
+        return UseTool(toolEntity, user, toolEntity, component.Delay, component.QualityNeeded, ev);
     }
 }
 
index ea257548d386fddca7e69a7bd3190ba893e30076..59c041010b44e4b9128e59a587dd4233349b3a89 100644 (file)
@@ -1,6 +1,7 @@
 using System.Threading;
 using Content.Server.Fluids.Components;
 using Content.Server.Tools.Components;
+using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Maps;
 using Content.Shared.Tools.Components;
@@ -18,8 +19,7 @@ public sealed partial class ToolSystem
     private void InitializeTilePrying()
     {
         SubscribeLocalEvent<TilePryingComponent, AfterInteractEvent>(OnTilePryingAfterInteract);
-        SubscribeLocalEvent<TilePryingComponent, TilePryingCompleteEvent>(OnTilePryComplete);
-        SubscribeLocalEvent<TilePryingComponent, TilePryingCancelledEvent>(OnTilePryCancelled);
+        SubscribeLocalEvent<TilePryingComponent, TilePryingDoAfterEvent>(OnTilePryComplete);
     }
 
     private void OnTilePryingAfterInteract(EntityUid uid, TilePryingComponent component, AfterInteractEvent args)
@@ -30,9 +30,11 @@ public sealed partial class ToolSystem
             args.Handled = true;
     }
 
-    private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingCompleteEvent args)
+    private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingDoAfterEvent args)
     {
-        component.CancelToken = null;
+        if (args.Cancelled)
+            return;
+
         var gridUid = args.Coordinates.GetGridUid(EntityManager);
         if (!_mapManager.TryGetGrid(gridUid, out var grid))
         {
@@ -44,14 +46,9 @@ public sealed partial class ToolSystem
         _tile.PryTile(tile);
     }
 
-    private void OnTilePryCancelled(EntityUid uid, TilePryingComponent component, TilePryingCancelledEvent args)
-    {
-        component.CancelToken = null;
-    }
-
     private bool TryPryTile(EntityUid toolEntity, EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation)
     {
-        if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded || component.CancelToken != null)
+        if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded)
             return false;
 
         if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
@@ -69,28 +66,8 @@ public sealed partial class ToolSystem
         if (!tileDef.CanCrowbar)
             return false;
 
-        component.CancelToken = new CancellationTokenSource();
-
-        var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), cancelledEv:new TilePryingCancelledEvent() ,targetEntity:toolEntity);
-
-        if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool, cancelToken: component.CancelToken))
-            return false;
-
-        return true;
-    }
-
-    private sealed class TilePryingCompleteEvent : EntityEventArgs
-    {
-        public readonly EntityCoordinates Coordinates;
-
-        public TilePryingCompleteEvent(EntityCoordinates coordinates)
-        {
-            Coordinates = coordinates;
-        }
-    }
-
-    private sealed class TilePryingCancelledEvent : EntityEventArgs
-    {
+        var ev = new TilePryingDoAfterEvent(clickLocation);
 
+        return UseTool(toolEntity, user, toolEntity, component.Delay, component.QualityNeeded, ev, toolComponent: tool);
     }
 }
index d50d4c106faa16beddb0076d2ed6575843409e78..43f43dc9d49be151109053df1f8470e036ec8429 100644 (file)
@@ -4,6 +4,7 @@ using Content.Server.Chemistry.Components.SolutionManager;
 using Content.Server.Chemistry.EntitySystems;
 using Content.Server.Tools.Components;
 using Content.Shared.Database;
+using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.FixedPoint;
 using Content.Shared.Interaction;
@@ -15,6 +16,7 @@ using Content.Shared.Weapons.Melee.Events;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
 
 namespace Content.Server.Tools
 {
@@ -38,8 +40,8 @@ namespace Content.Server.Tools
             SubscribeLocalEvent<WelderComponent, SolutionChangedEvent>(OnWelderSolutionChange);
             SubscribeLocalEvent<WelderComponent, ActivateInWorldEvent>(OnWelderActivate);
             SubscribeLocalEvent<WelderComponent, AfterInteractEvent>(OnWelderAfterInteract);
-            SubscribeLocalEvent<WelderComponent, ToolUseAttemptEvent>(OnWelderToolUseAttempt);
-            SubscribeLocalEvent<WelderComponent, ToolUseFinishAttemptEvent>(OnWelderToolUseFinishAttempt);
+            SubscribeLocalEvent<WelderComponent, DoAfterAttemptEvent<ToolDoAfterEvent>>(OnWelderToolUseAttempt);
+            SubscribeLocalEvent<WelderComponent, ToolDoAfterEvent>(OnWelderDoAfter);
             SubscribeLocalEvent<WelderComponent, ComponentShutdown>(OnWelderShutdown);
             SubscribeLocalEvent<WelderComponent, ComponentGetState>(OnWelderGetState);
             SubscribeLocalEvent<WelderComponent, MeleeHitEvent>(OnMeleeHit);
@@ -262,56 +264,36 @@ namespace Content.Server.Tools
             args.Handled = true;
         }
 
-        private void OnWelderToolUseAttempt(EntityUid uid, WelderComponent welder, ToolUseAttemptEvent args)
+        private void OnWelderToolUseAttempt(EntityUid uid, WelderComponent welder, DoAfterAttemptEvent<ToolDoAfterEvent> args)
         {
-            if (args.Cancelled)
-                return;
+            DebugTools.Assert(args.Event.Fuel > 0);
+            var user = args.DoAfter.Args.User;
 
             if (!welder.Lit)
             {
-                _popupSystem.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), uid, args.User);
+                _popupSystem.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), uid, user);
                 args.Cancel();
                 return;
             }
 
             var (fuel, _) = GetWelderFuelAndCapacity(uid, welder);
 
-            if (FixedPoint2.New(args.Fuel) > fuel)
+            if (FixedPoint2.New(args.Event.Fuel) > fuel)
             {
-                _popupSystem.PopupEntity(Loc.GetString("welder-component-cannot-weld-message"), uid, args.User);
+                _popupSystem.PopupEntity(Loc.GetString("welder-component-cannot-weld-message"), uid, user);
                 args.Cancel();
             }
         }
 
-        private void OnWelderToolUseFinishAttempt(EntityUid uid, WelderComponent welder, ToolUseFinishAttemptEvent args)
+        private void OnWelderDoAfter(EntityUid uid, WelderComponent welder, ToolDoAfterEvent args)
         {
             if (args.Cancelled)
                 return;
 
-            if (!welder.Lit)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), uid, args.User);
-                args.Cancel();
-                return;
-            }
-
-            var (fuel, _) = GetWelderFuelAndCapacity(uid, welder);
-
-            var neededFuel = FixedPoint2.New(args.Fuel);
-
-            if (neededFuel > fuel)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("welder-component-cannot-weld-message"), uid, args.User);
-                args.Cancel();
-            }
-
             if (!_solutionContainerSystem.TryGetSolution(uid, welder.FuelSolution, out var solution))
-            {
-                args.Cancel();
                 return;
-            }
 
-            solution.RemoveReagent(welder.FuelReagent, neededFuel);
+            solution.RemoveReagent(welder.FuelReagent, FixedPoint2.New(args.Fuel));
             _entityManager.Dirty(welder);
         }
 
index 39ae6739022bce2ed2577cd5a9308bd9b163f5ae..a3fa90495cd0157e6b24dbfefd1ca83133ebc77a 100644 (file)
@@ -1,6 +1,5 @@
 using System.Linq;
 using Content.Server.Cargo.Systems;
-using Content.Server.DoAfter;
 using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
@@ -16,7 +15,7 @@ namespace Content.Server.VendingMachines.Restock
     {
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
 
-        [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
         [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
         [Dependency] private readonly AudioSystem _audioSystem = default!;
         [Dependency] private readonly PricingSystem _pricingSystem = default!;
@@ -65,7 +64,7 @@ namespace Content.Server.VendingMachines.Restock
 
         private void OnAfterInteract(EntityUid uid, VendingMachineRestockComponent component, AfterInteractEvent args)
         {
-            if (args.Target == null || !args.CanReach)
+            if (args.Target == null || !args.CanReach || args.Handled)
                 return;
 
             if (!TryComp<VendingMachineComponent>(args.Target, out var machineComponent))
@@ -77,14 +76,19 @@ namespace Content.Server.VendingMachines.Restock
             if (!TryAccessMachine(uid, component, machineComponent, args.User, args.Target.Value))
                 return;
 
-            _doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, (float) component.RestockDelay.TotalSeconds, target:args.Target, used:uid)
+            args.Handled = true;
+
+            var doAfterArgs = new DoAfterArgs(args.User, (float) component.RestockDelay.TotalSeconds, new RestockDoAfterEvent(), args.Target,
+                target: args.Target, used: uid)
             {
                 BreakOnTargetMove = true,
                 BreakOnUserMove = true,
-                BreakOnStun = true,
                 BreakOnDamage = true,
                 NeedHand = true
-            });
+            };
+
+            if (!_doAfterSystem.TryStartDoAfter(doAfterArgs))
+                return;
 
             _popupSystem.PopupEntity(Loc.GetString("vending-machine-restock-start", ("this", uid), ("user", args.User), ("target", args.Target)),
                 args.User,
index 8072027f78c6642c32115216b1cc4fff0a92bf0f..0fb12fd55b9d0ffe4ba82c32a22a760810e598a3 100644 (file)
@@ -55,7 +55,7 @@ namespace Content.Server.VendingMachines
 
             SubscribeLocalEvent<VendingMachineComponent, VendingMachineSelfDispenseEvent>(OnSelfDispense);
 
-            SubscribeLocalEvent<VendingMachineComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<VendingMachineComponent, RestockDoAfterEvent>(OnDoAfter);
         }
 
         private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
index b285a972ceaef40c136179b1562d1f33112026d5..affda62f214c81ecb6d1cc8fd48c8705ef01b2a6 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.DoAfter;
 using Content.Server.Hands.Components;
 using Content.Server.Hands.Systems;
 using Content.Server.Wieldable.Components;
@@ -13,13 +12,14 @@ using Robust.Shared.Player;
 using Content.Server.Actions.Events;
 using Content.Shared.DoAfter;
 using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Wieldable;
 
 
 namespace Content.Server.Wieldable
 {
     public sealed class WieldableSystem : EntitySystem
     {
-        [Dependency] private readonly DoAfterSystem _doAfter = default!;
+        [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
         [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!;
         [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
         [Dependency] private readonly SharedItemSystem _itemSystem = default!;
@@ -31,7 +31,7 @@ namespace Content.Server.Wieldable
             base.Initialize();
 
             SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand);
-            SubscribeLocalEvent<WieldableComponent, DoAfterEvent>(OnDoAfter);
+            SubscribeLocalEvent<WieldableComponent, WieldableDoAfterEvent>(OnDoAfter);
             SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
             SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
             SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
@@ -126,15 +126,13 @@ namespace Content.Server.Wieldable
             if (ev.Cancelled)
                 return;
 
-            var doargs = new DoAfterEventArgs(user, component.WieldTime, used:used)
+            var doargs = new DoAfterArgs(user, component.WieldTime, new WieldableDoAfterEvent(), used, used: used)
             {
                 BreakOnUserMove = false,
-                BreakOnDamage = true,
-                BreakOnStun = true,
-                BreakOnTargetMove = true
+                BreakOnDamage = true
             };
 
-            _doAfter.DoAfter(doargs);
+            _doAfter.TryStartDoAfter(doargs);
         }
 
         /// <summary>
index 7a3f5f7a05f5e29fd29b78fb74955ef3784b7405..2199ee16432b3b3017d21d52de23afb9475d8c34 100644 (file)
@@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Threading;
 using Content.Server.Administration.Logs;
-using Content.Server.DoAfter;
 using Content.Server.Hands.Components;
 using Content.Server.Power.Components;
 using Content.Shared.DoAfter;
@@ -24,15 +23,14 @@ public sealed class WiresSystem : SharedWiresSystem
 {
     [Dependency] private readonly IPrototypeManager _protoMan = default!;
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
-    [Dependency] private readonly DoAfterSystem _doAfter = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = 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!;
     [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
-    private IRobustRandom _random = new RobustRandom();
+    [Dependency] private readonly IRobustRandom _random = default!;
 
     // This is where all the wire layouts are stored.
     [ViewVariables] private readonly Dictionary<string, WireLayout> _layouts = new();
@@ -48,15 +46,14 @@ public sealed class WiresSystem : SharedWiresSystem
         SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
 
         // this is a broadcast event
-        SubscribeLocalEvent<WireToolFinishedEvent>(OnToolFinished);
-        SubscribeLocalEvent<WireToolCanceledEvent>(OnToolCanceled);
+        SubscribeLocalEvent<WiresPanelComponent, WirePanelDoAfterEvent>(OnPanelDoAfter);
         SubscribeLocalEvent<WiresComponent, ComponentStartup>(OnWiresStartup);
         SubscribeLocalEvent<WiresComponent, WiresActionMessage>(OnWiresActionMessage);
         SubscribeLocalEvent<WiresComponent, InteractUsingEvent>(OnInteractUsing);
         SubscribeLocalEvent<WiresComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
         SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
-        SubscribeLocalEvent<WiresComponent, DoAfterEvent<WireExtraData>>(OnDoAfter);
+        SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
     }
 
     private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null)
@@ -437,84 +434,62 @@ public sealed class WiresSystem : SharedWiresSystem
         TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
     }
 
-    private void OnDoAfter(EntityUid uid, WiresComponent component, DoAfterEvent<WireExtraData> args)
+    private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args)
     {
         if (args.Cancelled)
         {
-            component.WiresQueue.Remove(args.AdditionalData.Id);
+            component.WiresQueue.Remove(args.Id);
             return;
         }
 
         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);
+        UpdateWires(args.Args.Target.Value, args.Args.User, args.Args.Used.Value, args.Id, args.Action, component);
 
         args.Handled = true;
     }
 
     private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args)
     {
+        if (args.Handled)
+            return;
+
         if (!TryComp<ToolComponent>(args.Used, out var tool) || !TryComp<WiresPanelComponent>(uid, out var panel))
             return;
+
         if (panel.Open &&
             (_toolSystem.HasQuality(args.Used, "Cutting", tool) ||
             _toolSystem.HasQuality(args.Used, "Pulsing", tool)))
         {
-            if (EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
-            {
-                var ui = _uiSystem.GetUiOrNull(uid, WiresUiKey.Key);
-                if (ui != null)
-                    _uiSystem.OpenUi(ui, actor.PlayerSession);
-                args.Handled = true;
-            }
+            if (TryComp(args.User, out ActorComponent? actor))
+                _uiSystem.TryOpen(uid, WiresUiKey.Key, actor.PlayerSession);
         }
-        else if (!panel.IsScrewing && _toolSystem.HasQuality(args.Used, "Screwing", tool))
+        else if (_toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, "Screwing", new WirePanelDoAfterEvent(), toolComponent: tool))
         {
-            var toolEvData = new ToolEventData(new WireToolFinishedEvent(uid, args.User), cancelledEv: new WireToolCanceledEvent(uid));
-
-            panel.IsScrewing = _toolSystem.UseTool(args.Used, args.User, uid, ScrewTime, new[] { "Screwing" }, toolEvData, toolComponent: tool);
-            args.Handled = panel.IsScrewing;
-
-            // Log attempt
-            _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} is screwing {ToPrettyString(uid):target}'s {(panel.Open ? "open" : "closed")} maintenance panel at {Transform(uid).Coordinates:targetlocation}");
+            _adminLogger.Add(LogType.Action, LogImpact.Low,
+                $"{ToPrettyString(args.User):user} is screwing {ToPrettyString(uid):target}'s {(panel.Open ? "open" : "closed")} maintenance panel at {Transform(uid).Coordinates:targetlocation}");
         }
     }
 
-    private void OnToolFinished(WireToolFinishedEvent args)
+    private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args)
     {
-        if (!TryComp<WiresPanelComponent>((args.Target), out var panel))
+        if (args.Cancelled)
             return;
 
-        panel.IsScrewing = false;
-        TogglePanel(args.Target, panel, !panel.Open);
-
-        // Log success
-        _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} screwed {ToPrettyString(args.Target):target}'s maintenance panel {(panel.Open ? "open" : "closed")}");
+        TogglePanel(uid, panel, !panel.Open);
+        UpdateAppearance(uid, panel);
+        _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} screwed {ToPrettyString(uid):target}'s maintenance panel {(panel.Open ? "open" : "closed")}");
 
         if (panel.Open)
         {
-            _audio.PlayPvs(panel.ScrewdriverOpenSound, args.Target);
+            _audio.PlayPvs(panel.ScrewdriverOpenSound, uid);
         }
         else
         {
-            _audio.PlayPvs(panel.ScrewdriverCloseSound, args.Target);
-            var ui = _uiSystem.GetUiOrNull(args.Target, WiresUiKey.Key);
-            if (ui != null)
-            {
-                _uiSystem.CloseAll(ui);
-            }
+            _audio.PlayPvs(panel.ScrewdriverCloseSound, uid);
+            _uiSystem.TryCloseAll(uid, WiresUiKey.Key);
         }
-
-        Dirty(panel);
-    }
-
-    private void OnToolCanceled(WireToolCanceledEvent ev)
-    {
-        if (!TryComp<WiresPanelComponent>(ev.Target, out var component))
-            return;
-
-        component.IsScrewing = false;
     }
 
     private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
@@ -662,16 +637,16 @@ public sealed class WiresSystem : SharedWiresSystem
             _appearance.SetData(uid, WiresVisuals.MaintenancePanelState, panel.Open && panel.Visible, appearance);
     }
 
-    private void TryDoWireAction(EntityUid used, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
+    private void TryDoWireAction(EntityUid target, EntityUid user, EntityUid toolEntity, int id, WiresAction action, WiresComponent? wires = null, ToolComponent? tool = null)
     {
-        if (!Resolve(used, ref wires)
+        if (!Resolve(target, ref wires)
             || !Resolve(toolEntity, ref tool))
             return;
 
         if (wires.WiresQueue.Contains(id))
             return;
 
-        var wire = TryGetWire(used, id, wires);
+        var wire = TryGetWire(target, id, wires);
 
         if (wire == null)
             return;
@@ -726,29 +701,21 @@ public sealed class WiresSystem : SharedWiresSystem
 
         if (_toolTime > 0f)
         {
-            var data = new WireExtraData(action, id);
-            var args = new DoAfterEventArgs(user, _toolTime, target: used, used: toolEntity)
+            var args = new DoAfterArgs(user, _toolTime, new WireDoAfterEvent(action, id), target, target: target, used: toolEntity)
             {
                 NeedHand = true,
-                BreakOnStun = true,
                 BreakOnDamage = true,
                 BreakOnUserMove = true
             };
 
-            _doAfter.DoAfter(args, data);
+            _doAfter.TryStartDoAfter(args);
         }
         else
         {
-            UpdateWires(used, user, toolEntity, id, action, wires);
+            UpdateWires(target, user, toolEntity, id, action, wires);
         }
     }
 
-    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)
     {
         if (!Resolve(used, ref wires))
@@ -786,7 +753,7 @@ public sealed class WiresSystem : SharedWiresSystem
                     break;
                 }
 
-                _toolSystem.PlayToolSound(toolEntity, tool);
+                _toolSystem.PlayToolSound(toolEntity, tool, user);
                 if (wire.Action == null || wire.Action.Cut(user, wire))
                 {
                     wire.IsCut = true;
@@ -807,7 +774,7 @@ public sealed class WiresSystem : SharedWiresSystem
                     break;
                 }
 
-                _toolSystem.PlayToolSound(toolEntity, tool);
+                _toolSystem.PlayToolSound(toolEntity, tool, user);
                 if (wire.Action == null || wire.Action.Mend(user, wire))
                 {
                     wire.IsCut = false;
@@ -921,25 +888,8 @@ public sealed class WiresSystem : SharedWiresSystem
     private void Reset(RoundRestartCleanupEvent args)
     {
         _layouts.Clear();
-        _random = new RobustRandom();
     }
     #endregion
-
-    #region Events
-    private sealed class WireToolFinishedEvent : EntityEventArgs
-    {
-        public EntityUid User { get; }
-        public EntityUid Target { get; }
-
-        public WireToolFinishedEvent(EntityUid target, EntityUid user)
-        {
-            Target = target;
-            User = user;
-        }
-    }
-
-    public record struct WireToolCanceledEvent(EntityUid Target);
-    #endregion
 }
 
 public sealed class Wire
index e0d772c718dd154ab922a7b2c657c83b2d5c7337..93aa5dd9099f300d485288e8c8097e94da9bd6f5 100644 (file)
@@ -72,7 +72,7 @@ namespace Content.Shared.ActionBlocker
             if (ev.Cancelled)
                 return false;
 
-            if (target == null)
+            if (target == null || target == user)
                 return true;
 
             var targetEv = new GettingInteractedWithAttemptEvent(user, target);
index 34e5518acd5d0f6c2664064cfd7c0ca01f307dc4..d2223313bcd347f0480516d256206d120d2bb27b 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.DoAfter;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.AirlockPainter
@@ -29,4 +30,22 @@ namespace Content.Shared.AirlockPainter
             SelectedStyle = selectedStyle;
         }
     }
+
+    [Serializable, NetSerializable]
+    public sealed class AirlockPainterDoAfterEvent : DoAfterEvent
+    {
+        [DataField("sprite", required: true)]
+        public readonly string Sprite = default!;
+
+        private AirlockPainterDoAfterEvent()
+        {
+        }
+
+        public AirlockPainterDoAfterEvent(string sprite)
+        {
+            Sprite = sprite;
+        }
+
+        public override DoAfterEvent Clone() => this;
+    }
 }
diff --git a/Content.Shared/Anomaly/ScannerDoAfterEvent.cs b/Content.Shared/Anomaly/ScannerDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..b280b60
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Anomaly;
+
+[Serializable, NetSerializable]
+public sealed class ScannerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index 7c81495f086697df2fa9d7b15ca5d9d56c9d623c..3c1e2e2e562bacb821d260e7b27732ed08b41439 100644 (file)
@@ -1,21 +1,21 @@
+using Content.Shared.DoAfter;
 using Content.Shared.FixedPoint;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Chemistry.Components
 {
+    [Serializable, NetSerializable]
+    public sealed class InjectorDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
+
     /// <summary>
     /// Shared class for injectors & syringes
     /// </summary>
     [NetworkedComponent, ComponentProtoName("Injector")]
     public abstract class SharedInjectorComponent : Component
     {
-        /// <summary>
-        /// Checks to see if the entity being injected
-        /// </summary>
-        [DataField("isInjecting")]
-        public bool IsInjecting;
-
         /// <summary>
         /// Component data used for net updates. Used by client for item status ui
         /// </summary>
@@ -26,7 +26,8 @@ namespace Content.Shared.Chemistry.Components
             public FixedPoint2 TotalVolume { get; }
             public InjectorToggleMode CurrentMode { get; }
 
-            public InjectorComponentState(FixedPoint2 currentVolume, FixedPoint2 totalVolume, InjectorToggleMode currentMode)
+            public InjectorComponentState(FixedPoint2 currentVolume, FixedPoint2 totalVolume,
+                InjectorToggleMode currentMode)
             {
                 CurrentVolume = currentVolume;
                 TotalVolume = totalVolume;
index 6521599f541a7644f1d951a79ab553b76723bcda..915b69392a8cab373d484212f69bff8ed17e1b22 100644 (file)
@@ -1,5 +1,7 @@
+using Content.Shared.DoAfter;
 using Content.Shared.DragDrop;
 using Content.Shared.Movement.Events;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Climbing;
 
@@ -24,4 +26,9 @@ public abstract class SharedClimbSystem : EntitySystem
     {
         args.CanDrop = HasComp<ClimbingComponent>(args.Dragged);
     }
+
+    [Serializable, NetSerializable]
+    protected sealed class ClimbDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
 }
index a029ad833c292bcb0f5a77245027ab39c5737d6a..fc584639e0487d5d989a8c30d8f48a1621e7b4bf 100644 (file)
@@ -69,7 +69,4 @@ public sealed class ToggleableClothingComponent : Component
     /// </summary>
     [DataField("verbText")]
     public string? VerbText;
-
-    // prevent duplicate doafters
-    public byte? DoAfterId;
 }
index b7ea47dc7b6efdf95d180265878931f4541361f2..0f69dafbed0f9f41625eb46b1e0267a991c66fd8 100644 (file)
@@ -12,6 +12,7 @@ using Content.Shared.Verbs;
 using Robust.Shared.Containers;
 using Robust.Shared.Network;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Clothing.EntitySystems;
@@ -47,7 +48,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
         SubscribeLocalEvent<ToggleableClothingComponent, InventoryRelayedEvent<GetVerbsEvent<EquipmentVerb>>>(GetRelayedVerbs);
         SubscribeLocalEvent<ToggleableClothingComponent, GetVerbsEvent<EquipmentVerb>>(OnGetVerbs);
         SubscribeLocalEvent<AttachedClothingComponent, GetVerbsEvent<EquipmentVerb>>(OnGetAttachedStripVerbsEvent);
-        SubscribeLocalEvent<ToggleableClothingComponent, DoAfterEvent<ToggleClothingEvent>>(OnDoAfterComplete);
+        SubscribeLocalEvent<ToggleableClothingComponent, ToggleClothingDoAfterEvent>(OnDoAfterComplete);
     }
 
     private void GetRelayedVerbs(EntityUid uid, ToggleableClothingComponent component, InventoryRelayedEvent<GetVerbsEvent<EquipmentVerb>> args)
@@ -92,37 +93,29 @@ public sealed class ToggleableClothingSystem : EntitySystem
 
     private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, ToggleableClothingComponent component)
     {
-        // TODO predict do afters & networked clothing toggle.
-        if (_net.IsClient)
-            return;
-
-        if (component.DoAfterId != null || component.StripDelay == null)
+        if (component.StripDelay == null)
             return;
 
         var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, (float) component.StripDelay.Value.TotalSeconds);
 
-        if (!stealth)
-        {
-            var popup = Loc.GetString("strippable-component-alert-owner-interact", ("user", Identity.Entity(user, EntityManager)), ("item", item));
-            _popupSystem.PopupEntity(popup, wearer, wearer, PopupType.Large);
-        }
-
-        var args = new DoAfterEventArgs(user, time, default, wearer, item)
+        var args = new DoAfterArgs(user, time, new ToggleClothingDoAfterEvent(), item, wearer, item)
         {
             BreakOnDamage = true,
-            BreakOnStun = true,
             BreakOnTargetMove = true,
-            RaiseOnTarget = false,
-            RaiseOnUsed = true,
-            RaiseOnUser = false,
             // This should just re-use the BUI range checks & cancel the do after if the BUI closes. But that is all
             // server-side at the moment.
             // TODO BUI REFACTOR.
             DistanceThreshold = 2,
         };
 
-        var doAfter = _doAfter.DoAfter(args, new ToggleClothingEvent() { Performer = user });
-        component.DoAfterId = doAfter.ID;
+        if (!_doAfter.TryStartDoAfter(args))
+            return;
+
+        if (!stealth)
+        {
+            var popup = Loc.GetString("strippable-component-alert-owner-interact", ("user", Identity.Entity(user, EntityManager)), ("item", item));
+            _popupSystem.PopupEntity(popup, wearer, wearer, PopupType.Large);
+        }
     }
 
     private void OnGetAttachedStripVerbsEvent(EntityUid uid, AttachedClothingComponent component, GetVerbsEvent<EquipmentVerb> args)
@@ -131,15 +124,12 @@ public sealed class ToggleableClothingSystem : EntitySystem
         OnGetVerbs(component.AttachedUid, Comp<ToggleableClothingComponent>(component.AttachedUid), args);
     }
 
-    private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, DoAfterEvent<ToggleClothingEvent> args)
+    private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, ToggleClothingDoAfterEvent args)
     {
-        DebugTools.Assert(component.DoAfterId == args.Id);
-        component.DoAfterId = null;
-
         if (args.Cancelled)
             return;
 
-        OnToggleClothing(uid, component, args.AdditionalData);
+        ToggleClothing(args.User, uid, component);
     }
 
     public override void Update(float frameTime)
@@ -241,21 +231,28 @@ public sealed class ToggleableClothingSystem : EntitySystem
     /// </summary>
     private void OnToggleClothing(EntityUid uid, ToggleableClothingComponent component, ToggleClothingEvent args)
     {
-        if (args.Handled || component.Container == null || component.ClothingUid == null)
+        if (args.Handled)
+            return;
+
+        args.Handled = true;
+        ToggleClothing(args.Performer, uid, component);
+    }
+
+    private void ToggleClothing(EntityUid user, EntityUid target, ToggleableClothingComponent component)
+    {
+        if (component.Container == null || component.ClothingUid == null)
             return;
 
-        var parent = Transform(uid).ParentUid;
+        var parent = Transform(target).ParentUid;
         if (component.Container.ContainedEntity == null)
             _inventorySystem.TryUnequip(parent, component.Slot);
         else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing))
         {
             _popupSystem.PopupEntity(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)),
-                args.Performer, args.Performer);
+                user, user);
         }
         else
             _inventorySystem.TryEquip(parent, component.ClothingUid.Value, component.Slot);
-
-        args.Handled = true;
     }
 
     private void OnGetActions(EntityUid uid, ToggleableClothingComponent component, GetItemActionsEvent args)
@@ -312,4 +309,11 @@ public sealed class ToggleableClothingSystem : EntitySystem
     }
 }
 
-public sealed class ToggleClothingEvent : InstantActionEvent { }
+public sealed class ToggleClothingEvent : InstantActionEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class ToggleClothingDoAfterEvent : SimpleDoAfterEvent
+{
+}
index 7c8a8620f9857d79a7cd9b389cf4c8f7079b8cc9..93e9504b126715cab404df99b48dce59124887dd 100644 (file)
@@ -1,8 +1,10 @@
 using Content.Shared.Construction.Components;
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
 using Content.Shared.Interaction;
 using Content.Shared.Pulling.Components;
 using Content.Shared.Tools.Components;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Construction.EntitySystems;
 
@@ -36,5 +38,17 @@ public abstract class SharedAnchorableSystem : EntitySystem
         ToolComponent? usingTool = null)
     {
         // Thanks tool system.
+
+        // TODO tool system is fixed now, make this actually shared.
+    }
+
+    [Serializable, NetSerializable]
+    protected sealed class TryUnanchorCompletedEvent : SimpleDoAfterEvent
+    {
+    }
+
+    [Serializable, NetSerializable]
+    protected sealed class TryAnchorCompletedEvent : SimpleDoAfterEvent
+    {
     }
 }
diff --git a/Content.Shared/Construction/Events.cs b/Content.Shared/Construction/Events.cs
new file mode 100644 (file)
index 0000000..1a4bace
--- /dev/null
@@ -0,0 +1,124 @@
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Robust.Shared.Map;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Construction;
+
+/// <summary>
+///     Sent client -> server to to tell the server that we started building
+///     a structure-construction.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class TryStartStructureConstructionMessage : EntityEventArgs
+{
+    /// <summary>
+    ///     Position to start building.
+    /// </summary>
+    public readonly EntityCoordinates Location;
+
+    /// <summary>
+    ///     The construction prototype to start building.
+    /// </summary>
+    public readonly string PrototypeName;
+
+    public readonly Angle Angle;
+
+    /// <summary>
+    ///     Identifier to be sent back in the acknowledgement so that the client can clean up its ghost.
+    /// </summary>
+    public readonly int Ack;
+
+    public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack)
+    {
+        Location = loc;
+        PrototypeName = prototypeName;
+        Angle = angle;
+        Ack = ack;
+    }
+}
+
+/// <summary>
+///     Sent client -> server to to tell the server that we started building
+///     an item-construction.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class TryStartItemConstructionMessage : EntityEventArgs
+{
+    /// <summary>
+    ///     The construction prototype to start building.
+    /// </summary>
+    public readonly string PrototypeName;
+
+    public TryStartItemConstructionMessage(string prototypeName)
+    {
+        PrototypeName = prototypeName;
+    }
+}
+
+/// <summary>
+/// Sent server -> client to tell the client that a ghost has started to be constructed.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class AckStructureConstructionMessage : EntityEventArgs
+{
+    public readonly int GhostId;
+
+    public AckStructureConstructionMessage(int ghostId)
+    {
+        GhostId = ghostId;
+    }
+}
+
+/// <summary>
+/// Sent client -> server to request a specific construction guide.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class RequestConstructionGuide : EntityEventArgs
+{
+    public readonly string ConstructionId;
+
+    public RequestConstructionGuide(string constructionId)
+    {
+        ConstructionId = constructionId;
+    }
+}
+
+/// <summary>
+/// Sent server -> client as a response to a <see cref="RequestConstructionGuide"/> net message.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class ResponseConstructionGuide : EntityEventArgs
+{
+    public readonly string ConstructionId;
+    public readonly ConstructionGuide Guide;
+
+    public ResponseConstructionGuide(string constructionId, ConstructionGuide guide)
+    {
+        ConstructionId = constructionId;
+        Guide = guide;
+    }
+}
+
+[Serializable, NetSerializable]
+public sealed class ConstructionInteractDoAfterEvent : DoAfterEvent
+{
+    [DataField("clickLocation")]
+    public readonly EntityCoordinates ClickLocation;
+
+    private ConstructionInteractDoAfterEvent()
+    {
+    }
+
+    public ConstructionInteractDoAfterEvent(InteractUsingEvent ev)
+    {
+        ClickLocation = ev.ClickLocation;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
+
+[Serializable, NetSerializable]
+public sealed class WelderRefineDoAfterEvent : SimpleDoAfterEvent
+{
+}
index 150ce79a0168f23c2346ac1240d0ba4bb15700e5..5e2f335200dcc5afdd17c671a333c66d8453a60a 100644 (file)
@@ -1,6 +1,5 @@
 using System.Linq;
 using Robust.Shared.Map;
-using Robust.Shared.Serialization;
 using static Content.Shared.Interaction.SharedInteractionSystem;
 
 namespace Content.Shared.Construction
@@ -23,100 +22,5 @@ namespace Content.Shared.Construction
             var ignored = grid.GetAnchoredEntities(coords).ToHashSet();
             return e => ignored.Contains(e);
         }
-
-        /// <summary>
-        ///     Sent client -> server to to tell the server that we started building
-        ///     a structure-construction.
-        /// </summary>
-        [Serializable, NetSerializable]
-        public sealed class TryStartStructureConstructionMessage : EntityEventArgs
-        {
-            /// <summary>
-            ///     Position to start building.
-            /// </summary>
-            public readonly EntityCoordinates Location;
-
-            /// <summary>
-            ///     The construction prototype to start building.
-            /// </summary>
-            public readonly string PrototypeName;
-
-            public readonly Angle Angle;
-
-            /// <summary>
-            ///     Identifier to be sent back in the acknowledgement so that the client can clean up its ghost.
-            /// </summary>
-            public readonly int Ack;
-
-            public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack)
-            {
-                Location = loc;
-                PrototypeName = prototypeName;
-                Angle = angle;
-                Ack = ack;
-            }
-        }
-
-        /// <summary>
-        ///     Sent client -> server to to tell the server that we started building
-        ///     an item-construction.
-        /// </summary>
-        [Serializable, NetSerializable]
-        public sealed class TryStartItemConstructionMessage : EntityEventArgs
-        {
-            /// <summary>
-            ///     The construction prototype to start building.
-            /// </summary>
-            public readonly string PrototypeName;
-
-            public TryStartItemConstructionMessage(string prototypeName)
-            {
-                PrototypeName = prototypeName;
-            }
-        }
-
-        /// <summary>
-        /// Sent server -> client to tell the client that a ghost has started to be constructed.
-        /// </summary>
-        [Serializable, NetSerializable]
-        public sealed class AckStructureConstructionMessage : EntityEventArgs
-        {
-            public readonly int GhostId;
-
-            public AckStructureConstructionMessage(int ghostId)
-            {
-                GhostId = ghostId;
-            }
-        }
-
-        /// <summary>
-        /// Sent client -> server to request a specific construction guide.
-        /// </summary>
-        [Serializable, NetSerializable]
-        public sealed class RequestConstructionGuide : EntityEventArgs
-        {
-            public readonly string ConstructionId;
-
-            public RequestConstructionGuide(string constructionId)
-            {
-                ConstructionId = constructionId;
-            }
-        }
-
-        /// <summary>
-        /// Sent server -> client as a response to a <see cref="RequestConstructionGuide"/> net message.
-        /// </summary>
-        [Serializable, NetSerializable]
-        public sealed class ResponseConstructionGuide : EntityEventArgs
-        {
-            public readonly string ConstructionId;
-            public readonly ConstructionGuide Guide;
-
-            public ResponseConstructionGuide(string constructionId, ConstructionGuide guide)
-            {
-                ConstructionId = constructionId;
-                Guide = guide;
-            }
-        }
     }
 }
index 14c572b7b8be19789ceb5fb963d134b103423676..624cc06ac49175ef3b9b96f222b68bb0f446857b 100644 (file)
@@ -38,29 +38,21 @@ public sealed class CuffableComponent : Component
     /// </summary>
     [DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)]
     public bool CanStillInteract = true;
-
-    /// <summary>
-    /// Whether or not the entity is currently in the process of being uncuffed.
-    /// </summary>
-    [DataField("uncuffing"), ViewVariables(VVAccess.ReadWrite)]
-    public bool Uncuffing;
 }
 
 [Serializable, NetSerializable]
 public sealed class CuffableComponentState : ComponentState
 {
     public readonly bool CanStillInteract;
-    public readonly bool Uncuffing;
     public readonly int NumHandsCuffed;
     public readonly string? RSI;
     public readonly string? IconState;
     public readonly Color? Color;
 
-    public CuffableComponentState(int numHandsCuffed, bool canStillInteract,  bool uncuffing, string? rsiPath, string? iconState, Color? color)
+    public CuffableComponentState(int numHandsCuffed, bool canStillInteract, string? rsiPath, string? iconState, Color? color)
     {
         NumHandsCuffed = numHandsCuffed;
         CanStillInteract = canStillInteract;
-        Uncuffing = uncuffing;
         RSI = rsiPath;
         IconState = iconState;
         Color = color;
index 7f0f60bfacedeeb0042dfbf0da9c37c43b71f5bd..27f8a0a112f9945b9a7e26476f362e1333087da4 100644 (file)
@@ -79,24 +79,16 @@ public sealed class HandcuffComponent : Component
 
     [DataField("endUncuffSound"), ViewVariables(VVAccess.ReadWrite)]
     public SoundSpecifier EndUncuffSound = new SoundPathSpecifier("/Audio/Items/Handcuffs/cuff_takeoff_end.ogg");
-
-    /// <summary>
-    ///     Used to prevent DoAfter getting spammed.
-    /// </summary>
-    [DataField("cuffing"), ViewVariables(VVAccess.ReadWrite)]
-    public bool Cuffing;
 }
 
 [Serializable, NetSerializable]
 public sealed class HandcuffComponentState : ComponentState
 {
     public readonly string? IconState;
-    public readonly bool Cuffing;
 
-    public HandcuffComponentState(string? iconState, bool cuffing)
+    public HandcuffComponentState(string? iconState)
     {
         IconState = iconState;
-        Cuffing = cuffing;
     }
 }
 
index f513bb4aa345d962dd4e12d1b789ffe6122bfc29..c972dac70195925cc34b6930868bfa16d591e0a7 100644 (file)
@@ -28,9 +28,11 @@ using Content.Shared.Weapons.Melee.Events;
 using Robust.Shared.Containers;
 using Robust.Shared.Network;
 using Robust.Shared.Player;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Cuffs
 {
+    // TODO remove all the IsServer() checks.
     public abstract class SharedCuffableSystem : EntitySystem
     {
         [Dependency] private readonly IComponentFactory _componentFactory = default!;
@@ -65,7 +67,7 @@ namespace Content.Shared.Cuffs
             SubscribeLocalEvent<CuffableComponent, IsUnequippingAttemptEvent>(OnUnequipAttempt);
             SubscribeLocalEvent<CuffableComponent, BeingPulledAttemptEvent>(OnBeingPulledAttempt);
             SubscribeLocalEvent<CuffableComponent, GetVerbsEvent<Verb>>(AddUncuffVerb);
-            SubscribeLocalEvent<CuffableComponent, DoAfterEvent<UnCuffDoAfter>>(OnCuffableDoAfter);
+            SubscribeLocalEvent<CuffableComponent, UnCuffDoAfterEvent>(OnCuffableDoAfter);
             SubscribeLocalEvent<CuffableComponent, PullStartedMessage>(OnPull);
             SubscribeLocalEvent<CuffableComponent, PullStoppedMessage>(OnPull);
             SubscribeLocalEvent<CuffableComponent, DropAttemptEvent>(CheckAct);
@@ -76,7 +78,7 @@ namespace Content.Shared.Cuffs
 
             SubscribeLocalEvent<HandcuffComponent, AfterInteractEvent>(OnCuffAfterInteract);
             SubscribeLocalEvent<HandcuffComponent, MeleeHitEvent>(OnCuffMeleeHit);
-            SubscribeLocalEvent<HandcuffComponent, DoAfterEvent<AddCuffDoAfter>>(OnAddCuffDoAfter);
+            SubscribeLocalEvent<HandcuffComponent, AddCuffDoAfterEvent>(OnAddCuffDoAfter);
 
         }
 
@@ -221,18 +223,14 @@ namespace Content.Shared.Cuffs
             args.Verbs.Add(verb);
         }
 
-        private void OnCuffableDoAfter(EntityUid uid, CuffableComponent component, DoAfterEvent<UnCuffDoAfter> args)
+        private void OnCuffableDoAfter(EntityUid uid, CuffableComponent component, UnCuffDoAfterEvent args)
         {
-            component.Uncuffing = false;
-
             if (args.Args.Target is not { } target || args.Args.Used is not { } used)
                 return;
             if (args.Handled)
                 return;
             args.Handled = true;
 
-            Dirty(component);
-
             var user = args.Args.User;
 
             if (!args.Cancelled)
@@ -270,7 +268,7 @@ namespace Content.Shared.Cuffs
             args.Handled = true;
         }
 
-        private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, DoAfterEvent<AddCuffDoAfter> args)
+        private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuffDoAfterEvent args)
         {
             var user = args.Args.User;
 
@@ -282,11 +280,10 @@ namespace Content.Shared.Cuffs
             if (args.Handled)
                 return;
             args.Handled = true;
-            component.Cuffing = false;
 
             if (!args.Cancelled && TryAddNewCuffs(target, user, uid, cuffable))
             {
-                _audio.PlayPvs(component.EndCuffSound, uid);
+                _audio.PlayPredicted(component.EndCuffSound, uid, user);
                 if (!_net.IsServer)
                     return;
 
@@ -321,6 +318,9 @@ namespace Content.Shared.Cuffs
                 }
                 else
                 {
+                    // TODO Fix popup message wording
+                    // This message assumes that the user being handcuffed is the one that caused the handcuff to fail.
+
                     _popup.PopupEntity(Loc.GetString("handcuff-component-cuff-interrupt-message",
                         ("targetName", Identity.Name(target, EntityManager, user))), user, user);
                     _popup.PopupEntity(Loc.GetString("handcuff-component-cuff-interrupt-other-message",
@@ -427,9 +427,6 @@ namespace Content.Shared.Cuffs
             if (!Resolve(handcuff, ref handcuffComponent) || !Resolve(target, ref cuffable, false))
                 return;
 
-            if (handcuffComponent.Cuffing)
-                return;
-
             if (!TryComp<SharedHandsComponent?>(target, out var hands))
             {
                 if (_net.IsServer)
@@ -450,6 +447,25 @@ namespace Content.Shared.Cuffs
                 return;
             }
 
+            var cuffTime = handcuffComponent.CuffTime;
+
+            if (HasComp<StunnedComponent>(target))
+                cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus);
+
+            if (HasComp<DisarmProneComponent>(target))
+                cuffTime = 0.0f; // cuff them instantly.
+
+            var doAfterEventArgs = new DoAfterArgs(user, cuffTime, new AddCuffDoAfterEvent(), handcuff, target, handcuff)
+            {
+                BreakOnTargetMove = true,
+                BreakOnUserMove = true,
+                BreakOnDamage = true,
+                NeedHand = true
+            };
+
+            if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+                return;
+
             if (_net.IsServer)
             {
                 _popup.PopupEntity(Loc.GetString("handcuff-component-start-cuffing-observer",
@@ -471,30 +487,6 @@ namespace Content.Shared.Cuffs
             }
 
             _audio.PlayPvs(handcuffComponent.StartCuffSound, handcuff);
-
-            var cuffTime = handcuffComponent.CuffTime;
-
-            if (HasComp<StunnedComponent>(target))
-                cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus);
-
-            if (HasComp<DisarmProneComponent>(target))
-                cuffTime = 0.0f; // cuff them instantly.
-
-            var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target, handcuff)
-            {
-                RaiseOnUser = false,
-                RaiseOnTarget = false,
-                RaiseOnUsed = true,
-                BreakOnTargetMove = true,
-                BreakOnUserMove = true,
-                BreakOnDamage = true,
-                BreakOnStun = true,
-                NeedHand = true
-            };
-
-            handcuffComponent.Cuffing = true;
-            if (_net.IsServer)
-                _doAfter.DoAfter(doAfterEventArgs, new AddCuffDoAfter());
         }
 
         /// <summary>
@@ -511,9 +503,6 @@ namespace Content.Shared.Cuffs
             if (!Resolve(target, ref cuffable))
                 return;
 
-            if (cuffable.Uncuffing)
-                return;
-
             var isOwner = user == target;
 
             if (cuffsToRemove == null)
@@ -551,28 +540,21 @@ namespace Content.Shared.Cuffs
                 return;
             }
 
-            if (_net.IsServer)
-                _popup.PopupEntity(Loc.GetString("cuffable-component-start-removing-cuffs-message"), user, user);
-
-            _audio.PlayPredicted(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, target, user);
-
             var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
-            var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime, default, target, cuffsToRemove)
+            var doAfterEventArgs = new DoAfterArgs(user, uncuffTime, new UnCuffDoAfterEvent(), target, target, cuffsToRemove)
             {
-                RaiseOnTarget = true,
-                RaiseOnUsed = false,
-                RaiseOnUser = false,
                 BreakOnUserMove = true,
                 BreakOnTargetMove = true,
                 BreakOnDamage = true,
-                BreakOnStun = true,
-                NeedHand = true
+                NeedHand = true,
+                RequireCanInteract = false, // Trust in UncuffAttemptEvent
             };
 
-            cuffable.Uncuffing = true;
-            Dirty(cuffable);
-            if (_net.IsServer)
-                _doAfter.DoAfter(doAfterEventArgs, new UnCuffDoAfter());
+            if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
+                return;
+
+            _popup.PopupEntity(Loc.GetString("cuffable-component-start-removing-cuffs-message"), user, Filter.Local(), false);
+            _audio.PlayPredicted(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, target, user);
         }
 
         public void Uncuff(EntityUid target, EntityUid user, EntityUid cuffsToRemove, CuffableComponent? cuffable = null, HandcuffComponent? cuff = null)
@@ -580,6 +562,11 @@ namespace Content.Shared.Cuffs
             if (!Resolve(target, ref cuffable) || !Resolve(cuffsToRemove, ref cuff))
                 return;
 
+            var attempt = new UncuffAttemptEvent(user, target);
+            RaiseLocalEvent(user, ref attempt);
+            if (attempt.Cancelled)
+                return;
+
             _audio.PlayPvs(cuff.EndUncuffSound, target);
 
             cuffable.Container.Remove(cuffsToRemove);
@@ -665,11 +652,13 @@ namespace Content.Shared.Cuffs
             return component.Container.ContainedEntities;
         }
 
-        private struct UnCuffDoAfter
+        [Serializable, NetSerializable]
+        private sealed class UnCuffDoAfterEvent : SimpleDoAfterEvent
         {
         }
 
-        private struct AddCuffDoAfter
+        [Serializable, NetSerializable]
+        private sealed class AddCuffDoAfterEvent : SimpleDoAfterEvent
         {
         }
     }
diff --git a/Content.Shared/Disease/Events/VaccineDoAfterEvent.cs b/Content.Shared/Disease/Events/VaccineDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..a1c2772
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Disease.Events;
+
+[Serializable, NetSerializable]
+public sealed class VaccineDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index 3171bd227bd0bad6f3f20dab03d94768edc84eb0..470dbbc94ccea6ec720008fce7d2d08f43eec409 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.Body.Components;
 using Content.Shared.Disposal.Components;
+using Content.Shared.DoAfter;
 using Content.Shared.DragDrop;
 using Content.Shared.Emag.Systems;
 using Content.Shared.Item;
@@ -9,10 +10,16 @@ using Content.Shared.Throwing;
 using JetBrains.Annotations;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Events;
+using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Disposal
 {
+    [Serializable, NetSerializable]
+    public sealed class DisposalDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
+
     [UsedImplicitly]
     public abstract class SharedDisposalUnitSystem : EntitySystem
     {
@@ -32,7 +39,8 @@ namespace Content.Shared.Disposal
             SubscribeLocalEvent<SharedDisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
         }
 
-        private void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component, ref PreventCollideEvent args)
+        private void OnPreventCollide(EntityUid uid, SharedDisposalUnitComponent component,
+            ref PreventCollideEvent args)
         {
             var otherBody = args.BodyB.Owner;
 
index d8d71de953e0581aa27f8ba725ea79fd33819d93..186b102f68b3f3a75b9a5e0cc3470c21ff8e332a 100644 (file)
@@ -5,8 +5,7 @@ namespace Content.Shared.DoAfter;
 /// <summary>
 ///     Added to entities that are currently performing any doafters.
 /// </summary>
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent]
 public sealed class ActiveDoAfterComponent : Component
 {
-
 }
index 85d15acba4103300f98308ec795a0eb9c8dee2f0..0a8e197c198c64e1c5d57be08d0f7386583f0c3b 100644 (file)
-using System.Threading.Tasks;
-using Content.Shared.Hands.Components;
 using Robust.Shared.Map;
 using Robust.Shared.Serialization;
-using Robust.Shared.Timing;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using Robust.Shared.Utility;
 
 namespace Content.Shared.DoAfter;
+
 [Serializable, NetSerializable]
 [DataDefinition]
+[Access(typeof(SharedDoAfterSystem))]
 public sealed class DoAfter
 {
-    [NonSerialized]
-    [Obsolete]
-    public Task<DoAfterStatus> AsTask;
-
-    [NonSerialized]
-    [Obsolete("Will be obsolete for EventBus")]
-    public TaskCompletionSource<DoAfterStatus> Tcs;
-
-    //TODO: Should be merged into here
-    public readonly DoAfterEventArgs EventArgs;
-
-    //ID so the client DoAfterSystem can track
-    public byte ID;
+    [DataField("index", required:true)]
+    public ushort Index;
 
-    public bool Cancelled = false;
+    public DoAfterId Id => new(Args.User, Index);
 
-    //Cache the delay so the timer properly shows
-    public float Delay;
+    [IncludeDataField]
+    public readonly DoAfterArgs Args = default!;
 
-    //Keep track of the time this DoAfter started
+    /// <summary>
+    ///     Time at which this do after was started.
+    /// </summary>
+    [DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer), required:true)]
     public TimeSpan StartTime;
 
-    //Keep track of the time this DoAfter was cancelled
-    public TimeSpan CancelledTime;
+    /// <summary>
+    ///     The time at which this do after was canceled
+    /// </summary>
+    [DataField("cancelledTime", customTypeSerializer: typeof(TimeOffsetSerializer), required:true)]
+    public TimeSpan? CancelledTime;
+
+    /// <summary>
+    ///     If true, this do after has finished, passed the final checks, and has raised its events.
+    /// </summary>
+    [DataField("completed")]
+    public bool Completed;
 
-    //How long has the do after been running?
-    public TimeSpan Elapsed = TimeSpan.Zero;
+    /// <summary>
+    ///     Whether the do after has been canceled.
+    /// </summary>
+    public bool Cancelled => CancelledTime != null;
 
     /// <summary>
-    /// Accrued time when cancelled.
+    ///     Position of the user relative to their parent when the do after was started.
     /// </summary>
-    public TimeSpan CancelledElapsed = TimeSpan.Zero;
+    [DataField("userPosition")]
+    public EntityCoordinates UserPosition;
 
-    public EntityCoordinates UserGrid;
-    public EntityCoordinates TargetGrid;
+    /// <summary>
+    ///     Position of the target relative to their parent when the do after was started.
+    /// </summary>
+    [DataField("targetPosition")]
+    public EntityCoordinates TargetPosition;
 
-    [NonSerialized]
-    public Action<bool>? Done;
+    /// <summary>
+    ///     If <see cref="DoAfterArgs.NeedHand"/> is true, this is the hand that was selected when the doafter started.
+    /// </summary>
+    [DataField("activeHand")]
+    public string? InitialHand;
 
-#pragma warning disable RA0004
-    public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running;
-#pragma warning restore RA0004
+    /// <summary>
+    ///     If <see cref="NeedHand"/> is true, this is the entity that was in the active hand when the doafter started.
+    /// </summary>
+    [DataField("activeItem")]
+    public EntityUid? InitialItem;
 
-    // NeedHand
-    public readonly string? ActiveHand;
-    public readonly EntityUid? ActiveItem;
+    // cached attempt event for the sake of avoiding unnecessary reflection every time this needs to be raised.
+    [NonSerialized] public object? AttemptEvent;
 
-    public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager)
+    private DoAfter()
     {
-        EventArgs = eventArgs;
-        StartTime = IoCManager.Resolve<IGameTiming>().CurTime;
-
-        if (eventArgs.BreakOnUserMove)
-            UserGrid = entityManager.GetComponent<TransformComponent>(eventArgs.User).Coordinates;
+    }
 
-        if (eventArgs.Target != null && eventArgs.BreakOnTargetMove)
-            // Target should never be null if the bool is set.
-            TargetGrid = entityManager.GetComponent<TransformComponent>(eventArgs.Target!.Value).Coordinates;
+    public DoAfter(ushort index, DoAfterArgs args, TimeSpan startTime)
+    {
+        Index = index;
 
-        // 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))
+        if (args.Target == null)
         {
-            ActiveHand = handsComponent.ActiveHand?.Name;
-            ActiveItem = handsComponent.ActiveHandEntity;
+            DebugTools.Assert(!args.BreakOnTargetMove);
+            args.BreakOnTargetMove = false;
         }
 
-        Tcs = new TaskCompletionSource<DoAfterStatus>();
-        AsTask = Tcs.Task;
+        Args = args;
+        StartTime = startTime;
+    }
+
+    public DoAfter(DoAfter other)
+    {
+        Index = other.Index;
+        Args = new(other.Args);
+        StartTime = other.StartTime;
+        CancelledTime = other.CancelledTime;
+        Completed = other.Completed;
+        UserPosition = other.UserPosition;
+        TargetPosition = other.TargetPosition;
+        InitialHand = other.InitialHand;
+        InitialItem = other.InitialItem;
     }
 }
+
+/// <summary>
+///     Simple struct that contains data required to uniquely identify a doAfter.
+/// </summary>
+/// <remarks>
+///     Can be used to track currently active do-afters to prevent simultaneous do-afters.
+/// </remarks>
+public record struct DoAfterId(EntityUid Uid, ushort Index);
diff --git a/Content.Shared/DoAfter/DoAfterArgs.cs b/Content.Shared/DoAfter/DoAfterArgs.cs
new file mode 100644 (file)
index 0000000..bf185cd
--- /dev/null
@@ -0,0 +1,295 @@
+using Content.Shared.FixedPoint;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DoAfter;
+
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed class DoAfterArgs
+{
+    /// <summary>
+    ///     The entity invoking do_after
+    /// </summary>
+    [DataField("user", required: true)]
+    public readonly EntityUid User;
+
+    /// <summary>
+    ///     How long does the do_after require to complete
+    /// </summary>
+    [DataField("delay", required: true)]
+    public readonly TimeSpan Delay;
+
+    /// <summary>
+    ///     Applicable target (if relevant)
+    /// </summary>
+    [DataField("target")]
+    public readonly EntityUid? Target;
+
+    /// <summary>
+    ///     Entity used by the User on the Target.
+    /// </summary>
+    [DataField("using")]
+    public readonly EntityUid? Used;
+
+    #region Event options
+    /// <summary>
+    ///     The event that will get raised when the DoAfter has finished. If null, this will simply raise a <see cref="SimpleDoAfterEvent"/>
+    /// </summary>
+    [DataField("event", required: true)]
+    public readonly DoAfterEvent Event = default!;
+
+    /// <summary>
+    ///     This option determines how frequently the DoAfterAttempt event will get raised. Defaults to never raising the
+    ///     event.
+    /// </summary>
+    [DataField("attemptEventFrequency")]
+    public AttemptFrequency AttemptFrequency;
+
+    /// <summary>
+    ///     Entity which will receive the directed event. If null, no directed event will be raised.
+    /// </summary>
+    [DataField("eventTarget")]
+    public readonly EntityUid? EventTarget;
+
+    /// <summary>
+    /// Should the DoAfter event broadcast? If this is false, then <see cref="EventTarget"/> should be a valid entity.
+    /// </summary>
+    [DataField("broadcast")]
+    public bool Broadcast;
+    #endregion
+
+    #region Break/Cancellation Options
+    // Break the chains
+    /// <summary>
+    ///     Whether or not this do after requires the user to have hands.
+    /// </summary>
+    [DataField("needHand")]
+    public bool NeedHand;
+
+    /// <summary>
+    ///     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). This does nothing if <see cref="NeedHand"/> is false.
+    /// </summary>
+    [DataField("breakOnHandChange")]
+    public bool BreakOnHandChange = true;
+
+    /// <summary>
+    ///     If do_after stops when the user moves
+    /// </summary>
+    [DataField("breakOnUserMove")]
+    public bool BreakOnUserMove;
+
+    /// <summary>
+    ///     If do_after stops when the target moves (if there is a target)
+    /// </summary>
+    [DataField("breakOnTargetMove")]
+    public bool BreakOnTargetMove;
+
+    /// <summary>
+    ///     Threshold for user and target movement
+    /// </summary>
+    [DataField("movementThreshold")]
+    public float MovementThreshold = 0.1f;
+
+    /// <summary>
+    ///     Threshold for distance user from the used OR target entities.
+    /// </summary>
+    [DataField("distanceThreshold")]
+    public float? DistanceThreshold;
+
+    /// <summary>
+    ///     Whether damage will cancel the DoAfter. See also <see cref="DamageThreshold"/>.
+    /// </summary>
+    [DataField("breakOnDamage")]
+    public bool BreakOnDamage;
+
+    /// <summary>
+    ///     Threshold for user damage. This damage has to be dealt in a single event, not over time.
+    /// </summary>
+    [DataField("damageThreshold")]
+    public FixedPoint2 DamageThreshold = 1;
+
+    /// <summary>
+    ///     If true, this DoAfter will be canceled if the user can no longer interact with the target.
+    /// </summary>
+    [DataField("requireCanInteract")]
+    public bool RequireCanInteract = true;
+    #endregion
+
+    #region Duplicates
+    /// <summary>
+    ///     If true, this will prevent duplicate DoAfters from being started See also <see cref="DuplicateConditions"/>.
+    /// </summary>
+    /// <remarks>
+    ///     Note that this will block even if the duplicate is cancelled because either DoAfter had
+    ///     <see cref="CancelDuplicate"/> enabled.
+    /// </remarks>
+    [DataField("blockDuplicate")]
+    public bool BlockDuplicate = true;
+
+    /// <summary>
+    ///     If true, this will cancel any duplicate DoAfters when attempting to add a new DoAfter. See also
+    ///     <see cref="DuplicateConditions"/>.
+    /// </summary>
+    [DataField("cancelDuplicate")]
+    public bool CancelDuplicate = true;
+
+    /// <summary>
+    ///     These flags determine what DoAfter properties are used to determine whether one DoAfter is a duplicate of
+    ///     another.
+    /// </summary>
+    /// <remarks>
+    ///     Note that both DoAfters may have their own conditions, and they will be considered duplicated if either set
+    ///     of conditions is satisfied.
+    /// </remarks>
+    [DataField("duplicateCondition")]
+    public DuplicateConditions DuplicateCondition = DuplicateConditions.All;
+    #endregion
+
+    /// <summary>
+    ///     Additional conditions that need to be met. Return false to cancel.
+    /// </summary>
+    [NonSerialized]
+    [Obsolete("Use checkEvent instead")]
+    public Func<bool>? ExtraCheck;
+
+    #region Constructors
+
+    /// <summary>
+    ///     Creates a new set of DoAfter arguments.
+    /// </summary>
+    /// <param name="user">The user that will perform the DoAfter</param>
+    /// <param name="delay">The time it takes for the DoAfter to complete</param>
+    /// <param name="event">The event that will be raised when the DoAfter has ended (completed or cancelled).</param>
+    /// <param name="eventTarget">The entity at which the event will be directed. If null, the event will not be directed.</param>
+    /// <param name="target">The entity being targeted by the DoAFter. Not the same as <see cref="EventTarget"/></param>.
+    /// <param name="used">The entity being used during the DoAfter. E.g., a tool</param>
+    public DoAfterArgs(
+        EntityUid user,
+        TimeSpan delay,
+        DoAfterEvent @event,
+        EntityUid? eventTarget,
+        EntityUid? target = null,
+        EntityUid? used = null)
+    {
+        User = user;
+        Delay = delay;
+        Target = target;
+        Used = used;
+        EventTarget = eventTarget;
+        Event = @event;
+    }
+
+    private DoAfterArgs()
+    {
+    }
+
+    /// <summary>
+    ///     Creates a new set of DoAfter arguments.
+    /// </summary>
+    /// <param name="user">The user that will perform the DoAfter</param>
+    /// <param name="seconds">The time it takes for the DoAfter to complete, in seconds</param>
+    /// <param name="event">The event that will be raised when the DoAfter has ended (completed or cancelled).</param>
+    /// <param name="eventTarget">The entity at which the event will be directed. If null, the event will not be directed.</param>
+    /// <param name="target">The entity being targeted by the DoAfter. Not the same as <see cref="EventTarget"/></param>.
+    /// <param name="used">The entity being used during the DoAfter. E.g., a tool</param>
+    public DoAfterArgs(
+        EntityUid user,
+        float seconds,
+        DoAfterEvent @event,
+        EntityUid? eventTarget,
+        EntityUid? target = null,
+        EntityUid? used = null)
+        : this(user, TimeSpan.FromSeconds(seconds), @event, eventTarget, target, used)
+    {
+    }
+
+    #endregion
+
+    public DoAfterArgs(DoAfterArgs other)
+    {
+        User = other.User;
+        Delay = other.Delay;
+        Target = other.Target;
+        Used = other.Used;
+        EventTarget = other.EventTarget;
+        Broadcast = other.Broadcast;
+        NeedHand = other.NeedHand;
+        BreakOnHandChange = other.BreakOnHandChange;
+        BreakOnUserMove = other.BreakOnUserMove;
+        BreakOnTargetMove = other.BreakOnTargetMove;
+        MovementThreshold = other.MovementThreshold;
+        DistanceThreshold = other.DistanceThreshold;
+        BreakOnDamage = other.BreakOnDamage;
+        DamageThreshold = other.DamageThreshold;
+        RequireCanInteract = other.RequireCanInteract;
+        AttemptFrequency = other.AttemptFrequency;
+        BlockDuplicate = other.BlockDuplicate;
+        CancelDuplicate = other.CancelDuplicate;
+        DuplicateCondition = other.DuplicateCondition;
+
+        Event = other.Event.Clone();
+    }
+}
+
+/// <summary>
+///     See <see cref="DoAfterArgs.DuplicateCondition"/>.
+/// </summary>
+[Flags]
+public enum DuplicateConditions : byte
+{
+    /// <summary>
+    ///     This DoAfter will consider any other DoAfter with the same user to be a duplicate.
+    /// </summary>
+    None = 0,
+
+    /// <summary>
+    ///     Requires that <see cref="Used"/> refers to the same entity in order to be considered a duplicate.
+    /// </summary>
+    /// <remarks>
+    ///     E.g., if all checks are enabled for stripping, then stripping different articles of clothing on the same
+    ///     mob would be allowed. If instead this check were disabled, then any stripping actions on the same target
+    ///     would be considered duplicates, so you would only be able to take one piece of clothing at a time.
+    /// </remarks>
+    SameTool = 1 << 1,
+
+    /// <summary>
+    ///     Requires that <see cref="Target"/> refers to the same entity in order to be considered a duplicate.
+    /// </summary>
+    /// <remarks>
+    ///     E.g., if all checks are enabled for mining, then using the same pickaxe to mine different rocks will be
+    ///     allowed. If instead this check were disabled, then the trying to mine a different rock with the same
+    ///     pickaxe would be considered a duplicate DoAfter.
+    /// </remarks>
+    SameTarget = 1 << 2,
+
+    /// <summary>
+    ///     Requires that the <see cref="Event"/> types match in order to be considered a duplicate.
+    /// </summary>
+    /// <remarks>
+    ///     If your DoAfter should block other unrelated DoAfters involving the same set of entities, you may want
+    ///     to disable this condition. E.g. force feeding a donk pocket and forcefully giving someone a donk pocket
+    ///     should be mutually exclusive, even though the DoAfters have unrelated effects.
+    /// </remarks>
+    SameEvent = 1 << 3,
+
+    All = SameTool | SameTarget | SameEvent,
+}
+
+public enum AttemptFrequency : byte
+{
+    /// <summary>
+    ///     Never raise the attempt event.
+    /// </summary>
+    Never = 0,
+
+    /// <summary>
+    ///     Raises the attempt event when the DoAfter is about to start or end.
+    /// </summary>
+    StartAndEnd = 1,
+
+    /// <summary>
+    ///     Raise the attempt event every tick while the DoAfter is running.
+    /// </summary>
+    EveryTick = 2
+}
index b8dc23d3786829c24f13ea0b6e3e295fd1da23b5..748fd40dfa26ee6fa1cf548d2dfefe9b18ea90b6 100644 (file)
@@ -1,92 +1,40 @@
+using System.Threading.Tasks;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.DoAfter;
 
 [RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedDoAfterSystem))]
 public sealed class DoAfterComponent : Component
 {
-    [DataField("doAfters")]
-    public readonly Dictionary<byte, DoAfter> DoAfters = new();
+    [DataField("nextId")]
+    public ushort NextId;
 
-    [DataField("cancelledDoAfters")]
-    public readonly Dictionary<byte, DoAfter> CancelledDoAfters = new();
+    [DataField("doAfters")]
+    public readonly Dictionary<ushort, DoAfter> 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.
-    [DataField("runningIndex")]
-    public byte RunningIndex;
+    // Used by obsolete async do afters
+    public readonly Dictionary<ushort, TaskCompletionSource<DoAfterStatus>> AwaitedDoAfters = new();
 }
 
 [Serializable, NetSerializable]
 public sealed class DoAfterComponentState : ComponentState
 {
-    public Dictionary<byte, DoAfter> DoAfters;
-
-    public DoAfterComponentState(Dictionary<byte, DoAfter> doAfters)
-    {
-        DoAfters = doAfters;
-    }
-}
-
-/// <summary>
-/// Use this event to raise your DoAfter events now.
-/// Check for cancelled, and if it is, then null the token there.
-/// </summary>
-/// TODO: Add a networked DoAfterEvent to pass in AdditionalData for the future
-[Serializable, NetSerializable]
-public sealed class DoAfterEvent : HandledEntityEventArgs
-{
-    public bool Cancelled;
-    public byte Id;
-    public readonly DoAfterEventArgs Args;
-
-    public DoAfterEvent(bool cancelled, DoAfterEventArgs args, byte id)
-    {
-        Cancelled = cancelled;
-        Args = args;
-        Id = id;
-    }
-}
-
-/// <summary>
-/// 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
-/// </summary>
-/// TODO: Net/Serilization isn't supported so this needs to be networked somehow
-public sealed class DoAfterEvent<T> : HandledEntityEventArgs
-{
-    public T AdditionalData;
-    public bool Cancelled;
-    public byte Id;
-    public readonly DoAfterEventArgs Args;
-
-    public DoAfterEvent(T additionalData, bool cancelled, DoAfterEventArgs args, byte id)
-    {
-        AdditionalData = additionalData;
-        Cancelled = cancelled;
-        Args = args;
-        Id = id;
-    }
-}
-
-[Serializable, NetSerializable]
-public sealed class CancelledDoAfterMessage : EntityEventArgs
-{
-    public EntityUid Uid;
-    public byte ID;
+    public readonly ushort NextId;
+    public readonly Dictionary<ushort, DoAfter> DoAfters;
 
-    public CancelledDoAfterMessage(EntityUid uid, byte id)
+    public DoAfterComponentState(DoAfterComponent component)
     {
-        Uid = uid;
-        ID = id;
+        NextId = component.NextId;
+        DoAfters = component.DoAfters;
     }
 }
 
 [Serializable, NetSerializable]
 public enum DoAfterStatus : byte
 {
+    Invalid,
     Running,
     Cancelled,
     Finished,
diff --git a/Content.Shared/DoAfter/DoAfterEvent.cs b/Content.Shared/DoAfter/DoAfterEvent.cs
new file mode 100644 (file)
index 0000000..07aa48b
--- /dev/null
@@ -0,0 +1,81 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DoAfter;
+
+/// <summary>
+///     Base type for events that get raised when a do-after is canceled or finished.
+/// </summary>
+[Serializable, NetSerializable]
+[ImplicitDataDefinitionForInheritors]
+public abstract class DoAfterEvent : HandledEntityEventArgs
+{
+    /// <summary>
+    ///     The do after that triggered this event. This will be set by the do after system before the event is raised.
+    /// </summary>
+    [NonSerialized]
+    public DoAfter DoAfter = default!;
+
+    /// <summary>
+    ///     Duplicate the current event. This is used by state handling, and should copy by value unless the reference
+    ///     types are immutable.
+    /// </summary>
+    public abstract DoAfterEvent Clone();
+
+    #region Convenience properties
+    public bool Cancelled => DoAfter.Cancelled;
+    public EntityUid User => DoAfter.Args.User;
+    public EntityUid? Target => DoAfter.Args.Target;
+    public EntityUid? Used => DoAfter.Args.Used;
+    public DoAfterArgs Args => DoAfter.Args;
+    #endregion
+}
+
+/// <summary>
+///     Blank / empty event for simple do afters that carry no information.
+/// </summary>
+/// <remarks>
+///     This just exists as a convenience to avoid having to re-implement Clone() for every simply DoAfterEvent.
+///     If an event actually contains data, it should actually override Clone().
+/// </remarks>
+[Serializable, NetSerializable]
+public abstract class SimpleDoAfterEvent : DoAfterEvent
+{
+    // TODO: Find some way to enforce that inheritors don't store data?
+    // Alternatively, I just need to allow generics to be networked.
+    // E.g., then a SimpleDoAfter<TEvent> would just raise a TEvent event.
+    // But afaik generic event types currently can't be serialized for networking or YAML.
+
+    public override DoAfterEvent Clone() => this;
+}
+
+// Placeholder for obsolete async do afters
+[Serializable, NetSerializable]
+[Obsolete("Dont use async DoAfters")]
+public sealed class AwaitedDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+///     This event will optionally get raised every tick while a do-after is in progress to check whether the do-after
+///     should be canceled.
+/// </summary>
+public sealed class DoAfterAttemptEvent<TEvent> : CancellableEntityEventArgs where TEvent : DoAfterEvent
+{
+    /// <summary>
+    ///     The do after that triggered this event.
+    /// </summary>
+    public readonly DoAfter DoAfter;
+
+    /// <summary>
+    ///     The event that the DoAfter will raise after sucesfully finishing. Given that this event has the data
+    ///     required to perform the interaction, it should also contain the data required to validate/attempt the
+    ///     interaction.
+    /// </summary>
+    public readonly TEvent Event;
+
+    public DoAfterAttemptEvent(DoAfter doAfter, TEvent @event)
+    {
+        DoAfter = doAfter;
+        Event = @event;
+    }
+}
diff --git a/Content.Shared/DoAfter/DoAfterEventArgs.cs b/Content.Shared/DoAfter/DoAfterEventArgs.cs
deleted file mode 100644 (file)
index 1aaa90e..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-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
-{
-    /// <summary>
-    ///     The entity invoking do_after
-    /// </summary>
-    public EntityUid User;
-
-    /// <summary>
-    ///     How long does the do_after require to complete
-    /// </summary>
-    public float Delay;
-
-    /// <summary>
-    ///     Applicable target (if relevant)
-    /// </summary>
-    public EntityUid? Target;
-
-    /// <summary>
-    ///     Entity used by the User on the Target.
-    /// </summary>
-    public EntityUid? Used;
-
-    public bool RaiseOnUser = true;
-
-    public bool RaiseOnTarget = true;
-
-    public bool RaiseOnUsed = true;
-
-    /// <summary>
-    ///     Manually cancel the do_after so it no longer runs
-    /// </summary>
-    [NonSerialized]
-    public CancellationToken CancelToken;
-
-    // Break the chains
-    /// <summary>
-    ///     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).
-    /// </summary>
-    public bool NeedHand;
-
-    /// <summary>
-    ///     If do_after stops when the user moves
-    /// </summary>
-    public bool BreakOnUserMove;
-
-    /// <summary>
-    ///     If do_after stops when the target moves (if there is a target)
-    /// </summary>
-    public bool BreakOnTargetMove;
-
-    /// <summary>
-    ///     Threshold for user and target movement
-    /// </summary>
-    public float MovementThreshold;
-
-    public bool BreakOnDamage;
-
-    /// <summary>
-    ///     Threshold for user damage
-    /// </summary>
-    public FixedPoint2? DamageThreshold;
-    public bool BreakOnStun;
-
-    /// <summary>
-    /// Should the DoAfter event broadcast?
-    /// </summary>
-    public bool Broadcast;
-
-    /// <summary>
-    ///     Threshold for distance user from the used OR target entities.
-    /// </summary>
-    public float? DistanceThreshold;
-
-    /// <summary>
-    ///     Requires a function call once at the end (like InRangeUnobstructed).
-    /// </summary>
-    /// <remarks>
-    ///     Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client.
-    /// </remarks>
-    [NonSerialized]
-    //TODO: Replace with eventbus
-    public Func<bool>? PostCheck;
-
-    /// <summary>
-    ///     Additional conditions that need to be met. Return false to cancel.
-    /// </summary>
-    [NonSerialized]
-    //TODO Replace with eventbus
-    public Func<bool>? 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/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs
new file mode 100644 (file)
index 0000000..a630662
--- /dev/null
@@ -0,0 +1,199 @@
+using Content.Shared.Hands.Components;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.DoAfter;
+
+public abstract partial class SharedDoAfterSystem : EntitySystem
+{
+    [Dependency] private readonly IDynamicTypeFactory _factory = default!;
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var time = GameTiming.CurTime;
+        var xformQuery = GetEntityQuery<TransformComponent>();
+        var handsQuery = GetEntityQuery<SharedHandsComponent>();
+
+        var enumerator = EntityQueryEnumerator<ActiveDoAfterComponent, DoAfterComponent>();
+        while (enumerator.MoveNext(out var uid, out var active, out var comp))
+        {
+            Update(uid, active, comp, time, xformQuery, handsQuery);
+        }
+    }
+
+    protected void Update(
+        EntityUid uid,
+        ActiveDoAfterComponent active,
+        DoAfterComponent comp,
+        TimeSpan time,
+        EntityQuery<TransformComponent> xformQuery,
+        EntityQuery<SharedHandsComponent> handsQuery)
+    {
+        var dirty = false;
+
+        foreach (var doAfter in comp.DoAfters.Values)
+        {
+            if (doAfter.CancelledTime != null)
+            {
+                if (time - doAfter.CancelledTime.Value > ExcessTime)
+                {
+                    comp.DoAfters.Remove(doAfter.Index);
+                    dirty = true;
+                }
+                continue;
+            }
+
+            if (doAfter.Completed)
+            {
+                if (time - doAfter.StartTime > doAfter.Args.Delay + ExcessTime)
+                {
+                    comp.DoAfters.Remove(doAfter.Index);
+                    dirty = true;
+                }
+                continue;
+            }
+
+            if (ShouldCancel(doAfter, xformQuery, handsQuery))
+            {
+                InternalCancel(doAfter, comp);
+                dirty = true;
+                continue;
+            }
+
+            if (time - doAfter.StartTime >= doAfter.Args.Delay)
+            {
+                TryComplete(doAfter, comp);
+                dirty = true;
+            }
+        }
+
+        if (dirty)
+            Dirty(comp);
+
+        if (comp.DoAfters.Count == 0)
+            RemCompDeferred(uid, active);
+    }
+
+    private bool TryAttemptEvent(DoAfter doAfter)
+    {
+        var args = doAfter.Args;
+
+        if (args.ExtraCheck?.Invoke() == false)
+            return false;
+
+        if (doAfter.AttemptEvent == null)
+        {
+            // I feel like this is somewhat cursed, but its the only way I can think of without having to just send
+            // redundant data over the network and increasing DoAfter boilerplate.
+            var evType = typeof(DoAfterAttemptEvent<>).MakeGenericType(args.Event.GetType());
+            doAfter.AttemptEvent = _factory.CreateInstance(evType, new object[] { doAfter, args.Event });
+        }
+
+        if (args.EventTarget != null)
+            RaiseLocalEvent(args.EventTarget.Value, doAfter.AttemptEvent, args.Broadcast);
+        else
+            RaiseLocalEvent(doAfter.AttemptEvent);
+
+        var ev = (CancellableEntityEventArgs) doAfter.AttemptEvent;
+        if (!ev.Cancelled)
+            return true;
+
+        ev.Uncancel();
+        return false;
+    }
+
+    private void TryComplete(DoAfter doAfter, DoAfterComponent component)
+    {
+        if (doAfter.Cancelled || doAfter.Completed)
+            return;
+
+        // Perform final check (if required)
+        if (doAfter.Args.AttemptFrequency == AttemptFrequency.StartAndEnd
+            && !TryAttemptEvent(doAfter))
+        {
+            InternalCancel(doAfter, component);
+            return;
+        }
+
+        doAfter.Completed = true;
+        RaiseDoAfterEvents(doAfter, component);
+    }
+
+    private bool ShouldCancel(DoAfter doAfter,
+        EntityQuery<TransformComponent> xformQuery,
+        EntityQuery<SharedHandsComponent> handsQuery)
+    {
+        var args = doAfter.Args;
+
+        //re-using xformQuery for Exists() checks.
+        if (args.Used is { } used && !xformQuery.HasComponent(used))
+            return true;
+
+        if (args.EventTarget is {Valid: true} eventTarget && !xformQuery.HasComponent(eventTarget))
+            return true;
+
+        if (!xformQuery.TryGetComponent(args.User, out var userXform))
+            return true;
+
+        TransformComponent? targetXform = null;
+        if (args.Target is { } target && !xformQuery.TryGetComponent(target, out targetXform))
+            return true;
+
+        TransformComponent? usedXform = null;
+        if (args.Used is { } @using && !xformQuery.TryGetComponent(@using, out usedXform))
+            return true;
+
+        // TODO: Handle Inertia in space
+        // TODO: Re-use existing xform query for these calculations.
+        if (args.BreakOnUserMove && !userXform.Coordinates
+                .InRange(EntityManager, _transform, doAfter.UserPosition, args.MovementThreshold))
+            return true;
+
+        if (args.BreakOnTargetMove)
+        {
+            DebugTools.Assert(targetXform != null, "Break on move is true, but no target specified?");
+            if (targetXform != null && !targetXform.Coordinates.InRange(EntityManager, _transform,
+                    doAfter.TargetPosition, args.MovementThreshold))
+                return true;
+        }
+
+        if (args.AttemptFrequency == AttemptFrequency.EveryTick && !TryAttemptEvent(doAfter))
+            return true;
+
+        if (args.NeedHand)
+        {
+            if (!handsQuery.TryGetComponent(args.User, out var hands) || hands.Count == 0)
+                return true;
+
+            if (args.BreakOnHandChange && (hands.ActiveHand?.Name != doAfter.InitialHand
+                                           || hands.ActiveHandEntity != doAfter.InitialItem))
+            {
+                return true;
+            }
+        }
+
+        if (args.RequireCanInteract && !_actionBlocker.CanInteract(args.User, args.Target))
+            return true;
+
+        if (args.DistanceThreshold != null)
+        {
+            if (targetXform != null
+                && !args.User.Equals(args.Target)
+                && !userXform.Coordinates.InRange(EntityManager, _transform, targetXform.Coordinates,
+                    args.DistanceThreshold.Value))
+            {
+                return true;
+            }
+
+            if (usedXform != null
+                && !userXform.Coordinates.InRange(EntityManager, _transform, usedXform.Coordinates,
+                    args.DistanceThreshold.Value))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
index 15cddd29c58a39fd9f1d84c955b5d2a4b0a0498c..7d0efb91aa7e941d601ceca36c1bba257e168a2c 100644 (file)
-using System.Linq;
-using System.Threading;
+using System.Diagnostics.CodeAnalysis;
 using System.Threading.Tasks;
+using Content.Shared.ActionBlocker;
 using Content.Shared.Damage;
 using Content.Shared.Hands.Components;
 using Content.Shared.Mobs;
-using Content.Shared.Stunnable;
 using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.DoAfter;
 
-public abstract class SharedDoAfterSystem : EntitySystem
+public abstract partial class SharedDoAfterSystem : EntitySystem
 {
     [Dependency] protected readonly IGameTiming GameTiming = default!;
+    [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
 
-    // We cache the list as to not allocate every update tick...
-    private readonly Queue<DoAfter> _pending = new();
+    /// <summary>
+    ///     We'll use an excess time so stuff like finishing effects can show.
+    /// </summary>
+    private static readonly TimeSpan ExcessTime = TimeSpan.FromSeconds(0.5f);
 
     public override void Initialize()
     {
         base.Initialize();
         SubscribeLocalEvent<DoAfterComponent, DamageChangedEvent>(OnDamage);
+        SubscribeLocalEvent<DoAfterComponent, EntityUnpausedEvent>(OnUnpaused);
         SubscribeLocalEvent<DoAfterComponent, MobStateChangedEvent>(OnStateChanged);
         SubscribeLocalEvent<DoAfterComponent, ComponentGetState>(OnDoAfterGetState);
+        SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
     }
 
-    public bool DoAfterExists(EntityUid uid, DoAfter doAFter, DoAfterComponent? component = null)
-        => DoAfterExists(uid, doAFter.ID, component);
-
-    public bool DoAfterExists(EntityUid uid, byte id, DoAfterComponent? component = null)
+    private void OnUnpaused(EntityUid uid, DoAfterComponent component, ref EntityUnpausedEvent args)
     {
-        if (!Resolve(uid, ref component))
-            return false;
-
-        return component.DoAfters.ContainsKey(id);
-    }
+        foreach (var doAfter in component.DoAfters.Values)
+        {
+            doAfter.StartTime += args.PausedTime;
+            if (doAfter.CancelledTime != null)
+                doAfter.CancelledTime = doAfter.CancelledTime.Value + args.PausedTime;
+        }
 
-    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<ActiveDoAfterComponent>(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<ActiveDoAfterComponent>(component.Owner);
-
-        RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index.ID));
-    }
-
-    /// <summary>
-    ///     Call when the particular DoAfter is finished.
-    ///     Client should be tracking this independently.
-    /// </summary>
-    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<ActiveDoAfterComponent>(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)
+        foreach (var doAfter in component.DoAfters.Values)
         {
-            Cancel(uid, doAfter, component);
+            InternalCancel(doAfter, component);
         }
+        Dirty(component);
     }
 
     /// <summary>
     /// Cancels DoAfter if it breaks on damage and it meets the threshold
     /// </summary>
-    /// <param name="uid">The EntityUID of the user</param>
-    /// <param name="component"></param>
-    /// <param name="args"></param>
     private void OnDamage(EntityUid uid, DoAfterComponent component, DamageChangedEvent args)
     {
         if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null)
             return;
 
+        var delta = args.DamageDelta?.Total;
+
+        var dirty = false;
         foreach (var doAfter in component.DoAfters.Values)
         {
-            if (doAfter.EventArgs.BreakOnDamage && args.DamageDelta?.Total.Float() > doAfter.EventArgs.DamageThreshold)
-                Cancel(uid, doAfter, component);
+            if (doAfter.Args.BreakOnDamage && delta >= doAfter.Args.DamageThreshold)
+            {
+                InternalCancel(doAfter, component);
+                dirty = true;
+            }
         }
+
+        if (dirty)
+            Dirty(component);
     }
 
-    public override void Update(float frameTime)
+    private void RaiseDoAfterEvents(DoAfter doAfter, DoAfterComponent component)
     {
-        base.Update(frameTime);
+        var ev = doAfter.Args.Event;
+        ev.DoAfter = doAfter;
 
-        foreach (var (_, comp) in EntityManager.EntityQuery<ActiveDoAfterComponent, DoAfterComponent>())
-        {
-            //Don't run the doafter if its comp or owner is deleted.
-            if (EntityManager.Deleted(comp.Owner) || comp.Deleted)
-                continue;
+        if (Exists(doAfter.Args.EventTarget))
+            RaiseLocalEvent(doAfter.Args.EventTarget.Value, (object)ev, doAfter.Args.Broadcast);
+        else if (doAfter.Args.Broadcast)
+            RaiseLocalEvent((object)ev);
 
-            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();
-                }
-            }
+        if (component.AwaitedDoAfters.Remove(doAfter.Index, out var tcs))
+            tcs.SetResult(doAfter.Cancelled ? DoAfterStatus.Cancelled : DoAfterStatus.Finished);
+    }
 
-            while (_pending.TryDequeue(out var doAfter))
-            {
-                if (doAfter.Status == DoAfterStatus.Cancelled)
-                {
-                    Cancelled(comp, doAfter);
+    private void OnDoAfterGetState(EntityUid uid, DoAfterComponent comp, ref ComponentGetState args)
+    {
+        args.State = new DoAfterComponentState(comp);
+    }
 
-                    if (doAfter.Done != null)
-                        doAfter.Done(true);
-                }
+    private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent comp, ref ComponentHandleState args)
+    {
+        if (args.Current is not DoAfterComponentState state)
+            return;
 
-                if (doAfter.Status == DoAfterStatus.Finished)
-                {
-                    Finished(comp, doAfter);
+        // Note that the client may have correctly predicted the creation of a do-after, but that doesn't guarantee that
+        // the contents of the do-after data are correct. So this just takes the brute force approach and completely
+        // overwrites the state.
 
-                    if (doAfter.Done != null)
-                        doAfter.Done(false);
-                }
-            }
+        comp.DoAfters.Clear();
+        foreach (var (id, doAfter) in state.DoAfters)
+        {
+            comp.DoAfters.Add(id, new(doAfter));
         }
+
+        comp.NextId = state.NextId;
+        DebugTools.Assert(!comp.DoAfters.ContainsKey(comp.NextId));
+
+        if (comp.DoAfters.Count == 0)
+            RemCompDeferred<ActiveDoAfterComponent>(uid);
+        else
+            EnsureComp<ActiveDoAfterComponent>(uid);
     }
 
+
+    #region Creation
     /// <summary>
     ///     Tasks that are delayed until the specified time has passed
     ///     These can be potentially cancelled by the user moving or when other things happen.
     /// </summary>
-    /// <param name="eventArgs"></param>
-    /// <returns></returns>
-    [Obsolete("Use the synchronous version instead, DoAfter")]
-    public async Task<DoAfterStatus> WaitDoAfter(DoAfterEventArgs eventArgs)
+    // TODO remove this, as well as AwaitedDoAfterEvent and DoAfterComponent.AwaitedDoAfters
+    [Obsolete("Use the synchronous version instead.")]
+    public async Task<DoAfterStatus> WaitDoAfter(DoAfterArgs doAfter, DoAfterComponent? component = null)
     {
-        var doAfter = CreateDoAfter(eventArgs);
+        if (!Resolve(doAfter.User, ref component))
+            return DoAfterStatus.Cancelled;
 
-        await doAfter.AsTask;
+        if (!TryStartDoAfter(doAfter, out var id, component))
+            return DoAfterStatus.Cancelled;
 
-        return doAfter.Status;
+        var tcs = new TaskCompletionSource<DoAfterStatus>();
+        component.AwaitedDoAfters.Add(id.Value.Index, tcs);
+        return await tcs.Task;
     }
 
     /// <summary>
-    ///     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
+    ///     Attempts to start a new DoAfter. Note that even if this function returns true, an interaction may have
+    ///     occured, as starting a duplicate DoAfter may cancel currently running DoAfters.
     /// </summary>
-    /// <param name="eventArgs">The DoAfterEventArgs</param>
-    /// <param name="data">The extra data sent over </param>
-    public DoAfter DoAfter<T>(DoAfterEventArgs eventArgs, T data)
-    {
-        var doAfter = CreateDoAfter(eventArgs);
-        doAfter.Done = cancelled => { Send(data, cancelled, eventArgs, doAfter.ID); };
-        return doAfter;
-    }
+    /// <param name="args">The DoAfter arguments</param>
+    /// <param name="component">The user's DoAfter component</param>
+    /// <returns></returns>
+    public bool TryStartDoAfter(DoAfterArgs args, DoAfterComponent? component = null)
+        => TryStartDoAfter(args, out _, component);
 
     /// <summary>
-    ///     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
+    ///     Attempts to start a new DoAfter. Note that even if this function returns false, an interaction may have
+    ///     occured, as starting a duplicate DoAfter may cancel currently running DoAfters.
     /// </summary>
-    /// <param name="eventArgs">The DoAfterEventArgs</param>
-    public DoAfter DoAfter(DoAfterEventArgs eventArgs)
+    /// <param name="args">The DoAfter arguments</param>
+    /// <param name="id">The Id of the newly started DoAfter</param>
+    /// <param name="comp">The user's DoAfter component</param>
+    /// <returns></returns>
+    public bool TryStartDoAfter(DoAfterArgs args, [NotNullWhen(true)] out DoAfterId? id, DoAfterComponent? comp = null)
     {
-        var doAfter = CreateDoAfter(eventArgs);
-        doAfter.Done = cancelled => { Send(cancelled, eventArgs, doAfter.ID); };
-        return doAfter;
-    }
+        DebugTools.Assert(args.Broadcast || Exists(args.EventTarget) || args.Event.GetType() == typeof(AwaitedDoAfterEvent));
+        DebugTools.Assert(args.Event.GetType().HasCustomAttribute<NetSerializableAttribute>()
+            || args.Event.GetType().Namespace is {} ns && ns.StartsWith("Content.IntegrationTests"), // classes defined in tests cannot be marked as serializable.
+            $"Do after event is not serializable. Event: {args.Event.GetType()}");
 
-    private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs)
-    {
-        // Setup
-        var doAfter = new DoAfter(eventArgs, EntityManager);
-        // Caller's gonna be responsible for this I guess
-        var doAfterComponent = Comp<DoAfterComponent>(eventArgs.User);
-        doAfter.ID = doAfterComponent.RunningIndex;
-        doAfter.StartTime = GameTiming.CurTime;
-        Add(eventArgs.User, doAfterComponent, doAfter);
-        return doAfter;
-    }
+        if (!Resolve(args.User, ref comp))
+        {
+            Logger.Error($"Attempting to start a doAfter with invalid user: {ToPrettyString(args.User)}.");
+            id = null;
+            return false;
+        }
 
-    private void Run(EntityUid entity, DoAfterComponent comp, DoAfter doAfter)
-    {
-        switch (doAfter.Status)
+        // Duplicate blocking & cancellation.
+        if (!ProcessDuplicates(args, comp))
         {
-            case DoAfterStatus.Running:
-                break;
-            case DoAfterStatus.Cancelled:
-            case DoAfterStatus.Finished:
-                return;
-            default:
-                throw new ArgumentOutOfRangeException();
+            id = null;
+            return false;
         }
 
-        doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime;
+        id = new DoAfterId(args.User, comp.NextId++);
+        var doAfter = new DoAfter(id.Value.Index, args, GameTiming.CurTime);
 
-        if (IsFinished(doAfter))
-        {
-            if (!TryPostCheck(doAfter))
-            {
-                Cancel(entity, doAfter, comp);
-            }
-            else
-            {
-                doAfter.Tcs.SetResult(DoAfterStatus.Finished);
-            }
+        if (args.BreakOnUserMove)
+            doAfter.UserPosition = Transform(args.User).Coordinates;
 
-            return;
-        }
+        if (args.Target != null && args.BreakOnTargetMove)
+            // Target should never be null if the bool is set.
+            doAfter.TargetPosition = Transform(args.Target.Value).Coordinates;
 
-        if (IsCancelled(doAfter))
+        // 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 (args.NeedHand && args.BreakOnHandChange)
         {
-            Cancel(entity, doAfter, comp);
-        }
-    }
+            if (!TryComp(args.User, out SharedHandsComponent? handsComponent))
+                return false;
 
-    private bool TryPostCheck(DoAfter doAfter)
-    {
-        return doAfter.EventArgs.PostCheck?.Invoke() != false;
-    }
+            doAfter.InitialHand = handsComponent.ActiveHand?.Name;
+            doAfter.InitialItem = handsComponent.ActiveHandEntity;
+        }
 
-    private bool IsFinished(DoAfter doAfter)
-    {
-        var delay = TimeSpan.FromSeconds(doAfter.EventArgs.Delay);
+        // Inital checks
+        if (ShouldCancel(doAfter, GetEntityQuery<TransformComponent>(), GetEntityQuery<SharedHandsComponent>()))
+            return false;
 
-        if (doAfter.Elapsed <= delay)
+        if (args.AttemptFrequency == AttemptFrequency.StartAndEnd && !TryAttemptEvent(doAfter))
             return false;
 
+        if (args.Delay <= TimeSpan.Zero)
+        {
+            RaiseDoAfterEvents(doAfter, comp);
+            // We don't store instant do-afters. This is just a lazy way of hiding them from client-side visuals.
+            return true;
+        }
+
+        comp.DoAfters.Add(doAfter.Index, doAfter);
+        EnsureComp<ActiveDoAfterComponent>(args.User);
+        Dirty(comp);
+        args.Event.DoAfter = doAfter;
         return true;
     }
 
-    private bool IsCancelled(DoAfter doAfter)
+    /// <summary>
+    ///     Cancel any applicable duplicate DoAfters and return whether or not the new DoAfter should be created.
+    /// </summary>
+    private bool ProcessDuplicates(DoAfterArgs args, DoAfterComponent component)
     {
-        var eventArgs = doAfter.EventArgs;
-        var xForm = GetEntityQuery<TransformComponent>();
+        var blocked = false;
+        foreach (var existing in component.DoAfters.Values)
+        {
+            if (existing.Cancelled || existing.Completed)
+                continue;
 
-        if (!Exists(eventArgs.User) || eventArgs.Target is { } target && !Exists(target))
-            return true;
+            if (!IsDuplicate(existing.Args, args))
+                continue;
 
-        if (eventArgs.CancelToken.IsCancellationRequested)
-            return true;
+            blocked = blocked | args.BlockDuplicate | existing.Args.BlockDuplicate;
 
-        //TODO: Handle Inertia in space
-        if (eventArgs.BreakOnUserMove && !xForm.GetComponent(eventArgs.User).Coordinates
-                .InRange(EntityManager, doAfter.UserGrid, eventArgs.MovementThreshold))
-            return true;
+            if (args.CancelDuplicate || existing.Args.CancelDuplicate)
+                Cancel(args.User, existing.Index, component);
+        }
 
-        if (eventArgs.Target != null && eventArgs.BreakOnTargetMove && !xForm.GetComponent(eventArgs.Target!.Value)
-                .Coordinates.InRange(EntityManager, doAfter.TargetGrid, eventArgs.MovementThreshold))
-            return true;
+        return !blocked;
+    }
 
-        if (eventArgs.ExtraCheck != null && !eventArgs.ExtraCheck.Invoke())
+    private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs)
+    {
+        if (IsDuplicate(args, otherArgs, args.DuplicateCondition))
             return true;
 
-        if (eventArgs.BreakOnStun && HasComp<StunnedComponent>(eventArgs.User))
-            return true;
+        if (args.DuplicateCondition == otherArgs.DuplicateCondition)
+            return false;
 
-        if (eventArgs.NeedHand)
-        {
-            if (!TryComp<SharedHandsComponent>(eventArgs.User, out var handsComp))
-            {
-                //TODO: Figure out active hand and item values
+        return IsDuplicate(args, otherArgs, otherArgs.DuplicateCondition);
+    }
 
-                // 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;
+    private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs, DuplicateConditions conditions )
+    {
+        if ((conditions & DuplicateConditions.SameTarget) != 0
+            && args.Target != otherArgs.Target)
+        {
+            return false;
+        }
 
-                var currentItem = handsComp.ActiveHandEntity;
-                if (doAfter.ActiveItem != currentItem)
-                    return true;
-            }
+        if ((conditions & DuplicateConditions.SameTool) != 0
+            && args.Used != otherArgs.Used)
+        {
+            return false;
         }
 
-        if (eventArgs.DistanceThreshold != null)
+        if ((conditions & DuplicateConditions.SameEvent) != 0
+            && args.Event.GetType() != otherArgs.Event.GetType())
         {
-            var userXform = xForm.GetComponent(eventArgs.User);
+            return false;
+        }
 
-            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;
-            }
+        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;
-            }
-        }
+    #endregion
 
-        return false;
+    #region Cancellation
+    /// <summary>
+    ///     Cancels an active DoAfter.
+    /// </summary>
+    public void Cancel(DoAfterId? id, DoAfterComponent? comp = null)
+    {
+        if (id != null)
+            Cancel(id.Value.Uid, id.Value.Index, comp);
     }
 
-    public void Cancel(EntityUid entity, DoAfter doAfter, DoAfterComponent? comp = null)
+    /// <summary>
+    ///     Cancels an active DoAfter.
+    /// </summary>
+    public void Cancel(EntityUid entity, ushort id, DoAfterComponent? comp = null)
     {
         if (!Resolve(entity, ref comp, false))
             return;
 
-        if (comp.CancelledDoAfters.ContainsKey(doAfter.ID))
+        if (!comp.DoAfters.TryGetValue(id, out var doAfter))
+        {
+            Logger.Error($"Attempted to cancel do after with an invalid id ({id}) on entity {ToPrettyString(entity)}");
             return;
+        }
 
-        if (!comp.DoAfters.ContainsKey(doAfter.ID))
+        InternalCancel(doAfter, comp);
+        Dirty(comp);
+    }
+
+    private void InternalCancel(DoAfter doAfter, DoAfterComponent component)
+    {
+        if (doAfter.Cancelled || doAfter.Completed)
             return;
 
-        doAfter.Cancelled = true;
+        // Caller is responsible for dirtying the component.
         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);
-        }
+        RaiseDoAfterEvents(doAfter, component);
     }
+    #endregion
 
+    #region Query
     /// <summary>
-    /// Send the DoAfter event, used where you don't need any extra data to send.
+    ///     Returns the current status of a DoAfter
     /// </summary>
-    /// <param name="cancelled"></param>
-    /// <param name="args"></param>
-    private void Send(bool cancelled, DoAfterEventArgs args, byte Id)
+    public DoAfterStatus GetStatus(DoAfterId? id, DoAfterComponent? comp = null)
     {
-        var ev = new DoAfterEvent(cancelled, args, Id);
-
-        RaiseDoAfterEvent(ev, args);
+        if (id != null)
+            return GetStatus(id.Value.Uid, id.Value.Index, comp);
+        else
+            return DoAfterStatus.Invalid;
     }
 
     /// <summary>
-    /// Send the DoAfter event, used where you need extra data to send
+    ///     Returns the current status of a DoAfter
     /// </summary>
-    /// <param name="data"></param>
-    /// <param name="cancelled"></param>
-    /// <param name="args"></param>
-    /// <typeparam name="T"></typeparam>
-    private void Send<T>(T data, bool cancelled, DoAfterEventArgs args, byte id)
+    public DoAfterStatus GetStatus(EntityUid entity, ushort id, DoAfterComponent? comp = null)
     {
-        var ev = new DoAfterEvent<T>(data, cancelled, args, id);
+        if (!Resolve(entity, ref comp, false))
+            return DoAfterStatus.Invalid;
 
-        RaiseDoAfterEvent(ev, args);
-    }
+        if (!comp.DoAfters.TryGetValue(id, out var doAfter))
+            return DoAfterStatus.Invalid;
 
-    private void RaiseDoAfterEvent<TEvent>(TEvent ev, DoAfterEventArgs args) where TEvent : notnull
-    {
-        if (args.RaiseOnUser && Exists(args.User))
-            RaiseLocalEvent(args.User, ev, args.Broadcast);
+        if (doAfter.Cancelled)
+            return DoAfterStatus.Cancelled;
 
-        if (args.RaiseOnTarget && args.Target is { } target && Exists(target))
-        {
-            DebugTools.Assert(!args.RaiseOnUser || args.Target != args.User);
-            DebugTools.Assert(!args.RaiseOnUsed || args.Target != args.Used);
-            RaiseLocalEvent(target, ev, args.Broadcast);
-        }
+        if (GameTiming.CurTime - doAfter.StartTime < doAfter.Args.Delay)
+            return DoAfterStatus.Running;
 
-        if (args.RaiseOnUsed && args.Used is { } used && Exists(used))
-        {
-            DebugTools.Assert(!args.RaiseOnUser || args.Used != args.User);
-            RaiseLocalEvent(used, ev, args.Broadcast);
-        }
+        // Theres the chance here that the DoAfter hasn't actually finished yet if the system's update hasn't run yet.
+        // This would also mean the post-DoAfter checks haven't run yet. But whatever, I can't be bothered tracking and
+        // networking whether a do-after has raised its events or not.
+        return DoAfterStatus.Finished;
     }
+    #endregion
 }
index 105a97f045c86237140ae9de483a660af8de4cc2..69fab8ee9e4911b425bff251c15a333cfe39756c 100644 (file)
@@ -75,8 +75,6 @@ public sealed class DoorComponent : Component, ISerializationHooks
     public bool Partial;
     #endregion
 
-    public bool BeingPried;
-
     #region Sounds
     /// <summary>
     /// Sound to play when the door opens.
index bb590a32d971539e85af4593b5cec168dd780678..6bbf943674ca9977e372e164f75cd010ba7f8864 100644 (file)
@@ -10,12 +10,14 @@ using Robust.Shared.Physics;
 using Robust.Shared.Physics.Dynamics;
 using Robust.Shared.Timing;
 using System.Linq;
+using Content.Shared.DoAfter;
 using Content.Shared.Tag;
 using Content.Shared.Tools.Components;
 using Content.Shared.Verbs;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Events;
 using Robust.Shared.Physics.Systems;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Doors.Systems;
 
@@ -636,4 +638,9 @@ public abstract class SharedDoorSystem : EntitySystem
     #endregion
 
     protected abstract void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted);
+
+    [Serializable, NetSerializable]
+    protected sealed class DoorPryDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
 }
diff --git a/Content.Shared/Dragon/DragonDevourDoAfterEvent.cs b/Content.Shared/Dragon/DragonDevourDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..2f5f0d5
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Dragon;
+
+[Serializable, NetSerializable]
+public sealed class DragonDevourDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index f7c4051814cb7ac828be878a209e11c6a30c995f..86f73f8ca97d2b0ef97f193e6cee22286ec8829e 100644 (file)
@@ -1,9 +1,16 @@
+using Content.Shared.DoAfter;
 using Content.Shared.Ensnaring.Components;
 using Content.Shared.Movement.Systems;
 using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Ensnaring;
 
+[Serializable, NetSerializable]
+public sealed class EnsnareableDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
 public abstract class SharedEnsnareableSystem : EntitySystem
 {
     [Dependency] private readonly MovementSpeedModifierSystem _speedModifier = default!;
@@ -67,7 +74,8 @@ public abstract class SharedEnsnareableSystem : EntitySystem
         Appearance.SetData(uid, EnsnareableVisuals.IsEnsnared, component.IsEnsnared, appearance);
     }
 
-    private void MovementSpeedModify(EntityUid uid, EnsnareableComponent component, RefreshMovementSpeedModifiersEvent args)
+    private void MovementSpeedModify(EntityUid uid, EnsnareableComponent component,
+        RefreshMovementSpeedModifiersEvent args)
     {
         if (!component.IsEnsnared)
             return;
diff --git a/Content.Shared/Exchanger/ExchangerDoAfterEvent.cs b/Content.Shared/Exchanger/ExchangerDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..0060cf7
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Exchanger;
+
+[Serializable, NetSerializable]
+public sealed class ExchangerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Fluids/Events.cs b/Content.Shared/Fluids/Events.cs
new file mode 100644 (file)
index 0000000..5cff7ea
--- /dev/null
@@ -0,0 +1,36 @@
+using Content.Shared.DoAfter;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Fluids;
+
+[Serializable, NetSerializable]
+public sealed class AbsorbantDoAfterEvent : DoAfterEvent
+{
+    [DataField("solution", required: true)]
+    public readonly string TargetSolution = default!;
+
+    [DataField("message", required: true)]
+    public readonly string Message = default!;
+
+    [DataField("sound", required: true)]
+    public readonly SoundSpecifier Sound = default!;
+
+    [DataField("transferAmount", required: true)]
+    public readonly FixedPoint2 TransferAmount;
+
+    private AbsorbantDoAfterEvent()
+    {
+    }
+
+    public AbsorbantDoAfterEvent(string targetSolution, string message, SoundSpecifier sound, FixedPoint2 transferAmount)
+    {
+        TargetSolution = targetSolution;
+        Message = message;
+        Sound = sound;
+        TransferAmount = transferAmount;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
diff --git a/Content.Shared/Forensics/Events.cs b/Content.Shared/Forensics/Events.cs
new file mode 100644 (file)
index 0000000..7db2965
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Forensics;
+
+[Serializable, NetSerializable]
+public sealed class ForensicScannerDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class ForensicPadDoAfterEvent : DoAfterEvent
+{
+    [DataField("sample", required: true)] public readonly string Sample = default!;
+
+    private ForensicPadDoAfterEvent()
+    {
+    }
+
+    public ForensicPadDoAfterEvent(string sample)
+    {
+        Sample = sample;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
diff --git a/Content.Shared/Gatherable/GatherableDoAfterEvent.cs b/Content.Shared/Gatherable/GatherableDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..4944ae3
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Gatherable;
+
+[Serializable, NetSerializable]
+public sealed class GatherableDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs b/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..98de647
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Guardian;
+
+[Serializable, NetSerializable]
+public sealed class GuardianCreatorDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index dc34639b8c8dca39a82fdfed946900eec61ddc8b..f98ac2a7327f93272e7f0749a5148d1241700a38 100644 (file)
@@ -1,5 +1,6 @@
 using System.Threading;
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
@@ -70,8 +71,6 @@ public sealed class ImplanterComponent : Component
     public ItemSlot ImplanterSlot = new();
 
     public bool UiUpdateNeeded;
-
-    public CancellationTokenSource? CancelToken;
 }
 
 [Serializable, NetSerializable]
index 995b9f829d67dbf62898cca5ffb418eec97a0183..30817feda1dda3e22ad0cc840b2f03715a62a80c 100644 (file)
@@ -1,10 +1,12 @@
 using System.Linq;
 using Content.Shared.Containers.ItemSlots;
+using Content.Shared.DoAfter;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Implants.Components;
 using Content.Shared.Popups;
 using Robust.Shared.Containers;
 using Robust.Shared.Player;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Implants;
 
@@ -21,7 +23,6 @@ public abstract class SharedImplanterSystem : EntitySystem
 
         SubscribeLocalEvent<ImplanterComponent, ComponentInit>(OnImplanterInit);
         SubscribeLocalEvent<ImplanterComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
-
     }
 
     private void OnImplanterInit(EntityUid uid, ImplanterComponent component, ComponentInit args)
@@ -95,7 +96,8 @@ public abstract class SharedImplanterSystem : EntitySystem
                 {
                     var implantName = Identity.Entity(implant, EntityManager);
                     var targetName = Identity.Entity(target, EntityManager);
-                    var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent", ("implant", implantName), ("target", targetName));
+                    var failedPermanentMessage = Loc.GetString("implanter-draw-failed-permanent",
+                        ("implant", implantName), ("target", targetName));
                     _popup.PopupEntity(failedPermanentMessage, target, user);
                     permanentFound = implantComp.Permanent;
                     continue;
@@ -147,10 +149,21 @@ public abstract class SharedImplanterSystem : EntitySystem
         else if (component.CurrentMode == ImplanterToggleMode.Inject && component.ImplantOnly)
         {
             _appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
-            _appearance.SetData(component.Owner, ImplanterImplantOnlyVisuals.ImplantOnly, component.ImplantOnly, appearance);
+            _appearance.SetData(component.Owner, ImplanterImplantOnlyVisuals.ImplantOnly, component.ImplantOnly,
+                appearance);
         }
 
         else
             _appearance.SetData(component.Owner, ImplanterVisuals.Full, implantFound, appearance);
     }
 }
+
+[Serializable, NetSerializable]
+public sealed class ImplantEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class DrawEvent : SimpleDoAfterEvent
+{
+}
index c3fbb299ab77df5b56472629eae47bde70b51c16..b9ae32cf5d2c48800202bc1eabe8b7669ab8c74c 100644 (file)
@@ -784,7 +784,6 @@ namespace Content.Shared.Interaction
                 return;
 
             // all interactions should only happen when in range / unobstructed, so no range check is needed
-            //TODO: See why this is firing off multiple times
             var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
             RaiseLocalEvent(target, interactUsingEvent, true);
             DoContactInteraction(user, used, interactUsingEvent);
diff --git a/Content.Shared/Internals/InternalsDoAfterEvent.cs b/Content.Shared/Internals/InternalsDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..98709a0
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Internals;
+
+[Serializable, NetSerializable]
+public sealed class InternalsDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index fa8d7a9e85da4fe804e6da58a0515193a9b53885..2a912cbba754a6f06b1087fc0e1bdc3837e56edf 100644 (file)
@@ -1,6 +1,8 @@
+using Content.Shared.DoAfter;
 using Content.Shared.DragDrop;
 using Content.Shared.Kitchen.Components;
 using Content.Shared.Nutrition.Components;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Kitchen;
 
@@ -29,3 +31,8 @@ public abstract class SharedKitchenSpikeSystem : EntitySystem
         args.CanDrop = true;
     }
 }
+
+[Serializable, NetSerializable]
+public sealed class SpikeDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/Kitchen/SharpDoAfterEvent.cs b/Content.Shared/Kitchen/SharpDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..1189fc8
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Kitchen;
+
+[Serializable, NetSerializable]
+public sealed class SharpDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Light/PoweredLightDoAfterEvent.cs b/Content.Shared/Light/PoweredLightDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..ac1a704
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Light;
+
+[Serializable, NetSerializable]
+public sealed class PoweredLightDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Magic/SpellbookDoAfterEvent.cs b/Content.Shared/Magic/SpellbookDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..7c05ecb
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Magic;
+
+[Serializable, NetSerializable]
+public sealed class SpellbookDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index 70e8405240f80254eca44c74ea09a7f9fc04395a..a41ec7ab618075888913fc9ef922036c3673aaf9 100644 (file)
@@ -5,6 +5,7 @@ using Content.Shared.ActionBlocker;
 using Content.Shared.Actions;
 using Content.Shared.Actions.ActionTypes;
 using Content.Shared.Destructible;
+using Content.Shared.DoAfter;
 using Content.Shared.FixedPoint;
 using Content.Shared.Hands.Components;
 using Content.Shared.Interaction;
@@ -19,6 +20,7 @@ using Content.Shared.Weapons.Melee;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Mech.EntitySystems;
@@ -60,6 +62,7 @@ public abstract class SharedMechSystem : EntitySystem
     }
 
     #region State Handling
+
     private void OnGetState(EntityUid uid, SharedMechComponent component, ref ComponentGetState args)
     {
         args.State = new MechComponentState
@@ -101,6 +104,7 @@ public abstract class SharedMechSystem : EntitySystem
 
         component.Mech = state.Mech;
     }
+
     #endregion
 
     private void OnToggleEquipmentAction(EntityUid uid, SharedMechComponent component, MechToggleEquipmentEvent args)
@@ -175,9 +179,12 @@ public abstract class SharedMechSystem : EntitySystem
         rider.Mech = mech;
         Dirty(rider);
 
-        _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechCycleAction)), mech);
-        _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechUiAction)), mech);
-        _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechEjectAction)), mech);
+        _actions.AddAction(pilot,
+            new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechCycleAction)), mech);
+        _actions.AddAction(pilot, new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechUiAction)),
+            mech);
+        _actions.AddAction(pilot,
+            new InstantAction(_prototype.Index<InstantActionPrototype>(component.MechEjectAction)), mech);
     }
 
     private void RemoveUser(EntityUid mech, EntityUid pilot)
@@ -252,7 +259,8 @@ public abstract class SharedMechSystem : EntitySystem
     /// <param name="toInsert"></param>
     /// <param name="component"></param>
     /// <param name="equipmentComponent"></param>
-    public void InsertEquipment(EntityUid uid, EntityUid toInsert, SharedMechComponent? component = null, MechEquipmentComponent? equipmentComponent = null)
+    public void InsertEquipment(EntityUid uid, EntityUid toInsert, SharedMechComponent? component = null,
+        MechEquipmentComponent? equipmentComponent = null)
     {
         if (!Resolve(uid, ref component))
             return;
@@ -281,7 +289,8 @@ public abstract class SharedMechSystem : EntitySystem
     /// <param name="component"></param>
     /// <param name="equipmentComponent"></param>
     /// <param name="forced">Whether or not the removal can be cancelled</param>
-    public void RemoveEquipment(EntityUid uid, EntityUid toRemove, SharedMechComponent? component = null, MechEquipmentComponent? equipmentComponent = null, bool forced = false)
+    public void RemoveEquipment(EntityUid uid, EntityUid toRemove, SharedMechComponent? component = null,
+        MechEquipmentComponent? equipmentComponent = null, bool forced = false)
     {
         if (!Resolve(uid, ref component))
             return;
@@ -296,6 +305,7 @@ public abstract class SharedMechSystem : EntitySystem
             if (attemptev.Cancelled)
                 return;
         }
+
         var ev = new MechEquipmentRemovedEvent(uid);
         RaiseLocalEvent(toRemove, ref ev);
 
@@ -388,7 +398,6 @@ public abstract class SharedMechSystem : EntitySystem
     /// </remarks>
     public virtual void UpdateUserInterface(EntityUid uid, SharedMechComponent? component = null)
     {
-
     }
 
     /// <summary>
@@ -461,7 +470,8 @@ public abstract class SharedMechSystem : EntitySystem
             args.Cancel();
     }
 
-    private void UpdateAppearance(EntityUid uid, SharedMechComponent ? component = null, AppearanceComponent? appearance = null)
+    private void UpdateAppearance(EntityUid uid, SharedMechComponent? component = null,
+        AppearanceComponent? appearance = null)
     {
         if (!Resolve(uid, ref component, ref appearance, false))
             return;
@@ -470,3 +480,29 @@ public abstract class SharedMechSystem : EntitySystem
         _appearance.SetData(uid, MechVisuals.Broken, component.Broken, appearance);
     }
 }
+
+/// <summary>
+///     Event raised when the battery is successfully removed from the mech,
+///     on both success and failure
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class RemoveBatteryEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+///     Event raised when a person removes someone from a mech,
+///     on both success and failure
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class MechExitEvent : SimpleDoAfterEvent
+{
+}
+
+/// <summary>
+///     Event raised when a person enters a mech, on both success and failure
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class MechEntryEvent : SimpleDoAfterEvent
+{
+}
index 36509054e6672cd7ecd64b224daf5e1c42905ceb..c35e8bd31c16732dcf1e9340af7c579909281783 100644 (file)
@@ -1,5 +1,7 @@
 using System.Threading;
+using Content.Shared.DoAfter;
 using Content.Shared.Mech.Components;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Mech.Equipment.Components;
 
@@ -12,14 +14,12 @@ public sealed class MechEquipmentComponent : Component
     /// <summary>
     /// How long does it take to install this piece of equipment
     /// </summary>
-    [DataField("installDuration")]
-    public float InstallDuration = 5;
+    [DataField("installDuration")] public float InstallDuration = 5;
 
     /// <summary>
     /// The mech that the equipment is inside of.
     /// </summary>
-    [ViewVariables]
-    public EntityUid? EquipmentOwner;
+    [ViewVariables] public EntityUid? EquipmentOwner;
 }
 
 /// <summary>
@@ -41,3 +41,14 @@ public sealed class MechEquipmentInstallFinished : EntityEventArgs
 public sealed class MechEquipmentInstallCancelled : EntityEventArgs
 {
 }
+
+[Serializable, NetSerializable]
+public sealed class GrabberDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class InsertEquipmentEvent : SimpleDoAfterEvent
+{
+}
+
index 2bd456d77494e0e2d5a9bd4261ba53578c0140ed..752d4ce72ebc9102915a3491ac499b78e61cb4d2 100644 (file)
@@ -76,8 +76,6 @@ public abstract class SharedCryoPodComponent: Component
     [DataField("permaLocked")]
     public bool PermaLocked { get; set; }
 
-    public bool IsPrying { get; set; }
-
     [Serializable, NetSerializable]
     public enum CryoPodVisuals : byte
     {
index 204f37ae92b0f9f187de30eeadec26b61a215727..4b82b8388a1c8ff7ddde3c21b263ff46535f743d 100644 (file)
@@ -11,6 +11,7 @@ using Content.Shared.Stunnable;
 using Content.Shared.Verbs;
 using Robust.Shared.Containers;
 using Robust.Shared.Player;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Medical.Cryogenics;
 
@@ -150,19 +151,19 @@ public abstract partial class SharedCryoPodSystem: EntitySystem
 
     protected void OnCryoPodPryFinished(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryFinished args)
     {
-        cryoPodComponent.IsPrying = false;
+        if (args.Cancelled)
+            return;
+
         EjectBody(uid, cryoPodComponent);
     }
 
-    protected void OnCryoPodPryInterrupted(EntityUid uid, SharedCryoPodComponent cryoPodComponent, CryoPodPryInterrupted args)
+    [Serializable, NetSerializable]
+    public sealed class CryoPodPryFinished : SimpleDoAfterEvent
     {
-        cryoPodComponent.IsPrying = false;
     }
 
-    #region Event records
-
-    protected record CryoPodPryFinished;
-    protected record CryoPodPryInterrupted;
-
-    #endregion
+    [Serializable, NetSerializable]
+    public sealed class CryoPodDragFinished : SimpleDoAfterEvent
+    {
+    }
 }
diff --git a/Content.Shared/Medical/HealingDoAfterEvent.cs b/Content.Shared/Medical/HealingDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..11e064e
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Medical;
+
+[Serializable, NetSerializable]
+public sealed class HealingDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Medical/ReclaimerDoAfterEvent.cs b/Content.Shared/Medical/ReclaimerDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..b94ed1e
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Medical;
+
+[Serializable, NetSerializable]
+public sealed class ReclaimerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Medical/StethoscopeDoAfterEvent.cs b/Content.Shared/Medical/StethoscopeDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..a6d2a66
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Medical;
+
+[Serializable, NetSerializable]
+public sealed class StethoscopeDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index 3366fbfd3d3a972c626a17a979a954682a1a94a5..712911125cdeb2456f03349ad373fcd839e23ee9 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.DoAfter;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.MedicalScanner
@@ -24,4 +25,9 @@ namespace Content.Shared.MedicalScanner
             Key
         }
     }
+
+    [Serializable, NetSerializable]
+    public sealed class HealthAnalyzerDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
 }
index a134a084d714bcc9382de8b4f73dfbc60d79a727..9ed6faafb0cfc18b6b73a7e502d44258606ce288 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.DoAfter;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Nuke
@@ -29,4 +30,9 @@ namespace Content.Shared.Nuke
         public int MaxCodeLength;
         public bool AllowArm;
     }
+
+    [Serializable, NetSerializable]
+    public sealed class NukeDisarmDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
 }
diff --git a/Content.Shared/Nutrition/Events.cs b/Content.Shared/Nutrition/Events.cs
new file mode 100644 (file)
index 0000000..3e3bd93
--- /dev/null
@@ -0,0 +1,29 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Nutrition;
+
+/// <summary>
+///     Do after even for food and drink.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class ConsumeDoAfterEvent : DoAfterEvent
+{
+    [DataField("solution", required: true)]
+    public readonly string Solution = default!;
+
+    [DataField("flavorMessage", required: true)]
+    public readonly string FlavorMessage = default!;
+
+    private ConsumeDoAfterEvent()
+    {
+    }
+
+    public ConsumeDoAfterEvent(string solution, string flavorMessage)
+    {
+        Solution = solution;
+        FlavorMessage = flavorMessage;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
diff --git a/Content.Shared/Power/ApcToolFinishedEvent.cs b/Content.Shared/Power/ApcToolFinishedEvent.cs
new file mode 100644 (file)
index 0000000..3178b24
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Power;
+
+[Serializable, NetSerializable]
+public sealed class ApcToolFinishedEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index c5cf05ea811b6129fd726bb1ce76c64f2a6a8837..ca5d1f647aa5a4eba5b792bbdc105b21fa0fb2fc 100644 (file)
@@ -42,12 +42,6 @@ public sealed class EncryptionKeyHolderComponent : Component
     public Container KeyContainer = default!;
     public const string KeyContainerName = "key_slots";
 
-    /// <summary>
-    /// Blocks multiple attempts to remove the key
-    /// </summary>
-    [DataField("removing")]
-    public bool Removing;
-
     /// <summary>
     ///     Combined set of radio channels provided by all contained keys.
     /// </summary>
index cb10ec07f2106920d33b8f89de21856ff1b732d5..a69438c354161f1f6a24b20a9bd9eee6412cbc8e 100644 (file)
@@ -1,5 +1,6 @@
 using System.Linq;
 using Content.Shared.Chat;
+using Content.Shared.DoAfter;
 using Content.Shared.Examine;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction;
@@ -11,6 +12,7 @@ using Content.Shared.Wires;
 using Robust.Shared.Containers;
 using Robust.Shared.Network;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Radio.EntitySystems;
@@ -40,16 +42,13 @@ public sealed class EncryptionKeySystem : EntitySystem
         SubscribeLocalEvent<EncryptionKeyHolderComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
         SubscribeLocalEvent<EncryptionKeyHolderComponent, EntRemovedFromContainerMessage>(OnContainerModified);
         SubscribeLocalEvent<EncryptionKeyHolderComponent, EncryptionRemovalFinishedEvent>(OnKeyRemoval);
-        SubscribeLocalEvent<EncryptionKeyHolderComponent, EncryptionRemovalCancelledEvent>(OnKeyCancelled);
-    }
-
-    private void OnKeyCancelled(EntityUid uid, EncryptionKeyHolderComponent component, EncryptionRemovalCancelledEvent args)
-    {
-        component.Removing = false;
     }
 
     private void OnKeyRemoval(EntityUid uid, EncryptionKeyHolderComponent component, EncryptionRemovalFinishedEvent args)
     {
+        if (args.Cancelled)
+            return;
+
         var contained = component.KeyContainer.ContainedEntities.ToArray();
         _container.EmptyContainer(component.KeyContainer, reparent: false);
         foreach (var ent in contained)
@@ -60,7 +59,6 @@ public sealed class EncryptionKeySystem : EntitySystem
         // if tool use ever gets predicted this needs changing.
         _popup.PopupEntity(Loc.GetString("encryption-keys-all-extracted"), uid, args.User);
         _audio.PlayPvs(component.KeyExtractionSound, uid);
-        component.Removing = false;
     }
 
     public void UpdateChannels(EntityUid uid, EncryptionKeyHolderComponent component)
@@ -91,14 +89,18 @@ public sealed class EncryptionKeySystem : EntitySystem
 
     private void OnInteractUsing(EntityUid uid, EncryptionKeyHolderComponent component, InteractUsingEvent args)
     {
-        if (!TryComp<ContainerManagerComponent>(uid, out var _) || args.Handled || component.Removing)
+        if ( args.Handled || !TryComp<ContainerManagerComponent>(uid, out var storage))
             return;
+
+        args.Handled = true;
+
         if (!component.KeysUnlocked)
         {
             if (_net.IsClient && _timing.IsFirstTimePredicted)
                 _popup.PopupEntity(Loc.GetString("encryption-keys-are-locked"), uid, args.User);
             return;
         }
+
         if (TryComp<EncryptionKeyComponent>(args.Used, out var key))
         {
             TryInsertKey(uid, component, args);
@@ -154,14 +156,7 @@ public sealed class EncryptionKeySystem : EntitySystem
             return;
         }
 
-        if (_net.IsServer)
-        {
-            //This is honestly the poor mans fix because the InteractUsingEvent fires off 12 times
-            component.Removing = true;
-            var toolEvData = new ToolEventData(new EncryptionRemovalFinishedEvent(args.User), cancelledEv: new EncryptionRemovalCancelledEvent(), targetEntity: uid);
-            if (_tool.UseTool(args.Used, args.User, uid, 1f, new[] { component.KeysExtractionMethod }, toolEvData, toolComponent: tool))
-                args.Handled = true;
-        }
+        _tool.UseTool(args.Used, args.User, uid, 1f, component.KeysExtractionMethod, new EncryptionRemovalFinishedEvent(), toolComponent: tool);
     }
 
     private void OnStartup(EntityUid uid, EncryptionKeyHolderComponent component, ComponentStartup args)
@@ -244,18 +239,8 @@ public sealed class EncryptionKeySystem : EntitySystem
         }
     }
 
-    public sealed class EncryptionRemovalFinishedEvent : EntityEventArgs
-    {
-        public EntityUid User;
-
-        public EncryptionRemovalFinishedEvent(EntityUid user)
-        {
-            User = user;
-        }
-    }
-
-    public sealed class EncryptionRemovalCancelledEvent : EntityEventArgs
+    [Serializable, NetSerializable]
+    public sealed class EncryptionRemovalFinishedEvent : SimpleDoAfterEvent
     {
-
     }
 }
diff --git a/Content.Shared/Repairable/SharedRepairableSystem.cs b/Content.Shared/Repairable/SharedRepairableSystem.cs
new file mode 100644 (file)
index 0000000..ec59a60
--- /dev/null
@@ -0,0 +1,13 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Repairable;
+
+public abstract class SharedRepairableSystem : EntitySystem
+{
+    [Serializable, NetSerializable]
+    protected sealed class RepairFinishedEvent : SimpleDoAfterEvent
+    {
+    }
+}
+
diff --git a/Content.Shared/Resist/EscapeInventoryEvent.cs b/Content.Shared/Resist/EscapeInventoryEvent.cs
new file mode 100644 (file)
index 0000000..27e5346
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Resist;
+
+[Serializable, NetSerializable]
+public sealed class EscapeInventoryEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Resist/ResistLockerDoAfterEvent.cs b/Content.Shared/Resist/ResistLockerDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..4f31dc0
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Resist;
+
+[Serializable, NetSerializable]
+public sealed class ResistLockerDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index 6de9a5d7a21a3a57b4821b141cd93dbfeb66507d..15289ad1d715bd94695802288930817cb2a31f05 100644 (file)
@@ -1,8 +1,14 @@
 using Content.Shared.Actions;
+using Content.Shared.DoAfter;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Revenant;
 
+[Serializable, NetSerializable]
+public sealed class SoulEvent : SimpleDoAfterEvent
+{
+}
+
 public sealed class SoulSearchDoAfterComplete : EntityEventArgs
 {
     public readonly EntityUid Target;
@@ -13,7 +19,14 @@ public sealed class SoulSearchDoAfterComplete : EntityEventArgs
     }
 }
 
-public sealed class SoulSearchDoAfterCancelled : EntityEventArgs { }
+public sealed class SoulSearchDoAfterCancelled : EntityEventArgs
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class HarvestEvent : SimpleDoAfterEvent
+{
+}
 
 public sealed class HarvestDoAfterComplete : EntityEventArgs
 {
@@ -25,12 +38,30 @@ public sealed class HarvestDoAfterComplete : EntityEventArgs
     }
 }
 
-public sealed class HarvestDoAfterCancelled : EntityEventArgs { }
-public sealed class RevenantShopActionEvent : InstantActionEvent { }
-public sealed class RevenantDefileActionEvent : InstantActionEvent { }
-public sealed class RevenantOverloadLightsActionEvent : InstantActionEvent { }
-public sealed class RevenantBlightActionEvent : InstantActionEvent { }
-public sealed class RevenantMalfunctionActionEvent : InstantActionEvent { }
+public sealed class HarvestDoAfterCancelled : EntityEventArgs
+{
+}
+
+public sealed class RevenantShopActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantDefileActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantOverloadLightsActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantBlightActionEvent : InstantActionEvent
+{
+}
+
+public sealed class RevenantMalfunctionActionEvent : InstantActionEvent
+{
+}
+
 
 [NetSerializable, Serializable]
 public enum RevenantVisuals : byte
diff --git a/Content.Shared/Spillable/SpillDoAfterEvent.cs b/Content.Shared/Spillable/SpillDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..8a24935
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Spillable;
+
+[Serializable, NetSerializable]
+public sealed class SpillDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Sticky/StickyDoAfterEvent.cs b/Content.Shared/Sticky/StickyDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..e460efa
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Sticky;
+
+[Serializable, NetSerializable]
+public sealed class StickyDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index 15e9726350776915ad4b31eacd17f9538fff7ccf..ac7003d6753d1c2de58258f7fd4937bc99567c82 100644 (file)
@@ -1,7 +1,14 @@
 using System.Threading;
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Storage.Components
 {
+    [Serializable, NetSerializable]
+    public sealed class DumpableDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
+
     /// <summary>
     /// Lets you dump this container on the ground using a verb,
     /// or when interacting with it on a disposal unit or placeable surface.
@@ -12,13 +19,11 @@ namespace Content.Shared.Storage.Components
         /// <summary>
         /// How long each item adds to the doafter.
         /// </summary>
-        [DataField("delayPerItem")]
-        public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
+        [DataField("delayPerItem")] public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
 
         /// <summary>
         /// The multiplier modifier
         /// </summary>
-        [DataField("multiplier")]
-        public float Multiplier = 1.0f;
+        [DataField("multiplier")] public float Multiplier = 1.0f;
     }
 }
diff --git a/Content.Shared/Storage/EntitySystems/BluespaceLockerDoAfterEvent.cs b/Content.Shared/Storage/EntitySystems/BluespaceLockerDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..8a488ea
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+[Serializable, NetSerializable]
+public sealed class BluespaceLockerDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/Storage/Events.cs b/Content.Shared/Storage/Events.cs
new file mode 100644 (file)
index 0000000..4c241ff
--- /dev/null
@@ -0,0 +1,22 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Storage;
+
+[Serializable, NetSerializable]
+public sealed class AreaPickupDoAfterEvent : DoAfterEvent
+{
+    [DataField("entities", required: true)]
+    public readonly IReadOnlyList<EntityUid> Entities = default!;
+
+    private AreaPickupDoAfterEvent()
+    {
+    }
+
+    public AreaPickupDoAfterEvent(List<EntityUid> entities)
+    {
+        Entities = entities;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
diff --git a/Content.Shared/Swab/SwabEvents.cs b/Content.Shared/Swab/SwabEvents.cs
new file mode 100644 (file)
index 0000000..05b1d43
--- /dev/null
@@ -0,0 +1,14 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Swab;
+
+[Serializable, NetSerializable]
+public sealed class DiseaseSwabDoAfterEvent : SimpleDoAfterEvent
+{
+}
+
+[Serializable, NetSerializable]
+public sealed class BotanySwabDoAfterEvent : SimpleDoAfterEvent
+{
+}
index d30a913adf02401dcddbb69faea6719c44300e2b..9b0290ba2c24a7998c394719da12b64c15e637c8 100644 (file)
@@ -1,6 +1,8 @@
-using Robust.Shared.Audio;
+using Content.Shared.DoAfter;
+using Robust.Shared.Audio;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Teleportation.Components;
@@ -18,17 +20,17 @@ public sealed class HandTeleporterComponent : Component
     [ViewVariables, DataField("secondPortal")]
     public EntityUid? SecondPortal = null;
 
-    [DataField("firstPortalPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
+    [DataField("firstPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
     public string FirstPortalPrototype = "PortalRed";
 
-    [DataField("secondPortalPrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
+    [DataField("secondPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
     public string SecondPortalPrototype = "PortalBlue";
 
-    [DataField("newPortalSound")]
-    public SoundSpecifier NewPortalSound = new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg")
-    {
-        Params = AudioParams.Default.WithVolume(-2f)
-    };
+    [DataField("newPortalSound")] public SoundSpecifier NewPortalSound =
+        new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg")
+        {
+            Params = AudioParams.Default.WithVolume(-2f)
+        };
 
     [DataField("clearPortalsSound")]
     public SoundSpecifier ClearPortalsSound = new SoundPathSpecifier("/Audio/Machines/button.ogg");
@@ -36,7 +38,10 @@ public sealed class HandTeleporterComponent : Component
     /// <summary>
     ///     Delay for creating the portals in seconds.
     /// </summary>
-    [DataField("portalCreationDelay")]
-    public float PortalCreationDelay = 2.5f;
+    [DataField("portalCreationDelay")] public float PortalCreationDelay = 2.5f;
+}
 
+[Serializable, NetSerializable]
+public sealed class TeleporterDoAfterEvent : SimpleDoAfterEvent
+{
 }
index 86067fb56cd5120e4426b53e7703675f9f3b9bfa..b0486a54f0a4921a167fc964f219a0167c27b018 100644 (file)
@@ -1,5 +1,7 @@
+using Content.Shared.DoAfter;
 using Content.Shared.Tools;
 using Robust.Shared.Audio;
+using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Toilet
@@ -16,8 +18,15 @@ namespace Content.Shared.Toilet
         [DataField("toggleSound")]
         public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Effects/toilet_seat_down.ogg");
 
+        [DataField("lidOpen")]
         public bool LidOpen = false;
+
+        [DataField("isSeatUp")]
         public bool IsSeatUp = false;
-        public bool IsPrying = false;
+    }
+
+    [Serializable, NetSerializable]
+    public sealed class ToiletPryDoAfterEvent : SimpleDoAfterEvent
+    {
     }
 }
index f631f66e200845afbca981463bed6e4ae12a99bf..c84eaddc2a4a04a6e1490fb8b2424c04996ab888 100644 (file)
@@ -43,46 +43,12 @@ namespace Content.Shared.Tools.Components
     [ByRefEvent]
     public struct ToolUserAttemptUseEvent
     {
-        public EntityUid User;
         public EntityUid? Target;
         public bool Cancelled = false;
 
-        public ToolUserAttemptUseEvent(EntityUid user, EntityUid? target)
+        public ToolUserAttemptUseEvent(EntityUid? target)
         {
-            User = user;
             Target = target;
         }
     }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    public sealed class ToolUseFinishAttemptEvent : CancellableEntityEventArgs
-    {
-        public float Fuel { get; }
-        public EntityUid User { get; }
-
-        public ToolUseFinishAttemptEvent(float fuel, EntityUid user)
-        {
-            User = 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;
-        }
-    }
 }
index 030c33f9cf3f15919d07cb426ff495c860923cef..b198f6d779c81b9288888215c81d067ce7943ab4 100644 (file)
 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;
 
-public abstract class SharedToolSystem : EntitySystem
+public abstract partial 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()
+    public void InitializeMultipleTool()
     {
         SubscribeLocalEvent<MultipleToolComponent, ComponentStartup>(OnMultipleToolStartup);
         SubscribeLocalEvent<MultipleToolComponent, ActivateInWorldEvent>(OnMultipleToolActivated);
         SubscribeLocalEvent<MultipleToolComponent, ComponentGetState>(OnMultipleToolGetState);
         SubscribeLocalEvent<MultipleToolComponent, ComponentHandleState>(OnMultipleToolHandleState);
-
-        SubscribeLocalEvent<ToolComponent, DoAfterEvent<ToolEventData>>(OnDoAfter);
-
-        SubscribeLocalEvent<ToolDoAfterComplete>(OnDoAfterComplete);
-        SubscribeLocalEvent<ToolDoAfterCancelled>(OnDoAfterCancelled);
-    }
-
-    private void OnDoAfter(EntityUid uid, ToolComponent component, DoAfterEvent<ToolEventData> args)
-    {
-        if (args.Handled || args.AdditionalData.Ev == null)
-            return;
-
-        if (args.Cancelled || !ToolFinishUse(uid, args.Args.User, args.AdditionalData.Fuel))
-        {
-            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;
-            }
-
-            return;
-        }
-
-        if (args.AdditionalData.TargetEntity != null)
-            RaiseLocalEvent(args.AdditionalData.TargetEntity.Value, args.AdditionalData.Ev);
-        else
-            RaiseLocalEvent(args.AdditionalData.Ev);
-
-        args.Handled = true;
-    }
-
-    public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float doAfterDelay, IEnumerable<string> toolQualitiesNeeded, ToolEventData toolEventData, float fuel = 0f, ToolComponent? toolComponent = null, Func<bool>? doAfterCheck = null, CancellationTokenSource? 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:cancelToken?.Token ?? default, 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<bool>? doAfterCheck = null)
-    {
-        return UseTool(tool, user, target, doAfterDelay, new[] { toolQualityNeeded }, toolEventData, fuel,
-            toolComponent, doAfterCheck);
     }
 
     private void OnMultipleToolHandleState(EntityUid uid, MultipleToolComponent component, ref ComponentHandleState args)
@@ -169,125 +85,5 @@ public abstract class SharedToolSystem : EntitySystem
         if (_protoMan.TryIndex(current.Behavior.First(), out ToolQualityPrototype? quality))
             multiple.CurrentQualityName = Loc.GetString(quality.Name);
     }
-
-    /// <summary>
-    ///     Whether a tool entity has the specified quality or not.
-    /// </summary>
-    public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null)
-    {
-        return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
-    }
-
-    /// <summary>
-    ///     Whether a tool entity has all specified qualities or not.
-    /// </summary>
-    public bool HasAllQualities(EntityUid uid, IEnumerable<string> qualities, ToolComponent? tool = null)
-    {
-        return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
-    }
-
-
-    private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable<string> 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;
-        }
-    }
 }
 
diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs
new file mode 100644 (file)
index 0000000..2898831
--- /dev/null
@@ -0,0 +1,286 @@
+using Content.Shared.DoAfter;
+using Content.Shared.Tools.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Tools;
+
+public abstract partial 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()
+    {
+        InitializeMultipleTool();
+        SubscribeLocalEvent<ToolComponent, ToolDoAfterEvent>(OnDoAfter);
+    }
+
+    private void OnDoAfter(EntityUid uid, ToolComponent tool, ToolDoAfterEvent args)
+    {
+        PlayToolSound(uid, tool, args.User);
+        var ev = args.WrappedEvent;
+        ev.DoAfter = args.DoAfter;
+
+        if (args.OriginalTarget != null)
+            RaiseLocalEvent(args.OriginalTarget.Value, (object) ev);
+        else
+            RaiseLocalEvent((object) ev);
+    }
+
+    public void PlayToolSound(EntityUid uid, ToolComponent tool, EntityUid? user)
+    {
+        if (tool.UseSound == null)
+            return;
+
+        _audioSystem.PlayPredicted(tool.UseSound, uid, user, tool.UseSound.Params.WithVariation(0.175f).AddVolume(-5f));
+    }
+
+    /// <summary>
+    ///     Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
+    ///     Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
+    /// </summary>
+    /// <param name="tool">The tool to use</param>
+    /// <param name="user">The entity using the tool</param>
+    /// <param name="target">The entity that the tool is being used on. This is also the entity that will receive the
+    /// event. If null, the event will be broadcast</param>
+    /// <param name="doAfterDelay">The base tool use delay (seconds). This will be modified by the tool's quality</param>
+    /// <param name="toolQualitiesNeeded">The qualities needed for this tool to work.</param>
+    /// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
+    /// will be directed at the tool target.</param>
+    /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
+    /// <param name="toolComponent">The tool component.</param>
+    /// <returns>Returns true if any interaction takes place.</returns>
+    public bool UseTool(
+        EntityUid tool,
+        EntityUid user,
+        EntityUid? target,
+        float doAfterDelay,
+        IEnumerable<string> toolQualitiesNeeded,
+        DoAfterEvent doAfterEv,
+        float fuel = 0f,
+        ToolComponent? toolComponent = null)
+    {
+        return UseTool(tool,
+            user,
+            target,
+            TimeSpan.FromSeconds(doAfterDelay),
+            toolQualitiesNeeded,
+            doAfterEv,
+            out _,
+            fuel,
+            toolComponent);
+    }
+
+    /// <summary>
+    ///     Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
+    ///     Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
+    /// </summary>
+    /// <param name="tool">The tool to use</param>
+    /// <param name="user">The entity using the tool</param>
+    /// <param name="target">The entity that the tool is being used on. This is also the entity that will receive the
+    /// event. If null, the event will be broadcast</param>
+    /// <param name="delay">The base tool use delay. This will be modified by the tool's quality</param>
+    /// <param name="toolQualitiesNeeded">The qualities needed for this tool to work.</param>
+    /// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
+    /// will be directed at the tool target.</param>
+    /// <param name="id">The id of the DoAfter that was created. This may be null even if the function returns true in
+    /// the event that this tool-use cancelled an existing DoAfter</param>
+    /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
+    /// <param name="toolComponent">The tool component.</param>
+    /// <returns>Returns true if any interaction takes place.</returns>
+    public bool UseTool(
+        EntityUid tool,
+        EntityUid user,
+        EntityUid? target,
+        TimeSpan delay,
+        IEnumerable<string> toolQualitiesNeeded,
+        DoAfterEvent doAfterEv,
+        out DoAfterId? id,
+        float fuel = 0f,
+        ToolComponent? toolComponent = null)
+    {
+        id = null;
+        if (!Resolve(tool, ref toolComponent, false))
+            return false;
+
+        if (!CanStartToolUse(tool, user, target, fuel, toolQualitiesNeeded, toolComponent))
+            return false;
+
+        var toolEvent = new ToolDoAfterEvent(fuel, doAfterEv, target);
+        var doAfterArgs = new DoAfterArgs(user, delay / toolComponent.SpeedModifier, toolEvent, tool, target: target, used: tool)
+        {
+            BreakOnDamage = true,
+            BreakOnTargetMove = true,
+            BreakOnUserMove = true,
+            NeedHand = true,
+            AttemptFrequency = fuel <= 0 ? AttemptFrequency.Never : AttemptFrequency.EveryTick
+        };
+
+        _doAfterSystem.TryStartDoAfter(doAfterArgs, out id);
+        return true;
+    }
+
+    /// <summary>
+    ///     Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
+    ///     Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
+    /// </summary>
+    /// <param name="tool">The tool to use</param>
+    /// <param name="user">The entity using the tool</param>
+    /// <param name="target">The entity that the tool is being used on. This is also the entity that will receive the
+    /// event. If null, the event will be broadcast</param>
+    /// <param name="doAfterDelay">The base tool use delay (seconds). This will be modified by the tool's quality</param>
+    /// <param name="toolQualityNeeded">The quality needed for this tool to work.</param>
+    /// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
+    /// will be directed at the tool target.</param>
+    /// <param name="id">The id of the DoAfter that was created. This may be null even if the function returns true in
+    /// the event that this tool-use cancelled an existing DoAfter</param>
+    /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
+    /// <param name="toolComponent">The tool component.</param>
+    /// <returns>Returns true if any interaction takes place.</returns>
+    public bool UseTool(
+        EntityUid tool,
+        EntityUid user,
+        EntityUid? target,
+        float doAfterDelay,
+        string toolQualityNeeded,
+        DoAfterEvent doAfterEv,
+        float fuel = 0,
+        ToolComponent? toolComponent = null)
+    {
+        return UseTool(tool,
+            user,
+            target,
+            TimeSpan.FromSeconds(doAfterDelay),
+            new[] { toolQualityNeeded },
+            doAfterEv,
+            out _,
+            fuel,
+            toolComponent);
+    }
+
+    /// <summary>
+    ///     Whether a tool entity has the specified quality or not.
+    /// </summary>
+    public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null)
+    {
+        return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
+    }
+
+    /// <summary>
+    ///     Whether a tool entity has all specified qualities or not.
+    /// </summary>
+    public bool HasAllQualities(EntityUid uid, IEnumerable<string> qualities, ToolComponent? tool = null)
+    {
+        return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
+    }
+
+    private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, float fuel, IEnumerable<string> toolQualitiesNeeded, ToolComponent? toolComponent = null)
+    {
+        if (!Resolve(tool, ref toolComponent))
+            return false;
+
+        var ev = new ToolUserAttemptUseEvent(target);
+        RaiseLocalEvent(user, ref ev);
+        if (ev.Cancelled)
+            return false;
+
+        if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded))
+            return false;
+
+        var beforeAttempt = new ToolUseAttemptEvent(fuel, user);
+        RaiseLocalEvent(tool, beforeAttempt, false);
+
+        return !beforeAttempt.Cancelled;
+    }
+
+    #region DoAfterEvents
+
+    [Serializable, NetSerializable]
+    protected sealed class ToolDoAfterEvent : DoAfterEvent
+    {
+        [DataField("fuel")]
+        public readonly float Fuel;
+
+        /// <summary>
+        ///     Entity that the wrapped do after event will get directed at. If null, event will be broadcast.
+        /// </summary>
+        [DataField("target")]
+        public readonly EntityUid? OriginalTarget;
+
+        [DataField("wrappedEvent")]
+        public readonly DoAfterEvent WrappedEvent = default!;
+
+        private ToolDoAfterEvent()
+        {
+        }
+
+        public ToolDoAfterEvent(float fuel, DoAfterEvent wrappedEvent, EntityUid? originalTarget)
+        {
+            DebugTools.Assert(wrappedEvent.GetType().HasCustomAttribute<NetSerializableAttribute>(), "Tool event is not serializable");
+
+            Fuel = fuel;
+            WrappedEvent = wrappedEvent;
+            OriginalTarget = originalTarget;
+        }
+
+        public override DoAfterEvent Clone()
+        {
+            var evClone = WrappedEvent.Clone();
+
+            // Most DoAfter events are immutable
+            if (evClone == WrappedEvent)
+                return this;
+
+            return new ToolDoAfterEvent(Fuel, evClone, OriginalTarget);
+        }
+    }
+
+    [Serializable, NetSerializable]
+    protected sealed class LatticeCuttingCompleteEvent : DoAfterEvent
+    {
+        [DataField("coordinates", required:true)]
+        public readonly EntityCoordinates Coordinates;
+
+        private LatticeCuttingCompleteEvent()
+        {
+        }
+
+        public LatticeCuttingCompleteEvent(EntityCoordinates coordinates)
+        {
+            Coordinates = coordinates;
+        }
+
+        public override DoAfterEvent Clone() => this;
+    }
+
+    [Serializable, NetSerializable]
+    protected sealed class TilePryingDoAfterEvent : DoAfterEvent
+    {
+        [DataField("coordinates", required:true)]
+        public readonly EntityCoordinates Coordinates;
+
+        private TilePryingDoAfterEvent()
+        {
+        }
+
+        public TilePryingDoAfterEvent(EntityCoordinates coordinates)
+        {
+            Coordinates = coordinates;
+        }
+
+        public override DoAfterEvent Clone() => this;
+    }
+}
+
+[Serializable, NetSerializable]
+public sealed class CableCuttingFinishedEvent : SimpleDoAfterEvent
+{
+}
+
+#endregion
+
diff --git a/Content.Shared/Tools/Systems/WeldFinishedEvent.cs b/Content.Shared/Tools/Systems/WeldFinishedEvent.cs
new file mode 100644 (file)
index 0000000..20dfae0
--- /dev/null
@@ -0,0 +1,13 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Tools.Systems;
+
+/// <summary>
+///     Raised after welding do_after has finished. It doesn't guarantee success,
+///     use <see cref="WeldableChangedEvent"/> to get updated status.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class WeldFinishedEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Udder/MilkingDoAfterEvent.cs b/Content.Shared/Udder/MilkingDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..03170b9
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Udder;
+
+[Serializable, NetSerializable]
+public sealed class MilkingDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
index dfd129a9da2857f8a5b5ca10022bb92ca593e784..78c774c5b357650d389907221de7c147c3b68d1c 100644 (file)
@@ -1,6 +1,8 @@
 using Content.Shared.Emag.Components;
 using Robust.Shared.Prototypes;
 using System.Linq;
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.VendingMachines;
 
@@ -111,3 +113,7 @@ public abstract class SharedVendingMachineSystem : EntitySystem
     }
 }
 
+[Serializable, NetSerializable]
+public sealed class RestockDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/Wieldable/WieldableDoAfterEvent.cs b/Content.Shared/Wieldable/WieldableDoAfterEvent.cs
new file mode 100644 (file)
index 0000000..fdbe028
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Wieldable;
+
+[Serializable, NetSerializable]
+public sealed class WieldableDoAfterEvent : SimpleDoAfterEvent
+{
+}
\ No newline at end of file
diff --git a/Content.Shared/Wires/Events.cs b/Content.Shared/Wires/Events.cs
new file mode 100644 (file)
index 0000000..d28203e
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Wires;
+
+[Serializable, NetSerializable]
+public sealed class WireDoAfterEvent : DoAfterEvent
+{
+    [DataField("action", required: true)]
+    public readonly WiresAction Action;
+
+    [DataField("id", required: true)]
+    public readonly int Id;
+
+    private WireDoAfterEvent()
+    {
+    }
+
+    public WireDoAfterEvent(WiresAction action, int id)
+    {
+        Action = action;
+        Id = id;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
index 0614d1344ab13f32a8d8bca03fdd2e9d0832718c..dd749affdb2096e831123387500c19d58cca37e7 100644 (file)
@@ -1,9 +1,15 @@
 using System.Diagnostics.CodeAnalysis;
+using Content.Shared.DoAfter;
 using JetBrains.Annotations;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Wires
 {
+    [Serializable, NetSerializable]
+    public sealed class WirePanelDoAfterEvent : SimpleDoAfterEvent
+    {
+    }
+
     [Serializable, NetSerializable]
     public enum WiresVisuals : byte
     {
index a1519b6325667f58bce2ba28a75531801dc55755..878ef13044bbdddd6e6fef3b216200d440e918ed 100644 (file)
@@ -20,12 +20,6 @@ public sealed class WiresPanelComponent : Component
     [ViewVariables]
     public bool Visible = true;
 
-    /// <summary>
-    ///     Marks if maintenance panel being open/closed by someone with a screwdriver.
-    ///     Prevents do after spam.
-    /// </summary>
-    public bool IsScrewing;
-
     [DataField("screwdriverOpenSound")]
     public SoundSpecifier ScrewdriverOpenSound = new SoundPathSpecifier("/Audio/Machines/screwdriveropen.ogg");