diff --git a/Content.Client/Cuffs/CuffableSystem.cs b/Content.Client/Cuffs/CuffableSystem.cs index 42d0ae03fe..e69d292221 100644 --- a/Content.Client/Cuffs/CuffableSystem.cs +++ b/Content.Client/Cuffs/CuffableSystem.cs @@ -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(); diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 1af3d8a6b2..090a8bc6a5 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -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; + /// + /// Flash time for cancelled DoAfters + /// + 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(); + _meta = _entManager.EntitySysManager.GetEntitySystem(); var sprite = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Interface/Misc/progress_bar.rsi"), "icon"); _barTexture = _entManager.EntitySysManager.GetEntitySystem().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(); var xformQuery = _entManager.GetEntityQuery(); // 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(true)) - { - if (comp.DoAfters.Count == 0 || - !xformQuery.TryGetComponent(comp.Owner, out var xform) || - xform.MapID != args.MapId) - { - continue; - } + var curTime = _timing.CurTime; + + var bounds = args.WorldAABB.Enlarged(5f); + + var metaQuery = _entManager.GetEntityQuery(); + var enumerator = _entManager.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var uid, out _, out var comp, out var sprite, out var xform)) + { + if (xform.MapID != args.MapId) + continue; + + if (comp.DoAfters.Count == 0) + continue; + + var worldPosition = _transform.GetWorldPosition(xform, xformQuery); + if (!bounds.Contains(worldPosition)) + continue; + + // 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; - var worldPosition = _transform.GetWorldPosition(xform); - var index = 0; 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) { - var elapsed = doAfter.Elapsed; - var displayRatio = MathF.Min(1.0f, - (float)elapsed.TotalSeconds / doAfter.Delay); - - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); - - handle.SetTransform(matty); - var offset = _barTexture.Height / scale * index; - // 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; } } diff --git a/Content.Client/DoAfter/DoAfterSystem.cs b/Content.Client/DoAfter/DoAfterSystem.cs index c4e06376da..ecabaa20e8 100644 --- a/Content.Client/DoAfter/DoAfterSystem.cs +++ b/Content.Client/DoAfter/DoAfterSystem.cs @@ -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!; - - /// - /// We'll use an excess time so stuff like finishing effects can show. - /// - public const float ExcessTime = 0.5f; + [Dependency] private readonly MetaDataSystem _metadata = default!; public override void Initialize() { base.Initialize(); - UpdatesOutsidePrediction = true; - SubscribeNetworkEvent(OnCancelledDoAfter); - SubscribeLocalEvent(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(); } - 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) - { - if (!TryComp(ev.Uid, out var doAfter)) - return; - - Cancel(doAfter, ev.ID); - } - - /// - /// Remove a DoAfter without showing a cancellation graphic. - /// - public void Remove(DoAfterComponent component, Shared.DoAfter.DoAfter doAfter, bool found = false) - { - component.DoAfters.Remove(doAfter.ID); - component.CancelledDoAfters.Remove(doAfter.ID); - } - - /// - /// Mark a DoAfter as cancelled and show a cancellation graphic. - /// - /// Actual removal is handled by DoAfterEntitySystem. - public void Cancel(DoAfterComponent component, byte id) - { - if (component.CancelledDoAfters.ContainsKey(id)) - return; - - if (!component.DoAfters.ContainsKey(id)) - return; - - var doAfterMessage = component.DoAfters[id]; - doAfterMessage.Cancelled = true; - doAfterMessage.CancelledTime = GameTiming.CurTime; - component.CancelledDoAfters.Add(id, doAfterMessage); - } - - // TODO separate DoAfter & ActiveDoAfter components for the entity query. public override void Update(float frameTime) { - if (!GameTiming.IsFirstTimePredicted) - return; + // Currently this only predicts do afters initiated by the player. + + // 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. var playerEntity = _player.LocalPlayer?.ControlledEntity; - foreach (var (comp, xform) in EntityQuery()) - { - var doAfters = comp.DoAfters; + if (!TryComp(playerEntity, out ActiveDoAfterComponent? active)) + return; - if (doAfters.Count == 0) - continue; + if (_metadata.EntityPaused(playerEntity.Value)) + return; - var userGrid = xform.Coordinates; - var toRemove = new RemQueue(); - - // Check cancellations / finishes - foreach (var (id, doAfter) in doAfters) - { - // If we've passed the final time (after the excess to show completion graphic) then remove. - if ((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(); - - 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(playerEntity.Value); + var xformQuery = GetEntityQuery(); + var handsQuery = GetEntityQuery(); + Update(playerEntity.Value, active, comp, time, xformQuery, handsQuery); } } diff --git a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs index fffbe3469b..d1918d75fb 100644 --- a/Content.Client/Medical/Cryogenics/CryoPodSystem.cs +++ b/Content.Client/Medical/Cryogenics/CryoPodSystem.cs @@ -19,7 +19,6 @@ public sealed class CryoPodSystem: SharedCryoPodSystem SubscribeLocalEvent>(AddAlternativeVerbs); SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnCryoPodPryFinished); - SubscribeLocalEvent(OnCryoPodPryInterrupted); SubscribeLocalEvent(OnAppearanceChange); SubscribeLocalEvent(OnCryoPodInsertion); diff --git a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs index 5fe84b506c..826bbf199b 100644 --- a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs +++ b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs @@ -20,10 +20,22 @@ public sealed class AlertsUIController : UIController, IOnStateEntered(); 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>(OnTestDoAfterFinishEvent); + return this; } - - private void OnTestDoAfterFinishEvent(DoAfterEvent ev) - { - ev.AdditionalData.Cancelled = ev.Cancelled; - } - } - - private sealed class TestDoAfterData - { - public bool Cancelled; }; + [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(); + + await server.WaitPost(() => + { + Assert.Multiple(() => + { + foreach (var type in refMan.GetAllChildren(true)) + { + if (type.IsAbstract || type == typeof(TestDoAfterEvent)) + continue; + + Assert.That(type.HasCustomAttribute() + && type.HasCustomAttribute(), + $"{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(); - var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem(); - var data = new TestDoAfterData(); + var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem(); + var ev = new TestDoAfterEvent(); // That it finishes successfully await server.WaitPost(() => { var tickTime = 1.0f / IoCManager.Resolve().TickRate; var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace); - var cancelToken = new CancellationTokenSource(); - var args = new DoAfterEventArgs(mob, tickTime / 2, cancelToken.Token) { 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(); - var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem(); - var data = new TestDoAfterData(); + var doAfterSystem = entityManager.EntitySysManager.GetEntitySystem(); + DoAfterId? id = default; + var ev = new TestDoAfterEvent(); + await server.WaitPost(() => { var tickTime = 1.0f / IoCManager.Resolve().TickRate; var mob = entityManager.SpawnEntity("Dummy", MapCoordinates.Nullspace); - var cancelToken = new CancellationTokenSource(); - var args = new DoAfterEventArgs(mob, tickTime * 2, cancelToken.Token) { 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(); } diff --git a/Content.Server/AirlockPainter/AirlockPainterSystem.cs b/Content.Server/AirlockPainter/AirlockPainterSystem.cs index b21ee825e0..59f0d6ae7b 100644 --- a/Content.Server/AirlockPainter/AirlockPainterSystem.cs +++ b/Content.Server/AirlockPainter/AirlockPainterSystem.cs @@ -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(AfterInteractOn); SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnSpritePicked); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } - private void OnDoAfter(EntityUid uid, AirlockPainterComponent component, DoAfterEvent 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; - } } } diff --git a/Content.Server/Alert/Click/RemoveEnsnare.cs b/Content.Server/Alert/Click/RemoveEnsnare.cs index 975734152a..1913dea905 100644 --- a/Content.Server/Alert/Click/RemoveEnsnare.cs +++ b/Content.Server/Alert/Click/RemoveEnsnare.cs @@ -18,7 +18,7 @@ public sealed class RemoveEnsnare : IAlertClick if (!entManager.TryGetComponent(ensnare, out EnsnaringComponent? ensnaringComponent)) return; - entManager.EntitySysManager.GetEntitySystem().TryFree(player, ensnare, ensnaringComponent); + entManager.EntitySysManager.GetEntitySystem().TryFree(player, player, ensnare, ensnaringComponent); } } } diff --git a/Content.Server/Animals/Components/UdderComponent.cs b/Content.Server/Animals/Components/UdderComponent.cs index f47b9a855c..ad591022c1 100644 --- a/Content.Server/Animals/Components/UdderComponent.cs +++ b/Content.Server/Animals/Components/UdderComponent.cs @@ -37,7 +37,5 @@ namespace Content.Server.Animals.Components public float UpdateRate = 5; public float AccumulatedFrameTime; - - public bool BeingMilked; } } diff --git a/Content.Server/Animals/Systems/UdderSystem.cs b/Content.Server/Animals/Systems/UdderSystem.cs index adb8a2bf83..ad0e6be27f 100644 --- a/Content.Server/Animals/Systems/UdderSystem.cs +++ b/Content.Server/Animals/Systems/UdderSystem.cs @@ -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>(AddMilkVerb); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(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 args) diff --git a/Content.Server/Anomaly/AnomalySystem.Scanner.cs b/Content.Server/Anomaly/AnomalySystem.Scanner.cs index 3cd08b0de1..2b090c1689 100644 --- a/Content.Server/Anomaly/AnomalySystem.Scanner.cs +++ b/Content.Server/Anomaly/AnomalySystem.Scanner.cs @@ -17,7 +17,7 @@ public sealed partial class AnomalySystem { SubscribeLocalEvent(OnScannerUiOpened); SubscribeLocalEvent(OnScannerAfterInteract); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnScannerAnomalyShutdown); SubscribeLocalEvent(OnScannerAnomalySeverityChanged); @@ -81,7 +81,7 @@ public sealed partial class AnomalySystem if (!HasComp(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 }); diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 3de46afb46..45e4836480 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -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!; diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index ed6ed288cc..6b0c8e046e 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -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(OnInternalsStartup); SubscribeLocalEvent(OnInternalsShutdown); SubscribeLocalEvent>(OnGetInteractionVerbs); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent 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 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 - { - - } } diff --git a/Content.Server/Botany/Systems/BotanySwabSystem.cs b/Content.Server/Botany/Systems/BotanySwabSystem.cs index ef7cc1760b..5e5bff0f5c 100644 --- a/Content.Server/Botany/Systems/BotanySwabSystem.cs +++ b/Content.Server/Botany/Systems/BotanySwabSystem.cs @@ -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(OnExamined); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } /// @@ -44,12 +44,11 @@ public sealed class BotanySwabSystem : EntitySystem if (args.Target == null || !args.CanReach || !HasComp(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 /// /// Save seed data or cross-pollenate. /// - private void OnDoAfter(EntityUid uid, BotanySwabComponent component, DoAfterEvent args) + private void OnDoAfter(EntityUid uid, BotanySwabComponent swab, DoAfterEvent args) { - if (args.Cancelled || args.Handled || !TryComp(args.Args.Target, out var plant) || !TryComp(args.Args.Used, out var swab)) + if (args.Cancelled || args.Handled || !TryComp(args.Args.Target, out var plant)) return; if (swab.SeedData == null) diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs index 598f470d6e..08bcf300d1 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.Injector.cs @@ -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>(AddSetTransferVerbs); SubscribeLocalEvent(OnSolutionChange); - SubscribeLocalEvent(OnInjectDoAfter); + SubscribeLocalEvent(OnInjectDoAfter); SubscribeLocalEvent(OnInjectorStartup); SubscribeLocalEvent(OnInjectorUse); SubscribeLocalEvent(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); } + } diff --git a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs index 6efb96b55b..b4425f66ce 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemistrySystem.cs @@ -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!; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs index 13ee95be14..8f9a20e239 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerSystem.cs @@ -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; diff --git a/Content.Server/Climbing/ClimbSystem.cs b/Content.Server/Climbing/ClimbSystem.cs index 3befaa3e87..82e41d66ce 100644 --- a/Content.Server/Climbing/ClimbSystem.cs +++ b/Content.Server/Climbing/ClimbSystem.cs @@ -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>(AddClimbableVerb); SubscribeLocalEvent(OnClimbableDragDrop); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnClimbEndCollide); SubscribeLocalEvent(OnBuckleChange); SubscribeLocalEvent(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 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 - } } /// diff --git a/Content.Server/Construction/AnchorableSystem.cs b/Content.Server/Construction/AnchorableSystem.cs index 3cb09d642f..4d22fa5fb1 100644 --- a/Content.Server/Construction/AnchorableSystem.cs +++ b/Content.Server/Construction/AnchorableSystem.cs @@ -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(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()); } /// @@ -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()); } /// @@ -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) - { - } - } } } diff --git a/Content.Server/Construction/Components/ConstructionComponent.cs b/Content.Server/Construction/Components/ConstructionComponent.cs index 6274128423..08ffb99fe8 100644 --- a/Content.Server/Construction/Components/ConstructionComponent.cs +++ b/Content.Server/Construction/Components/ConstructionComponent.cs @@ -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 InteractionQueue = new(); } } diff --git a/Content.Server/Construction/Components/WelderRefinableComponent.cs b/Content.Server/Construction/Components/WelderRefinableComponent.cs index a2bca0f430..c0bfd9fce9 100644 --- a/Content.Server/Construction/Components/WelderRefinableComponent.cs +++ b/Content.Server/Construction/Components/WelderRefinableComponent.cs @@ -21,7 +21,5 @@ namespace Content.Server.Construction.Components [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer))] public string QualityNeeded = "Welding"; - - public bool BeingWelded; } } diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index e72848031a..dc002be2a4 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -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, diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index e39a4c3d39..bfe8f7e04d 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -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(OnDoAfterComplete); - SubscribeLocalEvent(OnDoAfterCancelled); - SubscribeLocalEvent(EnqueueEvent); - SubscribeLocalEvent(EnqueueEvent); - SubscribeLocalEvent>(OnDoAfter); - - #endregion + SubscribeLocalEvent(EnqueueEvent); // Event handling. Add your subscriptions here! Just make sure they're all handled by EnqueueEvent. SubscribeLocalEvent(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; + + ev = new InteractUsingEvent( + interactDoAfter.User, + interactDoAfter.Used!.Value, + uid, + interactDoAfter.ClickLocation); + + doAfterState = DoAfterState.Completed; } - // 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; - // 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; + if (doAfterState == DoAfterState.Completed) + return HandleResult.True; - var toolEvData = new ToolEventData(new ConstructionDoAfterComplete(uid, ev), toolInsertStep.Fuel, new ConstructionDoAfterCancelled(uid, ev)); + 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); - 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; + return construction.DoAfter != null ? HandleResult.DoAfter : HandleResult.False; } case TemperatureConstructionGraphStep temperatureChangeStep: { - if (ev is not OnTemperatureChangeEvent) { + if (ev is not OnTemperatureChangeEvent) break; - } if (TryComp(uid, out var tempComp)) { @@ -550,7 +538,8 @@ namespace Content.Server.Construction /// in which case they will also be set as handled. 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 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; - } - } - - /// - /// 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. - /// - 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; - } - } - - /// - /// 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. - /// - 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 /// None, - /// - /// If Validation, we want to validate whether the specified event would handle the step or not. - /// Will NOT modify the construction state at all. - /// - Validation, - /// /// 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. /// - Completed, - - /// - /// 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. - /// - Cancelled + Completed } /// diff --git a/Content.Server/Construction/ConstructionSystem.cs b/Content.Server/Construction/ConstructionSystem.cs index 45b8f232df..237540f9fe 100644 --- a/Content.Server/Construction/ConstructionSystem.cs +++ b/Content.Server/Construction/ConstructionSystem.cs @@ -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!; diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs index c865f47926..2ea4cd10f3 100644 --- a/Content.Server/Construction/PartExchangerSystem.cs +++ b/Content.Server/Construction/PartExchangerSystem.cs @@ -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(OnAfterInteract); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args) { - if (args.Cancelled || args.Handled || args.Args.Target == null) - { - component.AudioStream?.Stop(); - return; - } - component.AudioStream?.Stop(); + if (args.Cancelled || args.Handled || args.Args.Target == null) + return; if (!TryComp(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 }); } + } diff --git a/Content.Server/Construction/RefiningSystem.cs b/Content.Server/Construction/RefiningSystem.cs index 73b4f8d760..cd4eb533e3 100644 --- a/Content.Server/Construction/RefiningSystem.cs +++ b/Content.Server/Construction/RefiningSystem.cs @@ -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,29 +17,22 @@ namespace Content.Server.Construction { base.Initialize(); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(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(args.Used)) + if (args.Handled) return; - // check if someone is already welding object - if (component.BeingWelded) + args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent(), component.RefineFuel); + } + + private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args) + { + if (args.Cancelled) return; - component.BeingWelded = true; - - var toolEvData = new ToolEventData(null); - - if (!_toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, toolEvData, component.RefineFuel)) - { - // failed to veld - abort refine - component.BeingWelded = false; - return; - } - // get last owner coordinates and delete it var resultPosition = Transform(uid).Coordinates; EntityManager.DeleteEntity(uid); @@ -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(droppedEnt, out var stack)) - _stackSystem.SetCount(droppedEnt,1, stack); + _stackSystem.SetCount(droppedEnt, 1, stack); } } } diff --git a/Content.Server/Cuffs/CuffableSystem.cs b/Content.Server/Cuffs/CuffableSystem.cs index 3891941cad..2b65803673 100644 --- a/Content.Server/Cuffs/CuffableSystem.cs +++ b/Content.Server/Cuffs/CuffableSystem.cs @@ -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); diff --git a/Content.Server/Disease/DiseaseDiagnosisSystem.cs b/Content.Server/Disease/DiseaseDiagnosisSystem.cs index 98b4d6c230..37136435a4 100644 --- a/Content.Server/Disease/DiseaseDiagnosisSystem.cs +++ b/Content.Server/Disease/DiseaseDiagnosisSystem.cs @@ -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 /// 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(OnDiagnoserFinished); SubscribeLocalEvent(OnVaccinatorFinished); - SubscribeLocalEvent(OnSwabDoAfter); + SubscribeLocalEvent(OnSwabDoAfter); } private Queue 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 }); } diff --git a/Content.Server/Disease/DiseaseSystem.cs b/Content.Server/Disease/DiseaseSystem.cs index db6f0dc6a5..93a1cab5a3 100644 --- a/Content.Server/Disease/DiseaseSystem.cs +++ b/Content.Server/Disease/DiseaseSystem.cs @@ -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(OnApplyMetabolicMultiplier); // Private events stuff - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private Queue AddQueue = new(); @@ -276,20 +275,21 @@ namespace Content.Server.Disease /// 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 }); } diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index 5a3c2ed642..e64f40a488 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -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>(AddClimbInsideVerb); // Units - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); //UI SubscribeLocalEvent(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; } diff --git a/Content.Server/DoAfter/DoAfterSystem.cs b/Content.Server/DoAfter/DoAfterSystem.cs index ad0d8d27d8..b3d2711f7b 100644 --- a/Content.Server/DoAfter/DoAfterSystem.cs +++ b/Content.Server/DoAfter/DoAfterSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.DoAfter; -using Content.Shared.Mobs; using JetBrains.Annotations; namespace Content.Server.DoAfter; diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index bb01665b8d..d6ecd5d65b 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -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>(OnDoorAltVerb); - SubscribeLocalEvent(OnPryFinished); - SubscribeLocalEvent(OnPryCancelled); + SubscribeLocalEvent(OnPryFinished); SubscribeLocalEvent(OnWeldAttempt); SubscribeLocalEvent(OnWeldChanged); SubscribeLocalEvent(OnEmagged); @@ -174,9 +174,6 @@ public sealed class DoorSystem : SharedDoorSystem /// 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 { } - diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index f4d3809b85..d969272ab9 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -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(OnDragonRift); SubscribeLocalEvent(OnDragonMove); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnMobStateChanged); @@ -75,7 +73,7 @@ namespace Content.Server.Dragon SubscribeLocalEvent(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, }); } } diff --git a/Content.Server/Engineering/Components/DisassembleOnAltVerbComponent.cs b/Content.Server/Engineering/Components/DisassembleOnAltVerbComponent.cs index 802c86518e..7bd620963c 100644 --- a/Content.Server/Engineering/Components/DisassembleOnAltVerbComponent.cs +++ b/Content.Server/Engineering/Components/DisassembleOnAltVerbComponent.cs @@ -12,7 +12,5 @@ namespace Content.Server.Engineering.Components [DataField("doAfter")] public float DoAfterTime = 0; - - public CancellationTokenSource TokenSource { get; } = new(); } } diff --git a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs index 79d25f3520..91bf49b7ec 100644 --- a/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs +++ b/Content.Server/Engineering/EntitySystems/DisassembleOnAltVerbSystem.cs @@ -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(out var doAfterSystem)) + if (component.DoAfterTime > 0 && TryGet(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)) diff --git a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs index 988b72ae88..89a74b2558 100644 --- a/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs +++ b/Content.Server/Engineering/EntitySystems/SpawnAfterInteractSystem.cs @@ -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(component.Owner, out var stackComp) diff --git a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs index dbb45f1f9b..f65871ef99 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.Ensnaring.cs @@ -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 /// /// Used where you want to try to free an entity with the /// - /// The entity that will be free + /// The entity that will be freed + /// The entity that is freeing the target /// The entity used to ensnare /// The ensnaring component - 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(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); } /// diff --git a/Content.Server/Ensnaring/EnsnareableSystem.cs b/Content.Server/Ensnaring/EnsnareableSystem.cs index d883cdb418..48e34537f8 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.cs @@ -20,7 +20,7 @@ public sealed partial class EnsnareableSystem : SharedEnsnareableSystem InitializeEnsnaring(); SubscribeLocalEvent(OnEnsnareableInit); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnEnsnareableInit(EntityUid uid, EnsnareableComponent component, ComponentInit args) diff --git a/Content.Server/Fluids/EntitySystems/MoppingSystem.cs b/Content.Server/Fluids/EntitySystems/MoppingSystem.cs index 4f7304ee6c..9aab13ba6e 100644 --- a/Content.Server/Fluids/EntitySystems/MoppingSystem.cs +++ b/Content.Server/Fluids/EntitySystems/MoppingSystem.cs @@ -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(OnAbsorbentInit); SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnAbsorbentSolutionChange); - SubscribeLocalEvent>(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 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; - } } diff --git a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs index 32e6dcbaa4..f98656e291 100644 --- a/Content.Server/Fluids/EntitySystems/SpillableSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpillableSystem.cs @@ -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>(AddSpillVerb); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnSpikeOverflow); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(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 = () => { - verb.Act = () => + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(args.User, component.SpillDelay ?? 0, new SpillDoAfterEvent(), uid, target: uid) { - var puddleSolution = _solutionContainerSystem.SplitSolution(args.Target, - solution, solution.Volume); - SpillAt(puddleSolution, Transform(args.Target).Coordinates, "PuddleSmear"); - }; - } - else - { - verb.Act = () => - { - _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); diff --git a/Content.Server/Forensics/Systems/ForensicPadSystem.cs b/Content.Server/Forensics/Systems/ForensicPadSystem.cs index bf419e2f64..f7a79ef974 100644 --- a/Content.Server/Forensics/Systems/ForensicPadSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicPadSystem.cs @@ -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 /// 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(OnExamined); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(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 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; - } - } } } diff --git a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs index 83dfc87223..4f629590c8 100644 --- a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs @@ -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>(OnUtilityVerb); SubscribeLocalEvent(OnPrint); SubscribeLocalEvent(OnClear); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent component) @@ -91,11 +90,10 @@ namespace Content.Server.Forensics /// 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 }); } diff --git a/Content.Server/Gatherable/Components/GatheringToolComponent.cs b/Content.Server/Gatherable/Components/GatheringToolComponent.cs index 9bf5bb2018..1d6f806904 100644 --- a/Content.Server/Gatherable/Components/GatheringToolComponent.cs +++ b/Content.Server/Gatherable/Components/GatheringToolComponent.cs @@ -32,6 +32,7 @@ namespace Content.Server.Gatherable.Components public int MaxGatheringEntities = 1; [ViewVariables] + [DataField("gatheringEntities")] public readonly List GatheringEntities = new(); } } diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs index e82a5130d3..0aa8c256f8 100644 --- a/Content.Server/Gatherable/GatherableSystem.cs +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -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(OnInteractUsing); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(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(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) diff --git a/Content.Server/Guardian/GuardianCreatorComponent.cs b/Content.Server/Guardian/GuardianCreatorComponent.cs index e120cc3b25..7729348453 100644 --- a/Content.Server/Guardian/GuardianCreatorComponent.cs +++ b/Content.Server/Guardian/GuardianCreatorComponent.cs @@ -28,7 +28,5 @@ namespace Content.Server.Guardian /// [DataField("delay")] public float InjectionDelay = 5f; - - public bool Injecting = false; } } diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs index b1ca676e04..ed6811449b 100644 --- a/Content.Server/Guardian/GuardianSystem.cs +++ b/Content.Server/Guardian/GuardianSystem.cs @@ -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 /// 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(OnCreatorUse); SubscribeLocalEvent(OnCreatorInteract); SubscribeLocalEvent(OnCreatorExamine); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnGuardianMove); SubscribeLocalEvent(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(args.Args.Target)) - { - component.Injecting = false; return; - } var hostXform = Transform(args.Args.Target.Value); var host = EnsureComp(args.Args.Target.Value); diff --git a/Content.Server/Implants/ImplanterSystem.cs b/Content.Server/Implants/ImplanterSystem.cs index 451f4d074e..f34d8eeb06 100644 --- a/Content.Server/Implants/ImplanterSystem.cs +++ b/Content.Server/Implants/ImplanterSystem.cs @@ -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(OnHandDeselect); SubscribeLocalEvent(OnImplanterAfterInteract); SubscribeLocalEvent(OnImplanterGetState); - SubscribeLocalEvent>(OnImplant); - SubscribeLocalEvent>(OnDraw); + SubscribeLocalEvent(OnImplant); + SubscribeLocalEvent(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; - } - /// /// Attempt to implant someone else. /// @@ -77,27 +67,21 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem /// The implanter being used public void TryImplant(ImplanterComponent component, EntityUid user, EntityUid target, EntityUid implanter) { - if (component.CancelToken != null) + 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); } /// @@ -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 args) + private void OnImplant(EntityUid uid, ImplanterComponent component, ImplantEvent args) { - if (args.Cancelled) - { - component.CancelToken = null; - return; - } - - if (args.Handled || args.Args.Target == null || args.Args.Used == null) + if (args.Cancelled || args.Handled || args.Target == null || 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 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 - { - } } diff --git a/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs b/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs index 3b608ceb87..f9d41e82c0 100644 --- a/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/KitchenSpikeSystem.cs @@ -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(OnDragDrop); //DoAfter - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(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; } diff --git a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs index 4a9e259e29..5a798132b9 100644 --- a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs @@ -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(OnAfterInteract); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent>(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) diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index 545ad4d4f3..9cd0c06dc4 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -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(OnPowerChanged); - SubscribeLocalEvent(OnDoAfter); - + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(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); } } } diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index 5cdb549b7a..5374d9ab74 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -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(OnInit); SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnInstantSpawn); SubscribeLocalEvent(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 diff --git a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs index baf4eafd21..310c5a50c8 100644 --- a/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs +++ b/Content.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.cs @@ -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(OnAttemptRemove); SubscribeLocalEvent(OnInteract); - SubscribeLocalEvent(OnMechGrab); + SubscribeLocalEvent(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 diff --git a/Content.Server/Mech/Systems/MechEquipmentSystem.cs b/Content.Server/Mech/Systems/MechEquipmentSystem.cs index 8a9032a21d..9ffa1030e1 100644 --- a/Content.Server/Mech/Systems/MechEquipmentSystem.cs +++ b/Content.Server/Mech/Systems/MechEquipmentSystem.cs @@ -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!; /// public override void Initialize() { SubscribeLocalEvent(OnUsed); - SubscribeLocalEvent>(OnInsertEquipment); + SubscribeLocalEvent(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 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 - { - - } } diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index cf41335f35..d264eb59b4 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -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(OnMapInit); SubscribeLocalEvent>(OnAlternativeVerb); SubscribeLocalEvent(OnOpenUi); - SubscribeLocalEvent>(OnRemoveBattery); - SubscribeLocalEvent>(OnMechEntry); - SubscribeLocalEvent>(OnMechExit); + SubscribeLocalEvent(OnRemoveBattery); + SubscribeLocalEvent(OnMechEntry); + SubscribeLocalEvent(OnMechExit); SubscribeLocalEvent(OnDamageChanged); SubscribeLocalEvent(OnRemoveEquipmentMessage); @@ -84,22 +83,17 @@ public sealed class MechSystem : SharedMechSystem if (TryComp(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 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 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 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 - - /// - /// Event raised when the battery is successfully removed from the mech, - /// on both success and failure - /// - private sealed class RemoveBatteryEvent : EntityEventArgs - { - } - - /// - /// Event raised when a person enters a mech, on both success and failure - /// - private sealed class MechEntryEvent : EntityEventArgs - { - } - - /// - /// Event raised when a person removes someone from a mech, - /// on both success and failure - /// - private sealed class MechExitEvent : EntityEventArgs - { - } } diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index b418e6a6e3..a36bc01ed6 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -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(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnSuicide); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnSuicide(EntityUid uid, BiomassReclaimerComponent component, SuicideEvent args) @@ -152,11 +150,10 @@ namespace Content.Server.Medical.BiomassReclaimer if (!HasComp(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 }); } diff --git a/Content.Server/Medical/Components/HealingComponent.cs b/Content.Server/Medical/Components/HealingComponent.cs index 5f3120cff0..adb5857b32 100644 --- a/Content.Server/Medical/Components/HealingComponent.cs +++ b/Content.Server/Medical/Components/HealingComponent.cs @@ -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; - /// - /// Cancel token to prevent rapid healing - /// - [DataField("cancelToken")] - public CancellationTokenSource? CancelToken; - /// /// Delay multiplier when healing yourself. /// diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index 886922986b..62c168c3e7 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -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(OnComponentInit); SubscribeLocalEvent>(AddAlternativeVerbs); SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDragFinished); SubscribeLocalEvent(OnCryoPodPryFinished); - SubscribeLocalEvent(OnCryoPodPryInterrupted); SubscribeLocalEvent(OnCryoPodUpdateAtmosphere); SubscribeLocalEvent(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) diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index 9b4d9e77ed..d4382848f0 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -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(OnHealingUse); SubscribeLocalEvent(OnHealingAfterInteract); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } - private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent 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; + var total = healed?.Total ?? FixedPoint2.Zero; + // Reverify that we can heal the damage. - _stacks.Use(args.Args.Used.Value, 1, args.AdditionalData.Stack); - - if (uid != args.Args.User) - _adminLogger.Add(LogType.Healed, $"{EntityManager.ToPrettyString(args.Args.User):user} healed {EntityManager.ToPrettyString(uid):target} for {healed.Total:damage} damage"); + _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(target, out var targetDamage) || component.CancelToken != null) + if (_mobStateSystem.IsDead(target) || !TryComp(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 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(uid, out var mobThreshold) || !TryComp(uid, out var damageable)) + if (!TryComp(uid, out var mobThreshold) || + !TryComp(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; - } } diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index 0ba0770036..67fa2ebac8 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -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(HandleActivateInWorld); SubscribeLocalEvent(OnAfterInteract); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(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 }); } diff --git a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs index af9589d016..b89c463dff 100644 --- a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs +++ b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs @@ -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>(AddStethoscopeVerb); SubscribeLocalEvent(OnGetActions); SubscribeLocalEvent(OnStethoscopeAction); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } /// @@ -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 }); } diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs index 06aa9f7ca9..526eaab55a 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Obstacles.cs @@ -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; diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 47e10929a3..fa18297a64 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -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(OnEnterButtonPressed); // Doafter events - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(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); } diff --git a/Content.Server/Nutrition/Components/DrinkComponent.cs b/Content.Server/Nutrition/Components/DrinkComponent.cs index 60784ac412..7bc1913fe2 100644 --- a/Content.Server/Nutrition/Components/DrinkComponent.cs +++ b/Content.Server/Nutrition/Components/DrinkComponent.cs @@ -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"); - /// - /// Is this drink being forced on someone else? - /// - /// - [DataField("forceDrink")] - public bool ForceDrink; - - /// - /// Is the entity currently drinking or trying to make someone else drink? - /// - [DataField("drinking")] - public bool Drinking; - /// /// How long it takes to drink this yourself. /// diff --git a/Content.Server/Nutrition/Components/FoodComponent.cs b/Content.Server/Nutrition/Components/FoodComponent.cs index 7e9effdfda..eb76cbbf55 100644 --- a/Content.Server/Nutrition/Components/FoodComponent.cs +++ b/Content.Server/Nutrition/Components/FoodComponent.cs @@ -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"; - /// - /// Is this entity being forcefed? - /// - [DataField("forceFeed")] - public bool ForceFeed; - - /// - /// Is this entity eating or being fed? - /// - [DataField(("eating"))] - public bool Eating; - /// /// How long it takes to eat the food personally. /// diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 933efc268a..21ab837b48 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -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(OnSolutionChange); SubscribeLocalEvent(OnDrinkInit); SubscribeLocalEvent(HandleLand); @@ -63,7 +63,7 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent>(AddDrinkVerb); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnTransferAttempt); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(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(target) || drink.Drinking) + if (!EntityManager.HasComponent(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; } /// /// Raised directed at a victim when someone has force fed them a drink. /// - private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent args) + private void OnDoAfter(EntityUid uid, DrinkComponent component, ConsumeDoAfterEvent args) { - if (args.Cancelled) - { - component.ForceDrink = false; - component.Drinking = false; - return; - } - - if (args.Handled || component.Deleted) + if (args.Handled || args.Cancelled || component.Deleted) return; if (!TryComp(args.Args.Target, out var body)) return; - component.Drinking = false; + if (!_solutionContainerSystem.TryGetSolution(args.Used, args.Solution, out var solution)) + return; - var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume); - var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount); + var transferAmount = FixedPoint2.Min(component.TransferAmount, solution.Volume); + var drained = _solutionContainerSystem.Drain(uid, solution, transferAmount); + var forceDrink = args.User != args.Target; + + //var forceDrink = args.Args.Target.Value != args.Args.User; if (!_bodySystem.TryGetBodyOrganComponents(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(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(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; - } } } diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index f7f9358cee..30cd1c8ba6 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -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(OnUseFoodInHand); SubscribeLocalEvent(OnFeedFood); SubscribeLocalEvent>(AddEatVerb); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(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); } /// @@ -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(target) || foodComp.Eating) + if (!EntityManager.HasComponent(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 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(args.Args.Target.Value, out var body)) @@ -169,29 +163,36 @@ namespace Content.Server.Nutrition.EntitySystems if (!_bodySystem.TryGetBodyOrganComponents(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; - var split = _solutionContainerSystem.SplitSolution(uid, args.AdditionalData.FoodSolution, transferAmount); + args.Handled = true; + + var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, solution.Volume) : solution.Volume; + + 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 utensils, HandsComponent? hands = null) + out List utensils, HandsComponent? hands = null) { - utensils = new List(); + utensils = new List(); 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 Utensils) - { - public readonly Solution FoodSolution = FoodSolution; - public readonly string FlavorMessage = FlavorMessage; - public readonly List Utensils = Utensils; - } } } diff --git a/Content.Server/Power/EntitySystems/ApcSystem.cs b/Content.Server/Power/EntitySystems/ApcSystem.cs index 691ba32d8a..183422aa21 100644 --- a/Content.Server/Power/EntitySystems/ApcSystem.cs +++ b/Content.Server/Power/EntitySystems/ApcSystem.cs @@ -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(OnToggleMainBreaker); SubscribeLocalEvent(OnEmagged); - SubscribeLocalEvent(OnToolFinished); + SubscribeLocalEvent(OnToolFinished); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(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; + UpdatePanelAppearance(uid, apc: component); - if (TryComp(args.Target, out AppearanceComponent? appearance)) - { - UpdatePanelAppearance(args.Target, appearance); - } - - 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 diff --git a/Content.Server/Power/EntitySystems/CableSystem.cs b/Content.Server/Power/EntitySystems/CableSystem.cs index eead07f34d..8dd09fab35 100644 --- a/Content.Server/Power/EntitySystems/CableSystem.cs +++ b/Content.Server/Power/EntitySystems/CableSystem.cs @@ -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(OnInteractUsing); - SubscribeLocalEvent(OnCableCut); + SubscribeLocalEvent(OnCableCut); // Shouldn't need re-anchoring. SubscribeLocalEvent(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; - } -} diff --git a/Content.Server/RCD/Components/RCDComponent.cs b/Content.Server/RCD/Components/RCDComponent.cs index 23c8009d56..5375ae71b7 100644 --- a/Content.Server/RCD/Components/RCDComponent.cs +++ b/Content.Server/RCD/Components/RCDComponent.cs @@ -39,7 +39,5 @@ namespace Content.Server.RCD.Components /// [ViewVariables(VVAccess.ReadWrite)] [DataField("ammo")] public int CurrentAmmo = DefaultAmmoCount; - - public CancellationTokenSource? CancelToken = null; } } diff --git a/Content.Server/RCD/Systems/RCDSystem.cs b/Content.Server/RCD/Systems/RCDSystem.cs index 38c4e0f36a..b1b674a0aa 100644 --- a/Content.Server/RCD/Systems/RCDSystem.cs +++ b/Content.Server/RCD/Systems/RCDSystem.cs @@ -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; diff --git a/Content.Server/Repairable/RepairableSystem.cs b/Content.Server/Repairable/RepairableSystem.cs index bf7593185d..56f1eb0903 100644 --- a/Content.Server/Repairable/RepairableSystem.cs +++ b/Content.Server/Repairable/RepairableSystem.cs @@ -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); } } } diff --git a/Content.Server/Resist/CanEscapeInventoryComponent.cs b/Content.Server/Resist/CanEscapeInventoryComponent.cs index b67adfc203..6572eca330 100644 --- a/Content.Server/Resist/CanEscapeInventoryComponent.cs +++ b/Content.Server/Resist/CanEscapeInventoryComponent.cs @@ -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; } diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs index 6230b16fc6..43012d8b7e 100644 --- a/Content.Server/Resist/EscapeInventorySystem.cs +++ b/Content.Server/Resist/EscapeInventorySystem.cs @@ -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(OnRelayMovement); - SubscribeLocalEvent>(OnEscape); + SubscribeLocalEvent(OnEscape); SubscribeLocalEvent(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 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); } } diff --git a/Content.Server/Resist/ResistLockerComponent.cs b/Content.Server/Resist/ResistLockerComponent.cs index 7cb2149100..52a4683abf 100644 --- a/Content.Server/Resist/ResistLockerComponent.cs +++ b/Content.Server/Resist/ResistLockerComponent.cs @@ -17,6 +17,4 @@ public sealed class ResistLockerComponent : Component /// [ViewVariables] public bool IsResisting = false; - - public CancellationTokenSource? CancelToken; } diff --git a/Content.Server/Resist/ResistLockerSystem.cs b/Content.Server/Resist/ResistLockerSystem.cs index 78da7c012a..4b433073ca 100644 --- a/Content.Server/Resist/ResistLockerSystem.cs +++ b/Content.Server/Resist/ResistLockerSystem.cs @@ -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(OnRelayMovement); - SubscribeLocalEvent>(OnDoAfter); - SubscribeLocalEvent(OnRemoved); + SubscribeLocalEvent(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()); + _doAfterSystem.TryStartDoAfter(doAfterEventArgs); } - private void OnRemoved(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage args) - { - component.CancelToken?.Cancel(); - component.CancelToken = null; - } - - private void OnDoAfter(EntityUid uid, ResistLockerComponent component, DoAfterEvent 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 - { - } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 7c3b1508e7..44313457f8 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -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(OnInteract); - SubscribeLocalEvent>(OnSoulSearch); - SubscribeLocalEvent>(OnHarvest); + SubscribeLocalEvent(OnSoulSearch); + SubscribeLocalEvent(OnHarvest); SubscribeLocalEvent(OnDefileAction); SubscribeLocalEvent(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 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 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 - { - - } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index b8d78f5803..968da8c269 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -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!; diff --git a/Content.Server/Sticky/Systems/StickySystem.cs b/Content.Server/Sticky/Systems/StickySystem.cs index 9437ebbd00..fb9c5fab37 100644 --- a/Content.Server/Sticky/Systems/StickySystem.cs +++ b/Content.Server/Sticky/Systems/StickySystem.cs @@ -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(OnStickSuccessful); + SubscribeLocalEvent(OnStickFinished); SubscribeLocalEvent(OnAfterInteract); SubscribeLocalEvent>(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 diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index 8c2e06cf48..5050b6dd01 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -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(OnStartup); SubscribeLocalEvent(PreOpen); SubscribeLocalEvent(PostClose); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args) @@ -284,7 +284,7 @@ public sealed class BluespaceLockerSystem : EntitySystem { EnsureComp(uid); - _doAfterSystem.DoAfter(new DoAfterEventArgs(uid, component.BehaviorProperties.Delay)); + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.BehaviorProperties.Delay, new BluespaceLockerDoAfterEvent(), uid)); return; } diff --git a/Content.Server/Storage/EntitySystems/DumpableSystem.cs b/Content.Server/Storage/EntitySystems/DumpableSystem.cs index a59e439bec..69a2742542 100644 --- a/Content.Server/Storage/EntitySystems/DumpableSystem.cs +++ b/Content.Server/Storage/EntitySystems/DumpableSystem.cs @@ -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(OnAfterInteract, after: new[]{ typeof(StorageSystem) }); SubscribeLocalEvent>(AddDumpVerb); SubscribeLocalEvent>(AddUtilityVerbs); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args) { - if (!args.CanReach) + if (!args.CanReach || args.Handled) return; - if (HasComp(args.Target) || HasComp(args.Target)) - StartDoAfter(uid, args.Target.Value, args.User, component); + if (!HasComp(args.Target) && !HasComp(args.Target)) + return; + + StartDoAfter(uid, args.Target.Value, args.User, component); + args.Handled = true; } private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent 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 }); } diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs index c8adcec54b..b09e436dc1 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -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(OnBoundUIClosed); SubscribeLocalEvent(OnStorageItemRemoved); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(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 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(); 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 validStorables) - { - public List ValidStorables = validStorables; - } } } diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 282e197d83..b0c9322ba7 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -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(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(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(component.Owner, out var inventory)) + if (!TryComp(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(args.User)) + if (!TryComp(args.User, out var actor)) return; - args.Handled = true; StartOpeningStripper(args.User, component); } /// /// Places item in user's active hand to an inventory slot. /// - 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(user); bool Check() { - if (userHands.ActiveHand?.HeldEntity is not { } held) + if (userHands.ActiveHandEntity != held) + return false; + + if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand!)) { - user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); + _popup.PopupCursor(Loc.GetString("strippable-component-cannot-drop"), user); return false; } - if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand)) + if (_inventorySystem.TryGetSlotEntity(target, slot, out _)) { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); + _popup.PopupCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", target)), user); return false; } - if (!_inventorySystem.HasSlot(component.Owner, slot)) - return false; - - if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _)) + if (!_inventorySystem.CanEquip(user, target, held, slot, out _)) { - user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", component.Owner))); - return false; - } - - if (!_inventorySystem.CanEquip(user, component.Owner, 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)) - { - _inventorySystem.TryEquip(user, component.Owner, held, slot); + DebugTools.Assert(userHands.ActiveHand?.HeldEntity == held); - _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(component.Owner):target}'s {slot} slot"); + if (_handsSystem.TryDrop(user, handsComp: userHands)) + { + _inventorySystem.TryEquip(user, target, held, slot); + + _adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):user} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot"); } } /// /// Places item in user's active hand in one of the entity's hands. /// - 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(component.Owner); + var hands = Comp(target); var userHands = Comp(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 } /// /// Takes an item from the inventory and places it in the user's active hand. /// - 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}"); - } } /// /// Takes an item from a hand and places it in the user's active hand. /// - 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(component.Owner); + var hands = Comp(target); var userHands = Comp(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(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}"); } } } diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 98a2aff1a8..f3a3b986a0 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -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!; /// public override void Initialize() { SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(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); } } diff --git a/Content.Server/Toilet/ToiletSystem.cs b/Content.Server/Toilet/ToiletSystem.cs index b98c5c9d38..03591433fb 100644 --- a/Content.Server/Toilet/ToiletSystem.cs +++ b/Content.Server/Toilet/ToiletSystem.cs @@ -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(OnInteractHand, new []{typeof(BuckleSystem)}); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnSuicide); - SubscribeLocalEvent(OnToiletPried); - SubscribeLocalEvent(OnToiletInterrupt); + SubscribeLocalEvent(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) + private void OnToiletPried(EntityUid uid, ToiletComponent toilet, ToiletPryDoAfterEvent args) { - if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet)) + if (args.Cancelled) return; - toilet.IsPrying = false; - } - - private void OnToiletPried(ToiletPryFinished ev) - { - if (!EntityManager.TryGetComponent(ev.Uid, out ToiletComponent? toilet)) - 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; - } - } } diff --git a/Content.Server/Tools/Components/LatticeCuttingComponent.cs b/Content.Server/Tools/Components/LatticeCuttingComponent.cs index b1e4e18fec..bbc782a5f1 100644 --- a/Content.Server/Tools/Components/LatticeCuttingComponent.cs +++ b/Content.Server/Tools/Components/LatticeCuttingComponent.cs @@ -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))] public string QualityNeeded = "Cutting"; diff --git a/Content.Server/Tools/Components/TilePryingComponent.cs b/Content.Server/Tools/Components/TilePryingComponent.cs index 9dee4fc004..82566e3559 100644 --- a/Content.Server/Tools/Components/TilePryingComponent.cs +++ b/Content.Server/Tools/Components/TilePryingComponent.cs @@ -15,8 +15,5 @@ namespace Content.Server.Tools.Components [DataField("delay")] public float Delay = 1f; - - [DataField("cancelToken")] - public CancellationTokenSource? CancelToken; } } diff --git a/Content.Server/Tools/Components/WeldableComponent.cs b/Content.Server/Tools/Components/WeldableComponent.cs index b59f493e23..46062692d3 100644 --- a/Content.Server/Tools/Components/WeldableComponent.cs +++ b/Content.Server/Tools/Components/WeldableComponent.cs @@ -47,15 +47,9 @@ public sealed class WeldableComponent : SharedWeldableComponent [ViewVariables(VVAccess.ReadWrite)] public string? WeldedExamineMessage = "weldable-component-examine-is-welded"; - /// - /// Whether something is currently using a welder on this so DoAfter isn't spammed. - /// - [ViewVariables(VVAccess.ReadOnly)] - public bool BeingWelded; - /// /// Is this entity currently welded shut? /// - [ViewVariables(VVAccess.ReadOnly)] + [DataField("isWelded")] public bool IsWelded; } diff --git a/Content.Server/Tools/Systems/WeldableSystem.cs b/Content.Server/Tools/Systems/WeldableSystem.cs index 780cab4368..c04ab840ef 100644 --- a/Content.Server/Tools/Systems/WeldableSystem.cs +++ b/Content.Server/Tools/Systems/WeldableSystem.cs @@ -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(OnInteractUsing); SubscribeLocalEvent(OnWeldFinished); - SubscribeLocalEvent(OnWeldCanceled); SubscribeLocalEvent(OnWeldChanged); SubscribeLocalEvent(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(uid, out var fixtures)) @@ -148,30 +143,6 @@ public sealed class WeldableSystem : EntitySystem return; component.WeldingTime = time; } - - /// - /// Raised after welding do_after has finished. It doesn't guarantee success, - /// use to get updated status. - /// - private sealed class WeldFinishedEvent : EntityEventArgs - { - public readonly EntityUid User; - public readonly EntityUid Tool; - - public WeldFinishedEvent(EntityUid user, EntityUid tool) - { - User = user; - Tool = tool; - } - } - - /// - /// Raised when entity welding has failed. - /// - private sealed class WeldCancelledEvent : EntityEventArgs - { - - } } /// diff --git a/Content.Server/Tools/ToolSystem.LatticeCutting.cs b/Content.Server/Tools/ToolSystem.LatticeCutting.cs index 4a62865561..c9d08eb29a 100644 --- a/Content.Server/Tools/ToolSystem.LatticeCutting.cs +++ b/Content.Server/Tools/ToolSystem.LatticeCutting.cs @@ -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(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); } } diff --git a/Content.Server/Tools/ToolSystem.TilePrying.cs b/Content.Server/Tools/ToolSystem.TilePrying.cs index ea257548d3..59c041010b 100644 --- a/Content.Server/Tools/ToolSystem.TilePrying.cs +++ b/Content.Server/Tools/ToolSystem.TilePrying.cs @@ -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(OnTilePryingAfterInteract); - SubscribeLocalEvent(OnTilePryComplete); - SubscribeLocalEvent(OnTilePryCancelled); + SubscribeLocalEvent(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(toolEntity, out var tool) && component.ToolComponentNeeded || component.CancelToken != null) + if (!TryComp(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); } } diff --git a/Content.Server/Tools/ToolSystem.Welder.cs b/Content.Server/Tools/ToolSystem.Welder.cs index d50d4c106f..43f43dc9d4 100644 --- a/Content.Server/Tools/ToolSystem.Welder.cs +++ b/Content.Server/Tools/ToolSystem.Welder.cs @@ -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(OnWelderSolutionChange); SubscribeLocalEvent(OnWelderActivate); SubscribeLocalEvent(OnWelderAfterInteract); - SubscribeLocalEvent(OnWelderToolUseAttempt); - SubscribeLocalEvent(OnWelderToolUseFinishAttempt); + SubscribeLocalEvent>(OnWelderToolUseAttempt); + SubscribeLocalEvent(OnWelderDoAfter); SubscribeLocalEvent(OnWelderShutdown); SubscribeLocalEvent(OnWelderGetState); SubscribeLocalEvent(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 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); } diff --git a/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs b/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs index 39ae673902..a3fa90495c 100644 --- a/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs +++ b/Content.Server/VendingMachines/Restock/VendingMachineRestockSystem.cs @@ -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(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, diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 8072027f78..0fb12fd55b 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -55,7 +55,7 @@ namespace Content.Server.VendingMachines SubscribeLocalEvent(OnSelfDispense); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); } private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args) diff --git a/Content.Server/Wieldable/WieldableSystem.cs b/Content.Server/Wieldable/WieldableSystem.cs index b285a972ce..affda62f21 100644 --- a/Content.Server/Wieldable/WieldableSystem.cs +++ b/Content.Server/Wieldable/WieldableSystem.cs @@ -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(OnUseInHand); - SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnItemUnwielded); SubscribeLocalEvent(OnItemLeaveHand); SubscribeLocalEvent(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); } /// diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index 7a3f5f7a05..2199ee1643 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -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 _layouts = new(); @@ -48,15 +46,14 @@ public sealed class WiresSystem : SharedWiresSystem SubscribeLocalEvent(Reset); // this is a broadcast event - SubscribeLocalEvent(OnToolFinished); - SubscribeLocalEvent(OnToolCanceled); + SubscribeLocalEvent(OnPanelDoAfter); SubscribeLocalEvent(OnWiresStartup); SubscribeLocalEvent(OnWiresActionMessage); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnTimedWire); SubscribeLocalEvent(OnWiresPowered); - SubscribeLocalEvent>(OnDoAfter); + SubscribeLocalEvent(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 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(args.Used, out var tool) || !TryComp(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((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(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 diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index e0d772c718..93aa5dd909 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -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); diff --git a/Content.Shared/AirlockPainter/AirlockPainterEvents.cs b/Content.Shared/AirlockPainter/AirlockPainterEvents.cs index 34e5518acd..d2223313bc 100644 --- a/Content.Shared/AirlockPainter/AirlockPainterEvents.cs +++ b/Content.Shared/AirlockPainter/AirlockPainterEvents.cs @@ -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 index 0000000000..b280b60b70 --- /dev/null +++ b/Content.Shared/Anomaly/ScannerDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs b/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs index 7c81495f08..3c1e2e2e56 100644 --- a/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs +++ b/Content.Shared/Chemistry/Components/SharedInjectorComponent.cs @@ -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 + { + } + /// /// Shared class for injectors & syringes /// [NetworkedComponent, ComponentProtoName("Injector")] public abstract class SharedInjectorComponent : Component { - /// - /// Checks to see if the entity being injected - /// - [DataField("isInjecting")] - public bool IsInjecting; - /// /// Component data used for net updates. Used by client for item status ui /// @@ -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; diff --git a/Content.Shared/Climbing/SharedClimbSystem.cs b/Content.Shared/Climbing/SharedClimbSystem.cs index 6521599f54..915b69392a 100644 --- a/Content.Shared/Climbing/SharedClimbSystem.cs +++ b/Content.Shared/Climbing/SharedClimbSystem.cs @@ -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(args.Dragged); } + + [Serializable, NetSerializable] + protected sealed class ClimbDoAfterEvent : SimpleDoAfterEvent + { + } } diff --git a/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs index a029ad833c..fc584639e0 100644 --- a/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs +++ b/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs @@ -69,7 +69,4 @@ public sealed class ToggleableClothingComponent : Component /// [DataField("verbText")] public string? VerbText; - - // prevent duplicate doafters - public byte? DoAfterId; } diff --git a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs index b7ea47dc7b..0f69dafbed 100644 --- a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs @@ -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>>(GetRelayedVerbs); SubscribeLocalEvent>(OnGetVerbs); SubscribeLocalEvent>(OnGetAttachedStripVerbsEvent); - SubscribeLocalEvent>(OnDoAfterComplete); + SubscribeLocalEvent(OnDoAfterComplete); } private void GetRelayedVerbs(EntityUid uid, ToggleableClothingComponent component, InventoryRelayedEvent> 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 args) @@ -131,15 +124,12 @@ public sealed class ToggleableClothingSystem : EntitySystem OnGetVerbs(component.AttachedUid, Comp(component.AttachedUid), args); } - private void OnDoAfterComplete(EntityUid uid, ToggleableClothingComponent component, DoAfterEvent 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 /// private void OnToggleClothing(EntityUid uid, ToggleableClothingComponent component, ToggleClothingEvent args) { - if (args.Handled || component.Container == null || component.ClothingUid == null) + if (args.Handled) return; - var parent = Transform(uid).ParentUid; + 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(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 +{ +} diff --git a/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs index 7c8a8620f9..93e9504b12 100644 --- a/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/SharedAnchorableSystem.cs @@ -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 index 0000000000..1a4bace014 --- /dev/null +++ b/Content.Shared/Construction/Events.cs @@ -0,0 +1,124 @@ +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared.Construction; + +/// +/// Sent client -> server to to tell the server that we started building +/// a structure-construction. +/// +[Serializable, NetSerializable] +public sealed class TryStartStructureConstructionMessage : EntityEventArgs +{ + /// + /// Position to start building. + /// + public readonly EntityCoordinates Location; + + /// + /// The construction prototype to start building. + /// + public readonly string PrototypeName; + + public readonly Angle Angle; + + /// + /// Identifier to be sent back in the acknowledgement so that the client can clean up its ghost. + /// + public readonly int Ack; + + public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack) + { + Location = loc; + PrototypeName = prototypeName; + Angle = angle; + Ack = ack; + } +} + +/// +/// Sent client -> server to to tell the server that we started building +/// an item-construction. +/// +[Serializable, NetSerializable] +public sealed class TryStartItemConstructionMessage : EntityEventArgs +{ + /// + /// The construction prototype to start building. + /// + public readonly string PrototypeName; + + public TryStartItemConstructionMessage(string prototypeName) + { + PrototypeName = prototypeName; + } +} + +/// +/// Sent server -> client to tell the client that a ghost has started to be constructed. +/// +[Serializable, NetSerializable] +public sealed class AckStructureConstructionMessage : EntityEventArgs +{ + public readonly int GhostId; + + public AckStructureConstructionMessage(int ghostId) + { + GhostId = ghostId; + } +} + +/// +/// Sent client -> server to request a specific construction guide. +/// +[Serializable, NetSerializable] +public sealed class RequestConstructionGuide : EntityEventArgs +{ + public readonly string ConstructionId; + + public RequestConstructionGuide(string constructionId) + { + ConstructionId = constructionId; + } +} + +/// +/// Sent server -> client as a response to a net message. +/// +[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 +{ +} diff --git a/Content.Shared/Construction/SharedConstructionSystem.cs b/Content.Shared/Construction/SharedConstructionSystem.cs index 150ce79a01..5e2f335200 100644 --- a/Content.Shared/Construction/SharedConstructionSystem.cs +++ b/Content.Shared/Construction/SharedConstructionSystem.cs @@ -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); } - - /// - /// Sent client -> server to to tell the server that we started building - /// a structure-construction. - /// - [Serializable, NetSerializable] - public sealed class TryStartStructureConstructionMessage : EntityEventArgs - { - /// - /// Position to start building. - /// - public readonly EntityCoordinates Location; - - /// - /// The construction prototype to start building. - /// - public readonly string PrototypeName; - - public readonly Angle Angle; - - /// - /// Identifier to be sent back in the acknowledgement so that the client can clean up its ghost. - /// - public readonly int Ack; - - public TryStartStructureConstructionMessage(EntityCoordinates loc, string prototypeName, Angle angle, int ack) - { - Location = loc; - PrototypeName = prototypeName; - Angle = angle; - Ack = ack; - } - } - - /// - /// Sent client -> server to to tell the server that we started building - /// an item-construction. - /// - [Serializable, NetSerializable] - public sealed class TryStartItemConstructionMessage : EntityEventArgs - { - /// - /// The construction prototype to start building. - /// - public readonly string PrototypeName; - - public TryStartItemConstructionMessage(string prototypeName) - { - PrototypeName = prototypeName; - } - } - - /// - /// Sent server -> client to tell the client that a ghost has started to be constructed. - /// - [Serializable, NetSerializable] - public sealed class AckStructureConstructionMessage : EntityEventArgs - { - public readonly int GhostId; - - public AckStructureConstructionMessage(int ghostId) - { - GhostId = ghostId; - } - } - - /// - /// Sent client -> server to request a specific construction guide. - /// - [Serializable, NetSerializable] - public sealed class RequestConstructionGuide : EntityEventArgs - { - public readonly string ConstructionId; - - public RequestConstructionGuide(string constructionId) - { - ConstructionId = constructionId; - } - } - - /// - /// Sent server -> client as a response to a net message. - /// - [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; - } - } } } diff --git a/Content.Shared/Cuffs/Components/CuffableComponent.cs b/Content.Shared/Cuffs/Components/CuffableComponent.cs index 14c572b7b8..624cc06ac4 100644 --- a/Content.Shared/Cuffs/Components/CuffableComponent.cs +++ b/Content.Shared/Cuffs/Components/CuffableComponent.cs @@ -38,29 +38,21 @@ public sealed class CuffableComponent : Component /// [DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)] public bool CanStillInteract = true; - - /// - /// Whether or not the entity is currently in the process of being uncuffed. - /// - [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; diff --git a/Content.Shared/Cuffs/Components/HandcuffComponent.cs b/Content.Shared/Cuffs/Components/HandcuffComponent.cs index 7f0f60bfac..27f8a0a112 100644 --- a/Content.Shared/Cuffs/Components/HandcuffComponent.cs +++ b/Content.Shared/Cuffs/Components/HandcuffComponent.cs @@ -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"); - - /// - /// Used to prevent DoAfter getting spammed. - /// - [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; } } diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index f513bb4aa3..c972dac701 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -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(OnUnequipAttempt); SubscribeLocalEvent(OnBeingPulledAttempt); SubscribeLocalEvent>(AddUncuffVerb); - SubscribeLocalEvent>(OnCuffableDoAfter); + SubscribeLocalEvent(OnCuffableDoAfter); SubscribeLocalEvent(OnPull); SubscribeLocalEvent(OnPull); SubscribeLocalEvent(CheckAct); @@ -76,7 +78,7 @@ namespace Content.Shared.Cuffs SubscribeLocalEvent(OnCuffAfterInteract); SubscribeLocalEvent(OnCuffMeleeHit); - SubscribeLocalEvent>(OnAddCuffDoAfter); + SubscribeLocalEvent(OnAddCuffDoAfter); } @@ -221,18 +223,14 @@ namespace Content.Shared.Cuffs args.Verbs.Add(verb); } - private void OnCuffableDoAfter(EntityUid uid, CuffableComponent component, DoAfterEvent 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 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(target, out var hands)) { if (_net.IsServer) @@ -450,6 +447,25 @@ namespace Content.Shared.Cuffs return; } + var cuffTime = handcuffComponent.CuffTime; + + if (HasComp(target)) + cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus); + + if (HasComp(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(target)) - cuffTime = MathF.Max(0.1f, cuffTime - handcuffComponent.StunBonus); - - if (HasComp(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()); } /// @@ -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 index 0000000000..a1c2772459 --- /dev/null +++ b/Content.Shared/Disease/Events/VaccineDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs index 3171bd227b..470dbbc94c 100644 --- a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs +++ b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs @@ -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(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; diff --git a/Content.Shared/DoAfter/ActiveDoAfterComponent.cs b/Content.Shared/DoAfter/ActiveDoAfterComponent.cs index d8d71de953..186b102f68 100644 --- a/Content.Shared/DoAfter/ActiveDoAfterComponent.cs +++ b/Content.Shared/DoAfter/ActiveDoAfterComponent.cs @@ -5,8 +5,7 @@ namespace Content.Shared.DoAfter; /// /// Added to entities that are currently performing any doafters. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent] public sealed class ActiveDoAfterComponent : Component { - } diff --git a/Content.Shared/DoAfter/DoAfter.cs b/Content.Shared/DoAfter/DoAfter.cs index 85d15acba4..0a8e197c19 100644 --- a/Content.Shared/DoAfter/DoAfter.cs +++ b/Content.Shared/DoAfter/DoAfter.cs @@ -1,82 +1,109 @@ -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 AsTask; + [DataField("index", required:true)] + public ushort Index; - [NonSerialized] - [Obsolete("Will be obsolete for EventBus")] - public TaskCompletionSource Tcs; + public DoAfterId Id => new(Args.User, Index); - //TODO: Should be merged into here - public readonly DoAfterEventArgs EventArgs; - - //ID so the client DoAfterSystem can track - public byte ID; - - public bool Cancelled = false; - - //Cache the delay so the timer properly shows - public float Delay; - - //Keep track of the time this DoAfter started - public TimeSpan StartTime; - - //Keep track of the time this DoAfter was cancelled - public TimeSpan CancelledTime; - - //How long has the do after been running? - public TimeSpan Elapsed = TimeSpan.Zero; + [IncludeDataField] + public readonly DoAfterArgs Args = default!; /// - /// Accrued time when cancelled. + /// Time at which this do after was started. /// - public TimeSpan CancelledElapsed = TimeSpan.Zero; + [DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer), required:true)] + public TimeSpan StartTime; - public EntityCoordinates UserGrid; - public EntityCoordinates TargetGrid; + /// + /// The time at which this do after was canceled + /// + [DataField("cancelledTime", customTypeSerializer: typeof(TimeOffsetSerializer), required:true)] + public TimeSpan? CancelledTime; - [NonSerialized] - public Action? Done; + /// + /// If true, this do after has finished, passed the final checks, and has raised its events. + /// + [DataField("completed")] + public bool Completed; -#pragma warning disable RA0004 - public DoAfterStatus Status => AsTask.IsCompletedSuccessfully ? AsTask.Result : DoAfterStatus.Running; -#pragma warning restore RA0004 + /// + /// Whether the do after has been canceled. + /// + public bool Cancelled => CancelledTime != null; - // NeedHand - public readonly string? ActiveHand; - public readonly EntityUid? ActiveItem; + /// + /// Position of the user relative to their parent when the do after was started. + /// + [DataField("userPosition")] + public EntityCoordinates UserPosition; - public DoAfter(DoAfterEventArgs eventArgs, IEntityManager entityManager) + /// + /// Position of the target relative to their parent when the do after was started. + /// + [DataField("targetPosition")] + public EntityCoordinates TargetPosition; + + /// + /// If is true, this is the hand that was selected when the doafter started. + /// + [DataField("activeHand")] + public string? InitialHand; + + /// + /// If is true, this is the entity that was in the active hand when the doafter started. + /// + [DataField("activeItem")] + public EntityUid? InitialItem; + + // cached attempt event for the sake of avoiding unnecessary reflection every time this needs to be raised. + [NonSerialized] public object? AttemptEvent; + + private DoAfter() { - EventArgs = eventArgs; - StartTime = IoCManager.Resolve().CurTime; + } - if (eventArgs.BreakOnUserMove) - UserGrid = entityManager.GetComponent(eventArgs.User).Coordinates; + public DoAfter(ushort index, DoAfterArgs args, TimeSpan startTime) + { + Index = index; - if (eventArgs.Target != null && eventArgs.BreakOnTargetMove) - // Target should never be null if the bool is set. - TargetGrid = entityManager.GetComponent(eventArgs.Target!.Value).Coordinates; - - // For this we need to stay on the same hand slot and need the same item in that hand slot - // (or if there is no item there we need to keep it free). - if (eventArgs.NeedHand && entityManager.TryGetComponent(eventArgs.User, out SharedHandsComponent? handsComponent)) + if (args.Target == null) { - ActiveHand = handsComponent.ActiveHand?.Name; - ActiveItem = handsComponent.ActiveHandEntity; + DebugTools.Assert(!args.BreakOnTargetMove); + args.BreakOnTargetMove = false; } - Tcs = new TaskCompletionSource(); - 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; } } + +/// +/// Simple struct that contains data required to uniquely identify a doAfter. +/// +/// +/// Can be used to track currently active do-afters to prevent simultaneous do-afters. +/// +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 index 0000000000..bf185cd85a --- /dev/null +++ b/Content.Shared/DoAfter/DoAfterArgs.cs @@ -0,0 +1,295 @@ +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization; + +namespace Content.Shared.DoAfter; + +[Serializable, NetSerializable] +[DataDefinition] +public sealed class DoAfterArgs +{ + /// + /// The entity invoking do_after + /// + [DataField("user", required: true)] + public readonly EntityUid User; + + /// + /// How long does the do_after require to complete + /// + [DataField("delay", required: true)] + public readonly TimeSpan Delay; + + /// + /// Applicable target (if relevant) + /// + [DataField("target")] + public readonly EntityUid? Target; + + /// + /// Entity used by the User on the Target. + /// + [DataField("using")] + public readonly EntityUid? Used; + + #region Event options + /// + /// The event that will get raised when the DoAfter has finished. If null, this will simply raise a + /// + [DataField("event", required: true)] + public readonly DoAfterEvent Event = default!; + + /// + /// This option determines how frequently the DoAfterAttempt event will get raised. Defaults to never raising the + /// event. + /// + [DataField("attemptEventFrequency")] + public AttemptFrequency AttemptFrequency; + + /// + /// Entity which will receive the directed event. If null, no directed event will be raised. + /// + [DataField("eventTarget")] + public readonly EntityUid? EventTarget; + + /// + /// Should the DoAfter event broadcast? If this is false, then should be a valid entity. + /// + [DataField("broadcast")] + public bool Broadcast; + #endregion + + #region Break/Cancellation Options + // Break the chains + /// + /// Whether or not this do after requires the user to have hands. + /// + [DataField("needHand")] + public bool NeedHand; + + /// + /// 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 is false. + /// + [DataField("breakOnHandChange")] + public bool BreakOnHandChange = true; + + /// + /// If do_after stops when the user moves + /// + [DataField("breakOnUserMove")] + public bool BreakOnUserMove; + + /// + /// If do_after stops when the target moves (if there is a target) + /// + [DataField("breakOnTargetMove")] + public bool BreakOnTargetMove; + + /// + /// Threshold for user and target movement + /// + [DataField("movementThreshold")] + public float MovementThreshold = 0.1f; + + /// + /// Threshold for distance user from the used OR target entities. + /// + [DataField("distanceThreshold")] + public float? DistanceThreshold; + + /// + /// Whether damage will cancel the DoAfter. See also . + /// + [DataField("breakOnDamage")] + public bool BreakOnDamage; + + /// + /// Threshold for user damage. This damage has to be dealt in a single event, not over time. + /// + [DataField("damageThreshold")] + public FixedPoint2 DamageThreshold = 1; + + /// + /// If true, this DoAfter will be canceled if the user can no longer interact with the target. + /// + [DataField("requireCanInteract")] + public bool RequireCanInteract = true; + #endregion + + #region Duplicates + /// + /// If true, this will prevent duplicate DoAfters from being started See also . + /// + /// + /// Note that this will block even if the duplicate is cancelled because either DoAfter had + /// enabled. + /// + [DataField("blockDuplicate")] + public bool BlockDuplicate = true; + + /// + /// If true, this will cancel any duplicate DoAfters when attempting to add a new DoAfter. See also + /// . + /// + [DataField("cancelDuplicate")] + public bool CancelDuplicate = true; + + /// + /// These flags determine what DoAfter properties are used to determine whether one DoAfter is a duplicate of + /// another. + /// + /// + /// Note that both DoAfters may have their own conditions, and they will be considered duplicated if either set + /// of conditions is satisfied. + /// + [DataField("duplicateCondition")] + public DuplicateConditions DuplicateCondition = DuplicateConditions.All; + #endregion + + /// + /// Additional conditions that need to be met. Return false to cancel. + /// + [NonSerialized] + [Obsolete("Use checkEvent instead")] + public Func? ExtraCheck; + + #region Constructors + + /// + /// Creates a new set of DoAfter arguments. + /// + /// The user that will perform the DoAfter + /// The time it takes for the DoAfter to complete + /// The event that will be raised when the DoAfter has ended (completed or cancelled). + /// The entity at which the event will be directed. If null, the event will not be directed. + /// The entity being targeted by the DoAFter. Not the same as . + /// The entity being used during the DoAfter. E.g., a tool + 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() + { + } + + /// + /// Creates a new set of DoAfter arguments. + /// + /// The user that will perform the DoAfter + /// The time it takes for the DoAfter to complete, in seconds + /// The event that will be raised when the DoAfter has ended (completed or cancelled). + /// The entity at which the event will be directed. If null, the event will not be directed. + /// The entity being targeted by the DoAfter. Not the same as . + /// The entity being used during the DoAfter. E.g., a tool + 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(); + } +} + +/// +/// See . +/// +[Flags] +public enum DuplicateConditions : byte +{ + /// + /// This DoAfter will consider any other DoAfter with the same user to be a duplicate. + /// + None = 0, + + /// + /// Requires that refers to the same entity in order to be considered a duplicate. + /// + /// + /// 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. + /// + SameTool = 1 << 1, + + /// + /// Requires that refers to the same entity in order to be considered a duplicate. + /// + /// + /// 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. + /// + SameTarget = 1 << 2, + + /// + /// Requires that the types match in order to be considered a duplicate. + /// + /// + /// 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. + /// + SameEvent = 1 << 3, + + All = SameTool | SameTarget | SameEvent, +} + +public enum AttemptFrequency : byte +{ + /// + /// Never raise the attempt event. + /// + Never = 0, + + /// + /// Raises the attempt event when the DoAfter is about to start or end. + /// + StartAndEnd = 1, + + /// + /// Raise the attempt event every tick while the DoAfter is running. + /// + EveryTick = 2 +} diff --git a/Content.Shared/DoAfter/DoAfterComponent.cs b/Content.Shared/DoAfter/DoAfterComponent.cs index b8dc23d378..748fd40dfa 100644 --- a/Content.Shared/DoAfter/DoAfterComponent.cs +++ b/Content.Shared/DoAfter/DoAfterComponent.cs @@ -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("nextId")] + public ushort NextId; + [DataField("doAfters")] - public readonly Dictionary DoAfters = new(); + public readonly Dictionary DoAfters = new(); - [DataField("cancelledDoAfters")] - public readonly Dictionary CancelledDoAfters = new(); - - // So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates) - // we'll just send them the index. Doesn't matter if it wraps around. - [DataField("runningIndex")] - public byte RunningIndex; + // Used by obsolete async do afters + public readonly Dictionary> AwaitedDoAfters = new(); } [Serializable, NetSerializable] public sealed class DoAfterComponentState : ComponentState { - public Dictionary DoAfters; + public readonly ushort NextId; + public readonly Dictionary DoAfters; - public DoAfterComponentState(Dictionary doAfters) + public DoAfterComponentState(DoAfterComponent component) { - DoAfters = doAfters; - } -} - -/// -/// Use this event to raise your DoAfter events now. -/// Check for cancelled, and if it is, then null the token there. -/// -/// TODO: Add a networked DoAfterEvent to pass in AdditionalData for the future -[Serializable, NetSerializable] -public sealed class DoAfterEvent : HandledEntityEventArgs -{ - public bool Cancelled; - public byte Id; - public readonly DoAfterEventArgs Args; - - public DoAfterEvent(bool cancelled, DoAfterEventArgs args, byte id) - { - Cancelled = cancelled; - Args = args; - Id = id; - } -} - -/// -/// Use this event to raise your DoAfter events now. -/// Check for cancelled, and if it is, then null the token there. -/// Can't be serialized -/// -/// TODO: Net/Serilization isn't supported so this needs to be networked somehow -public sealed class DoAfterEvent : HandledEntityEventArgs -{ - public T AdditionalData; - public bool Cancelled; - public 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 CancelledDoAfterMessage(EntityUid uid, byte id) - { - 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 index 0000000000..07aa48b6a8 --- /dev/null +++ b/Content.Shared/DoAfter/DoAfterEvent.cs @@ -0,0 +1,81 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.DoAfter; + +/// +/// Base type for events that get raised when a do-after is canceled or finished. +/// +[Serializable, NetSerializable] +[ImplicitDataDefinitionForInheritors] +public abstract class DoAfterEvent : HandledEntityEventArgs +{ + /// + /// The do after that triggered this event. This will be set by the do after system before the event is raised. + /// + [NonSerialized] + public DoAfter DoAfter = default!; + + /// + /// Duplicate the current event. This is used by state handling, and should copy by value unless the reference + /// types are immutable. + /// + 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 +} + +/// +/// Blank / empty event for simple do afters that carry no information. +/// +/// +/// 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(). +/// +[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 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 +{ +} + +/// +/// This event will optionally get raised every tick while a do-after is in progress to check whether the do-after +/// should be canceled. +/// +public sealed class DoAfterAttemptEvent : CancellableEntityEventArgs where TEvent : DoAfterEvent +{ + /// + /// The do after that triggered this event. + /// + public readonly DoAfter DoAfter; + + /// + /// 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. + /// + 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 index 1aaa90edd1..0000000000 --- a/Content.Shared/DoAfter/DoAfterEventArgs.cs +++ /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 -{ - /// - /// The entity invoking do_after - /// - public EntityUid User; - - /// - /// How long does the do_after require to complete - /// - public float Delay; - - /// - /// Applicable target (if relevant) - /// - public EntityUid? Target; - - /// - /// Entity used by the User on the Target. - /// - public EntityUid? Used; - - public bool RaiseOnUser = true; - - public bool RaiseOnTarget = true; - - public bool RaiseOnUsed = true; - - /// - /// Manually cancel the do_after so it no longer runs - /// - [NonSerialized] - public CancellationToken CancelToken; - - // Break the chains - /// - /// Whether we need to keep our active hand as is (i.e. can't change hand or change item). - /// This also covers requiring the hand to be free (if applicable). - /// - public bool NeedHand; - - /// - /// If do_after stops when the user moves - /// - public bool BreakOnUserMove; - - /// - /// If do_after stops when the target moves (if there is a target) - /// - public bool BreakOnTargetMove; - - /// - /// Threshold for user and target movement - /// - public float MovementThreshold; - - public bool BreakOnDamage; - - /// - /// Threshold for user damage - /// - public FixedPoint2? DamageThreshold; - public bool BreakOnStun; - - /// - /// Should the DoAfter event broadcast? - /// - public bool Broadcast; - - /// - /// Threshold for distance user from the used OR target entities. - /// - public float? DistanceThreshold; - - /// - /// Requires a function call once at the end (like InRangeUnobstructed). - /// - /// - /// Anything that needs a pre-check should do it itself so no DoAfterState is ever sent to the client. - /// - [NonSerialized] - //TODO: Replace with eventbus - public Func? PostCheck; - - /// - /// Additional conditions that need to be met. Return false to cancel. - /// - [NonSerialized] - //TODO Replace with eventbus - public Func? ExtraCheck; - - public DoAfterEventArgs( - EntityUid user, - float delay, - CancellationToken cancelToken = default, - EntityUid? target = null, - EntityUid? used = null) - { - User = user; - Delay = delay; - CancelToken = cancelToken; - Target = target; - Used = used; - MovementThreshold = 0.1f; - DamageThreshold = 1.0; - - if (Target == null) - { - DebugTools.Assert(!BreakOnTargetMove); - BreakOnTargetMove = false; - } - } -} diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs new file mode 100644 index 0000000000..a630662765 --- /dev/null +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -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(); + var handsQuery = GetEntityQuery(); + + var enumerator = EntityQueryEnumerator(); + 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 xformQuery, + EntityQuery 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 xformQuery, + EntityQuery 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; + } +} diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs index 15cddd29c5..7d0efb91aa 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -1,405 +1,361 @@ -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 _pending = new(); + /// + /// We'll use an excess time so stuff like finishing effects can show. + /// + private static readonly TimeSpan ExcessTime = TimeSpan.FromSeconds(0.5f); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDamage); + SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnStateChanged); SubscribeLocalEvent(OnDoAfterGetState); + SubscribeLocalEvent(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; + foreach (var doAfter in component.DoAfters.Values) + { + doAfter.StartTime += args.PausedTime; + if (doAfter.CancelledTime != null) + doAfter.CancelledTime = doAfter.CancelledTime.Value + args.PausedTime; + } - return component.DoAfters.ContainsKey(id); - } - - private void Add(EntityUid entity, DoAfterComponent component, DoAfter doAfter) - { - doAfter.ID = component.RunningIndex; - doAfter.Delay = doAfter.EventArgs.Delay; - component.DoAfters.Add(component.RunningIndex, doAfter); - EnsureComp(entity); - component.RunningIndex++; Dirty(component); } - private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args) - { - args.State = new DoAfterComponentState(component.DoAfters); - } - - private void Cancelled(DoAfterComponent component, DoAfter doAfter) - { - if (!component.DoAfters.TryGetValue(doAfter.ID, out var index)) - return; - - component.DoAfters.Remove(doAfter.ID); - - if (component.DoAfters.Count == 0) - RemComp(component.Owner); - - RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index.ID)); - } - - /// - /// Call when the particular DoAfter is finished. - /// Client should be tracking this independently. - /// - private void Finished(DoAfterComponent component, DoAfter doAfter) - { - if (!component.DoAfters.ContainsKey(doAfter.ID)) - return; - - component.DoAfters.Remove(doAfter.ID); - - if (component.DoAfters.Count == 0) - RemComp(component.Owner); - } - private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args) { if (args.NewMobState != MobState.Dead || args.NewMobState != MobState.Critical) return; - foreach (var (_, doAfter) in component.DoAfters) + foreach (var doAfter in component.DoAfters.Values) { - Cancel(uid, doAfter, component); + InternalCancel(doAfter, component); } + Dirty(component); } /// /// Cancels DoAfter if it breaks on damage and it meets the threshold /// - /// The EntityUID of the user - /// - /// private void OnDamage(EntityUid uid, DoAfterComponent component, DamageChangedEvent args) { if (!args.InterruptsDoAfters || !args.DamageIncreased || args.DamageDelta == null) return; + 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()) - { - //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(); - } - } - - while (_pending.TryDequeue(out var doAfter)) - { - if (doAfter.Status == DoAfterStatus.Cancelled) - { - Cancelled(comp, doAfter); - - if (doAfter.Done != null) - doAfter.Done(true); - } - - if (doAfter.Status == DoAfterStatus.Finished) - { - Finished(comp, doAfter); - - if (doAfter.Done != null) - doAfter.Done(false); - } - } - } + if (component.AwaitedDoAfters.Remove(doAfter.Index, out var tcs)) + tcs.SetResult(doAfter.Cancelled ? DoAfterStatus.Cancelled : DoAfterStatus.Finished); } + private void OnDoAfterGetState(EntityUid uid, DoAfterComponent comp, ref ComponentGetState args) + { + args.State = new DoAfterComponentState(comp); + } + + private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent comp, ref ComponentHandleState args) + { + if (args.Current is not DoAfterComponentState state) + return; + + // 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. + + 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(uid); + else + EnsureComp(uid); + } + + + #region Creation /// /// Tasks that are delayed until the specified time has passed /// These can be potentially cancelled by the user moving or when other things happen. /// - /// + // TODO remove this, as well as AwaitedDoAfterEvent and DoAfterComponent.AwaitedDoAfters + [Obsolete("Use the synchronous version instead.")] + public async Task WaitDoAfter(DoAfterArgs doAfter, DoAfterComponent? component = null) + { + if (!Resolve(doAfter.User, ref component)) + return DoAfterStatus.Cancelled; + + if (!TryStartDoAfter(doAfter, out var id, component)) + return DoAfterStatus.Cancelled; + + var tcs = new TaskCompletionSource(); + component.AwaitedDoAfters.Add(id.Value.Index, tcs); + return await tcs.Task; + } + + /// + /// 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. + /// + /// The DoAfter arguments + /// The user's DoAfter component /// - [Obsolete("Use the synchronous version instead, DoAfter")] - public async Task WaitDoAfter(DoAfterEventArgs eventArgs) - { - var doAfter = CreateDoAfter(eventArgs); - - await doAfter.AsTask; - - return doAfter.Status; - } + public bool TryStartDoAfter(DoAfterArgs args, DoAfterComponent? component = null) + => TryStartDoAfter(args, out _, component); /// - /// 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 false, an interaction may have + /// occured, as starting a duplicate DoAfter may cancel currently running DoAfters. /// - /// The DoAfterEventArgs - /// The extra data sent over - public DoAfter DoAfter(DoAfterEventArgs eventArgs, T data) + /// The DoAfter arguments + /// The Id of the newly started DoAfter + /// The user's DoAfter component + /// + public bool TryStartDoAfter(DoAfterArgs args, [NotNullWhen(true)] out DoAfterId? id, DoAfterComponent? comp = null) { - var doAfter = CreateDoAfter(eventArgs); - doAfter.Done = cancelled => { Send(data, cancelled, eventArgs, doAfter.ID); }; - return doAfter; - } + DebugTools.Assert(args.Broadcast || Exists(args.EventTarget) || args.Event.GetType() == typeof(AwaitedDoAfterEvent)); + DebugTools.Assert(args.Event.GetType().HasCustomAttribute() + || 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()}"); - /// - /// Creates a DoAfter without waiting for it to finish. You can use events with this. - /// These can be potentially cancelled by the user moving or when other things happen. - /// Use this if you don't have any extra data to send with the DoAfter - /// - /// The DoAfterEventArgs - public DoAfter DoAfter(DoAfterEventArgs eventArgs) - { - var doAfter = CreateDoAfter(eventArgs); - doAfter.Done = cancelled => { Send(cancelled, eventArgs, doAfter.ID); }; - return doAfter; - } - - private DoAfter CreateDoAfter(DoAfterEventArgs eventArgs) - { - // Setup - var doAfter = new DoAfter(eventArgs, EntityManager); - // Caller's gonna be responsible for this I guess - var doAfterComponent = Comp(eventArgs.User); - doAfter.ID = doAfterComponent.RunningIndex; - doAfter.StartTime = GameTiming.CurTime; - Add(eventArgs.User, doAfterComponent, doAfter); - return doAfter; - } - - private void Run(EntityUid entity, DoAfterComponent comp, DoAfter doAfter) - { - switch (doAfter.Status) + if (!Resolve(args.User, ref comp)) { - case DoAfterStatus.Running: - break; - case DoAfterStatus.Cancelled: - case DoAfterStatus.Finished: - return; - default: - throw new ArgumentOutOfRangeException(); - } - - doAfter.Elapsed = GameTiming.CurTime - doAfter.StartTime; - - if (IsFinished(doAfter)) - { - if (!TryPostCheck(doAfter)) - { - Cancel(entity, doAfter, comp); - } - else - { - doAfter.Tcs.SetResult(DoAfterStatus.Finished); - } - - return; - } - - if (IsCancelled(doAfter)) - { - Cancel(entity, doAfter, comp); - } - } - - private bool TryPostCheck(DoAfter doAfter) - { - return doAfter.EventArgs.PostCheck?.Invoke() != false; - } - - private bool IsFinished(DoAfter doAfter) - { - var delay = TimeSpan.FromSeconds(doAfter.EventArgs.Delay); - - if (doAfter.Elapsed <= delay) + Logger.Error($"Attempting to start a doAfter with invalid user: {ToPrettyString(args.User)}."); + id = null; return false; + } + + // Duplicate blocking & cancellation. + if (!ProcessDuplicates(args, comp)) + { + id = null; + return false; + } + + id = new DoAfterId(args.User, comp.NextId++); + var doAfter = new DoAfter(id.Value.Index, args, GameTiming.CurTime); + + if (args.BreakOnUserMove) + doAfter.UserPosition = Transform(args.User).Coordinates; + + if (args.Target != null && args.BreakOnTargetMove) + // Target should never be null if the bool is set. + doAfter.TargetPosition = Transform(args.Target.Value).Coordinates; + + // For this we need to stay on the same hand slot and need the same item in that hand slot + // (or if there is no item there we need to keep it free). + if (args.NeedHand && args.BreakOnHandChange) + { + if (!TryComp(args.User, out SharedHandsComponent? handsComponent)) + return false; + + doAfter.InitialHand = handsComponent.ActiveHand?.Name; + doAfter.InitialItem = handsComponent.ActiveHandEntity; + } + + // Inital checks + if (ShouldCancel(doAfter, GetEntityQuery(), GetEntityQuery())) + return false; + + 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(args.User); + Dirty(comp); + args.Event.DoAfter = doAfter; + return true; + } + + /// + /// Cancel any applicable duplicate DoAfters and return whether or not the new DoAfter should be created. + /// + private bool ProcessDuplicates(DoAfterArgs args, DoAfterComponent component) + { + var blocked = false; + foreach (var existing in component.DoAfters.Values) + { + if (existing.Cancelled || existing.Completed) + continue; + + if (!IsDuplicate(existing.Args, args)) + continue; + + blocked = blocked | args.BlockDuplicate | existing.Args.BlockDuplicate; + + if (args.CancelDuplicate || existing.Args.CancelDuplicate) + Cancel(args.User, existing.Index, component); + } + + return !blocked; + } + + private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs) + { + if (IsDuplicate(args, otherArgs, args.DuplicateCondition)) + return true; + + if (args.DuplicateCondition == otherArgs.DuplicateCondition) + return false; + + return IsDuplicate(args, otherArgs, otherArgs.DuplicateCondition); + } + + private bool IsDuplicate(DoAfterArgs args, DoAfterArgs otherArgs, DuplicateConditions conditions ) + { + if ((conditions & DuplicateConditions.SameTarget) != 0 + && args.Target != otherArgs.Target) + { + return false; + } + + if ((conditions & DuplicateConditions.SameTool) != 0 + && args.Used != otherArgs.Used) + { + return false; + } + + if ((conditions & DuplicateConditions.SameEvent) != 0 + && args.Event.GetType() != otherArgs.Event.GetType()) + { + return false; + } return true; } - private bool IsCancelled(DoAfter doAfter) + #endregion + + #region Cancellation + /// + /// Cancels an active DoAfter. + /// + public void Cancel(DoAfterId? id, DoAfterComponent? comp = null) { - var eventArgs = doAfter.EventArgs; - var xForm = GetEntityQuery(); - - if (!Exists(eventArgs.User) || eventArgs.Target is { } target && !Exists(target)) - return true; - - if (eventArgs.CancelToken.IsCancellationRequested) - return true; - - //TODO: Handle Inertia in space - if (eventArgs.BreakOnUserMove && !xForm.GetComponent(eventArgs.User).Coordinates - .InRange(EntityManager, doAfter.UserGrid, eventArgs.MovementThreshold)) - return true; - - if (eventArgs.Target != null && eventArgs.BreakOnTargetMove && !xForm.GetComponent(eventArgs.Target!.Value) - .Coordinates.InRange(EntityManager, doAfter.TargetGrid, eventArgs.MovementThreshold)) - return true; - - if (eventArgs.ExtraCheck != null && !eventArgs.ExtraCheck.Invoke()) - return true; - - if (eventArgs.BreakOnStun && HasComp(eventArgs.User)) - return true; - - if (eventArgs.NeedHand) - { - if (!TryComp(eventArgs.User, out var handsComp)) - { - //TODO: Figure out active hand and item values - - // If we had a hand but no longer have it that's still a paddlin' - if (doAfter.ActiveHand != null) - return true; - } - else - { - var currentActiveHand = handsComp.ActiveHand?.Name; - if (doAfter.ActiveHand != currentActiveHand) - return true; - - var currentItem = handsComp.ActiveHandEntity; - if (doAfter.ActiveItem != currentItem) - return true; - } - } - - if (eventArgs.DistanceThreshold != null) - { - var userXform = xForm.GetComponent(eventArgs.User); - - if (eventArgs.Target != null && !eventArgs.User.Equals(eventArgs.Target)) - { - //recalculate Target location in case Target has also moved - var targetCoords = xForm.GetComponent(eventArgs.Target.Value).Coordinates; - if (!userXform.Coordinates.InRange(EntityManager, targetCoords, eventArgs.DistanceThreshold.Value)) - return true; - } - - if (eventArgs.Used != null) - { - var usedCoords = xForm.GetComponent(eventArgs.Used.Value).Coordinates; - if (!userXform.Coordinates.InRange(EntityManager, usedCoords, eventArgs.DistanceThreshold.Value)) - return true; - } - } - - return false; + if (id != null) + Cancel(id.Value.Uid, id.Value.Index, comp); } - public void Cancel(EntityUid entity, DoAfter doAfter, DoAfterComponent? comp = null) + /// + /// Cancels an active DoAfter. + /// + 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; + } + + InternalCancel(doAfter, comp); + Dirty(comp); + } + + private void InternalCancel(DoAfter doAfter, DoAfterComponent component) + { + if (doAfter.Cancelled || doAfter.Completed) return; - if (!comp.DoAfters.ContainsKey(doAfter.ID)) - return; - - doAfter.Cancelled = true; + // Caller is responsible for dirtying the component. doAfter.CancelledTime = GameTiming.CurTime; + RaiseDoAfterEvents(doAfter, component); + } + #endregion - var doAfterMessage = comp.DoAfters[doAfter.ID]; - comp.CancelledDoAfters.Add(doAfter.ID, doAfterMessage); - - if (doAfter.Status == DoAfterStatus.Running) - { - doAfter.Tcs.SetResult(DoAfterStatus.Cancelled); - } + #region Query + /// + /// Returns the current status of a DoAfter + /// + public DoAfterStatus GetStatus(DoAfterId? id, DoAfterComponent? comp = null) + { + if (id != null) + return GetStatus(id.Value.Uid, id.Value.Index, comp); + else + return DoAfterStatus.Invalid; } /// - /// Send the DoAfter event, used where you don't need any extra data to send. + /// Returns the current status of a DoAfter /// - /// - /// - private void Send(bool cancelled, DoAfterEventArgs args, byte Id) + public DoAfterStatus GetStatus(EntityUid entity, ushort id, DoAfterComponent? comp = null) { - var ev = new DoAfterEvent(cancelled, args, Id); + if (!Resolve(entity, ref comp, false)) + return DoAfterStatus.Invalid; - RaiseDoAfterEvent(ev, args); - } - - /// - /// Send the DoAfter event, used where you need extra data to send - /// - /// - /// - /// - /// - private void Send(T data, bool cancelled, DoAfterEventArgs args, byte id) - { - var ev = new DoAfterEvent(data, cancelled, args, id); - - RaiseDoAfterEvent(ev, args); - } - - private void RaiseDoAfterEvent(TEvent ev, DoAfterEventArgs args) where TEvent : notnull - { - if (args.RaiseOnUser && Exists(args.User)) - RaiseLocalEvent(args.User, ev, args.Broadcast); - - 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 (args.RaiseOnUsed && args.Used is { } used && Exists(used)) - { - DebugTools.Assert(!args.RaiseOnUser || args.Used != args.User); - RaiseLocalEvent(used, ev, args.Broadcast); - } + if (!comp.DoAfters.TryGetValue(id, out var doAfter)) + return DoAfterStatus.Invalid; + + if (doAfter.Cancelled) + return DoAfterStatus.Cancelled; + + if (GameTiming.CurTime - doAfter.StartTime < doAfter.Args.Delay) + return DoAfterStatus.Running; + + // 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 } diff --git a/Content.Shared/Doors/Components/DoorComponent.cs b/Content.Shared/Doors/Components/DoorComponent.cs index 105a97f045..69fab8ee9e 100644 --- a/Content.Shared/Doors/Components/DoorComponent.cs +++ b/Content.Shared/Doors/Components/DoorComponent.cs @@ -75,8 +75,6 @@ public sealed class DoorComponent : Component, ISerializationHooks public bool Partial; #endregion - public bool BeingPried; - #region Sounds /// /// Sound to play when the door opens. diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index bb590a32d9..6bbf943674 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -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 index 0000000000..2f5f0d5fff --- /dev/null +++ b/Content.Shared/Dragon/DragonDevourDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Ensnaring/SharedEnsnareableSystem.cs b/Content.Shared/Ensnaring/SharedEnsnareableSystem.cs index f7c4051814..86f73f8ca9 100644 --- a/Content.Shared/Ensnaring/SharedEnsnareableSystem.cs +++ b/Content.Shared/Ensnaring/SharedEnsnareableSystem.cs @@ -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 index 0000000000..0060cf7f57 --- /dev/null +++ b/Content.Shared/Exchanger/ExchangerDoAfterEvent.cs @@ -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 index 0000000000..5cff7ea757 --- /dev/null +++ b/Content.Shared/Fluids/Events.cs @@ -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 index 0000000000..7db2965c0e --- /dev/null +++ b/Content.Shared/Forensics/Events.cs @@ -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 index 0000000000..4944ae3ee1 --- /dev/null +++ b/Content.Shared/Gatherable/GatherableDoAfterEvent.cs @@ -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 index 0000000000..98de64764d --- /dev/null +++ b/Content.Shared/Guardian/GuardianCreatorDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Implants/Components/ImplanterComponent.cs b/Content.Shared/Implants/Components/ImplanterComponent.cs index dc34639b8c..f98ac2a732 100644 --- a/Content.Shared/Implants/Components/ImplanterComponent.cs +++ b/Content.Shared/Implants/Components/ImplanterComponent.cs @@ -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] diff --git a/Content.Shared/Implants/SharedImplanterSystem.cs b/Content.Shared/Implants/SharedImplanterSystem.cs index 995b9f829d..30817feda1 100644 --- a/Content.Shared/Implants/SharedImplanterSystem.cs +++ b/Content.Shared/Implants/SharedImplanterSystem.cs @@ -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(OnImplanterInit); SubscribeLocalEvent(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 +{ +} diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index c3fbb299ab..b9ae32cf5d 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -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 index 0000000000..98709a0342 --- /dev/null +++ b/Content.Shared/Internals/InternalsDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Kitchen/SharedKitchenSpikeSystem.cs b/Content.Shared/Kitchen/SharedKitchenSpikeSystem.cs index fa8d7a9e85..2a912cbba7 100644 --- a/Content.Shared/Kitchen/SharedKitchenSpikeSystem.cs +++ b/Content.Shared/Kitchen/SharedKitchenSpikeSystem.cs @@ -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 index 0000000000..1189fc8212 --- /dev/null +++ b/Content.Shared/Kitchen/SharpDoAfterEvent.cs @@ -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 index 0000000000..ac1a7045e9 --- /dev/null +++ b/Content.Shared/Light/PoweredLightDoAfterEvent.cs @@ -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 index 0000000000..7c05ecb8a9 --- /dev/null +++ b/Content.Shared/Magic/SpellbookDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs index 70e8405240..a41ec7ab61 100644 --- a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs +++ b/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs @@ -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(component.MechCycleAction)), mech); - _actions.AddAction(pilot, new InstantAction(_prototype.Index(component.MechUiAction)), mech); - _actions.AddAction(pilot, new InstantAction(_prototype.Index(component.MechEjectAction)), mech); + _actions.AddAction(pilot, + new InstantAction(_prototype.Index(component.MechCycleAction)), mech); + _actions.AddAction(pilot, new InstantAction(_prototype.Index(component.MechUiAction)), + mech); + _actions.AddAction(pilot, + new InstantAction(_prototype.Index(component.MechEjectAction)), mech); } private void RemoveUser(EntityUid mech, EntityUid pilot) @@ -252,7 +259,8 @@ public abstract class SharedMechSystem : EntitySystem /// /// /// - 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 /// /// /// Whether or not the removal can be cancelled - 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 /// public virtual void UpdateUserInterface(EntityUid uid, SharedMechComponent? component = null) { - } /// @@ -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); } } + +/// +/// Event raised when the battery is successfully removed from the mech, +/// on both success and failure +/// +[Serializable, NetSerializable] +public sealed class RemoveBatteryEvent : SimpleDoAfterEvent +{ +} + +/// +/// Event raised when a person removes someone from a mech, +/// on both success and failure +/// +[Serializable, NetSerializable] +public sealed class MechExitEvent : SimpleDoAfterEvent +{ +} + +/// +/// Event raised when a person enters a mech, on both success and failure +/// +[Serializable, NetSerializable] +public sealed class MechEntryEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs b/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs index 36509054e6..c35e8bd31c 100644 --- a/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs +++ b/Content.Shared/Mech/Equipment/Components/MechEquipmentComponent.cs @@ -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 /// /// How long does it take to install this piece of equipment /// - [DataField("installDuration")] - public float InstallDuration = 5; + [DataField("installDuration")] public float InstallDuration = 5; /// /// The mech that the equipment is inside of. /// - [ViewVariables] - public EntityUid? EquipmentOwner; + [ViewVariables] public EntityUid? EquipmentOwner; } /// @@ -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 +{ +} + diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs index 2bd456d774..752d4ce72e 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodComponent.cs @@ -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 { diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs index 204f37ae92..4b82b8388a 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs @@ -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 index 0000000000..11e064e66d --- /dev/null +++ b/Content.Shared/Medical/HealingDoAfterEvent.cs @@ -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 index 0000000000..b94ed1e844 --- /dev/null +++ b/Content.Shared/Medical/ReclaimerDoAfterEvent.cs @@ -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 index 0000000000..a6d2a6605d --- /dev/null +++ b/Content.Shared/Medical/StethoscopeDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/MedicalScanner/SharedHealthAnalyzerComponent.cs b/Content.Shared/MedicalScanner/SharedHealthAnalyzerComponent.cs index 3366fbfd3d..712911125c 100644 --- a/Content.Shared/MedicalScanner/SharedHealthAnalyzerComponent.cs +++ b/Content.Shared/MedicalScanner/SharedHealthAnalyzerComponent.cs @@ -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 + { + } } diff --git a/Content.Shared/Nuke/SharedNuke.cs b/Content.Shared/Nuke/SharedNuke.cs index a134a084d7..9ed6faafb0 100644 --- a/Content.Shared/Nuke/SharedNuke.cs +++ b/Content.Shared/Nuke/SharedNuke.cs @@ -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 index 0000000000..3e3bd93470 --- /dev/null +++ b/Content.Shared/Nutrition/Events.cs @@ -0,0 +1,29 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Nutrition; + +/// +/// Do after even for food and drink. +/// +[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 index 0000000000..3178b24dc9 --- /dev/null +++ b/Content.Shared/Power/ApcToolFinishedEvent.cs @@ -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 diff --git a/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs b/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs index c5cf05ea81..ca5d1f647a 100644 --- a/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs +++ b/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs @@ -42,12 +42,6 @@ public sealed class EncryptionKeyHolderComponent : Component public Container KeyContainer = default!; public const string KeyContainerName = "key_slots"; - /// - /// Blocks multiple attempts to remove the key - /// - [DataField("removing")] - public bool Removing; - /// /// Combined set of radio channels provided by all contained keys. /// diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index cb10ec07f2..a69438c354 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -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(OnContainerModified); SubscribeLocalEvent(OnContainerModified); SubscribeLocalEvent(OnKeyRemoval); - SubscribeLocalEvent(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(uid, out var _) || args.Handled || component.Removing) + if ( args.Handled || !TryComp(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(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 + [Serializable, NetSerializable] + public sealed class EncryptionRemovalFinishedEvent : SimpleDoAfterEvent { - public EntityUid User; - - public EncryptionRemovalFinishedEvent(EntityUid user) - { - User = user; - } - } - - public sealed class EncryptionRemovalCancelledEvent : EntityEventArgs - { - } } diff --git a/Content.Shared/Repairable/SharedRepairableSystem.cs b/Content.Shared/Repairable/SharedRepairableSystem.cs new file mode 100644 index 0000000000..ec59a60a8f --- /dev/null +++ b/Content.Shared/Repairable/SharedRepairableSystem.cs @@ -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 index 0000000000..27e5346ead --- /dev/null +++ b/Content.Shared/Resist/EscapeInventoryEvent.cs @@ -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 index 0000000000..4f31dc0330 --- /dev/null +++ b/Content.Shared/Resist/ResistLockerDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Revenant/SharedRevenant.cs b/Content.Shared/Revenant/SharedRevenant.cs index 6de9a5d7a2..15289ad1d7 100644 --- a/Content.Shared/Revenant/SharedRevenant.cs +++ b/Content.Shared/Revenant/SharedRevenant.cs @@ -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 index 0000000000..8a24935cf6 --- /dev/null +++ b/Content.Shared/Spillable/SpillDoAfterEvent.cs @@ -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 index 0000000000..e460efa40e --- /dev/null +++ b/Content.Shared/Sticky/StickyDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/Storage/Components/DumpableComponent.cs b/Content.Shared/Storage/Components/DumpableComponent.cs index 15e9726350..ac7003d675 100644 --- a/Content.Shared/Storage/Components/DumpableComponent.cs +++ b/Content.Shared/Storage/Components/DumpableComponent.cs @@ -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 + { + } + /// /// 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 /// /// How long each item adds to the doafter. /// - [DataField("delayPerItem")] - public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2); + [DataField("delayPerItem")] public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2); /// /// The multiplier modifier /// - [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 index 0000000000..8a488ead77 --- /dev/null +++ b/Content.Shared/Storage/EntitySystems/BluespaceLockerDoAfterEvent.cs @@ -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 index 0000000000..4c241ff4ad --- /dev/null +++ b/Content.Shared/Storage/Events.cs @@ -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 Entities = default!; + + private AreaPickupDoAfterEvent() + { + } + + public AreaPickupDoAfterEvent(List 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 index 0000000000..05b1d43da6 --- /dev/null +++ b/Content.Shared/Swab/SwabEvents.cs @@ -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 +{ +} diff --git a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs index d30a913adf..9b0290ba2c 100644 --- a/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs +++ b/Content.Shared/Teleportation/Components/HandTeleporterComponent.cs @@ -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))] + [DataField("firstPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string FirstPortalPrototype = "PortalRed"; - [DataField("secondPortalPrototype", customTypeSerializer:typeof(PrototypeIdSerializer))] + [DataField("secondPortalPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] 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 /// /// Delay for creating the portals in seconds. /// - [DataField("portalCreationDelay")] - public float PortalCreationDelay = 2.5f; - + [DataField("portalCreationDelay")] public float PortalCreationDelay = 2.5f; +} + +[Serializable, NetSerializable] +public sealed class TeleporterDoAfterEvent : SimpleDoAfterEvent +{ } diff --git a/Content.Shared/Toilet/ToiletComponent.cs b/Content.Shared/Toilet/ToiletComponent.cs index 86067fb56c..b0486a54f0 100644 --- a/Content.Shared/Toilet/ToiletComponent.cs +++ b/Content.Shared/Toilet/ToiletComponent.cs @@ -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 + { } } diff --git a/Content.Shared/Tools/Components/ToolComponent.cs b/Content.Shared/Tools/Components/ToolComponent.cs index f631f66e20..c84eaddc2a 100644 --- a/Content.Shared/Tools/Components/ToolComponent.cs +++ b/Content.Shared/Tools/Components/ToolComponent.cs @@ -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; } } - - /// - /// Attempt event called *after* any do afters to see if the tool usage should succeed or not. - /// You can use this event to consume any fuel needed. - /// - public sealed class ToolUseFinishAttemptEvent : CancellableEntityEventArgs - { - public float Fuel { get; } - public EntityUid User { get; } - - public ToolUseFinishAttemptEvent(float fuel, EntityUid user) - { - 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; - } - } } diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs b/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs index 030c33f9cf..b198f6d779 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.MultipleTool.cs @@ -1,102 +1,18 @@ 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(OnMultipleToolStartup); SubscribeLocalEvent(OnMultipleToolActivated); SubscribeLocalEvent(OnMultipleToolGetState); SubscribeLocalEvent(OnMultipleToolHandleState); - - SubscribeLocalEvent>(OnDoAfter); - - SubscribeLocalEvent(OnDoAfterComplete); - SubscribeLocalEvent(OnDoAfterCancelled); - } - - private void OnDoAfter(EntityUid uid, ToolComponent component, DoAfterEvent 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 toolQualitiesNeeded, ToolEventData toolEventData, float fuel = 0f, ToolComponent? toolComponent = null, Func? 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? 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); } - - /// - /// Whether a tool entity has the specified quality or not. - /// - public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null) - { - return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality); - } - - /// - /// Whether a tool entity has all specified qualities or not. - /// - public bool HasAllQualities(EntityUid uid, IEnumerable qualities, ToolComponent? tool = null) - { - return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities); - } - - - private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable toolQualitiesNeeded, ToolComponent? toolComponent = null) - { - if (!Resolve(tool, ref toolComponent)) - return false; - - if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded)) - return false; - - var beforeAttempt = new ToolUseAttemptEvent(fuel, user); - RaiseLocalEvent(tool, beforeAttempt, false); - - return !beforeAttempt.Cancelled; - } - - private bool ToolFinishUse(EntityUid tool, EntityUid user, float fuel, ToolComponent? toolComponent = null) - { - if (!Resolve(tool, ref toolComponent)) - return false; - - var afterAttempt = new ToolUseFinishAttemptEvent(fuel, user); - RaiseLocalEvent(tool, afterAttempt, false); - - if (afterAttempt.Cancelled) - return false; - - if (toolComponent.UseSound != null) - PlayToolSound(tool, toolComponent); - - return true; - } - - public void PlayToolSound(EntityUid uid, ToolComponent? tool = null) - { - if (!Resolve(uid, ref tool)) - return; - - if (tool.UseSound is not {} sound) - return; - - // Pass tool.Owner to Filter.Pvs to avoid a TryGetEntity call. - SoundSystem.Play(sound.GetSound(), Filter.Pvs(tool.Owner), - uid, AudioHelpers.WithVariation(0.175f).WithVolume(-5f)); - } - - private void OnDoAfterComplete(ToolDoAfterComplete ev) - { - // Actually finish the tool use! Depending on whether that succeeds or not, either event will be broadcast. - if(ToolFinishUse(ev.Uid, ev.UserUid, ev.Fuel)) - { - if (ev.EventTarget != null) - RaiseLocalEvent(ev.EventTarget.Value, ev.CompletedEvent, false); - else - RaiseLocalEvent(ev.CompletedEvent); - } - else if(ev.CancelledEvent != null) - { - if (ev.EventTarget != null) - RaiseLocalEvent(ev.EventTarget.Value, ev.CancelledEvent, false); - else - RaiseLocalEvent(ev.CancelledEvent); - } - } - - private void OnDoAfterCancelled(ToolDoAfterCancelled ev) - { - if (ev.EventTarget != null) - RaiseLocalEvent(ev.EventTarget.Value, ev.Event, false); - else - RaiseLocalEvent(ev.Event); - } - - private sealed class ToolDoAfterComplete : EntityEventArgs - { - public readonly object CompletedEvent; - public readonly object? CancelledEvent; - public readonly EntityUid Uid; - public readonly EntityUid UserUid; - public readonly float Fuel; - public readonly EntityUid? EventTarget; - - public ToolDoAfterComplete(object completedEvent, object? cancelledEvent, EntityUid uid, EntityUid userUid, float fuel, EntityUid? eventTarget = null) - { - CompletedEvent = completedEvent; - Uid = uid; - UserUid = userUid; - Fuel = fuel; - CancelledEvent = cancelledEvent; - EventTarget = eventTarget; - } - } - - private sealed class ToolDoAfterCancelled : EntityEventArgs - { - public readonly object Event; - public readonly EntityUid? EventTarget; - - public ToolDoAfterCancelled(object @event, EntityUid? eventTarget = null) - { - Event = @event; - EventTarget = eventTarget; - } - } } diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs new file mode 100644 index 0000000000..28988314be --- /dev/null +++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs @@ -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(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)); + } + + /// + /// 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. + /// + /// The tool to use + /// The entity using the tool + /// 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 + /// The base tool use delay (seconds). This will be modified by the tool's quality + /// The qualities needed for this tool to work. + /// The event that will be raised when the tool has finished (including cancellation). Event + /// will be directed at the tool target. + /// Amount of fuel that should be taken from the tool. + /// The tool component. + /// Returns true if any interaction takes place. + public bool UseTool( + EntityUid tool, + EntityUid user, + EntityUid? target, + float doAfterDelay, + IEnumerable toolQualitiesNeeded, + DoAfterEvent doAfterEv, + float fuel = 0f, + ToolComponent? toolComponent = null) + { + return UseTool(tool, + user, + target, + TimeSpan.FromSeconds(doAfterDelay), + toolQualitiesNeeded, + doAfterEv, + out _, + fuel, + toolComponent); + } + + /// + /// 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. + /// + /// The tool to use + /// The entity using the tool + /// 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 + /// The base tool use delay. This will be modified by the tool's quality + /// The qualities needed for this tool to work. + /// The event that will be raised when the tool has finished (including cancellation). Event + /// will be directed at the tool target. + /// 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 + /// Amount of fuel that should be taken from the tool. + /// The tool component. + /// Returns true if any interaction takes place. + public bool UseTool( + EntityUid tool, + EntityUid user, + EntityUid? target, + TimeSpan delay, + IEnumerable 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; + } + + /// + /// 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. + /// + /// The tool to use + /// The entity using the tool + /// 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 + /// The base tool use delay (seconds). This will be modified by the tool's quality + /// The quality needed for this tool to work. + /// The event that will be raised when the tool has finished (including cancellation). Event + /// will be directed at the tool target. + /// 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 + /// Amount of fuel that should be taken from the tool. + /// The tool component. + /// Returns true if any interaction takes place. + 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); + } + + /// + /// Whether a tool entity has the specified quality or not. + /// + public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null) + { + return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality); + } + + /// + /// Whether a tool entity has all specified qualities or not. + /// + public bool HasAllQualities(EntityUid uid, IEnumerable qualities, ToolComponent? tool = null) + { + return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities); + } + + private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, float fuel, IEnumerable 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; + + /// + /// Entity that the wrapped do after event will get directed at. If null, event will be broadcast. + /// + [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(), "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 index 0000000000..20dfae0584 --- /dev/null +++ b/Content.Shared/Tools/Systems/WeldFinishedEvent.cs @@ -0,0 +1,13 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Tools.Systems; + +/// +/// Raised after welding do_after has finished. It doesn't guarantee success, +/// use to get updated status. +/// +[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 index 0000000000..03170b93f7 --- /dev/null +++ b/Content.Shared/Udder/MilkingDoAfterEvent.cs @@ -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 diff --git a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs index dfd129a9da..78c774c5b3 100644 --- a/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs +++ b/Content.Shared/VendingMachines/SharedVendingMachineSystem.cs @@ -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 index 0000000000..fdbe02884d --- /dev/null +++ b/Content.Shared/Wieldable/WieldableDoAfterEvent.cs @@ -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 index 0000000000..d28203e009 --- /dev/null +++ b/Content.Shared/Wires/Events.cs @@ -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; +} diff --git a/Content.Shared/Wires/SharedWiresComponent.cs b/Content.Shared/Wires/SharedWiresComponent.cs index 0614d1344a..dd749affdb 100644 --- a/Content.Shared/Wires/SharedWiresComponent.cs +++ b/Content.Shared/Wires/SharedWiresComponent.cs @@ -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 { diff --git a/Content.Shared/Wires/WiresPanelComponent.cs b/Content.Shared/Wires/WiresPanelComponent.cs index a1519b6325..878ef13044 100644 --- a/Content.Shared/Wires/WiresPanelComponent.cs +++ b/Content.Shared/Wires/WiresPanelComponent.cs @@ -20,12 +20,6 @@ public sealed class WiresPanelComponent : Component [ViewVariables] public bool Visible = true; - /// - /// Marks if maintenance panel being open/closed by someone with a screwdriver. - /// Prevents do after spam. - /// - public bool IsScrewing; - [DataField("screwdriverOpenSound")] public SoundSpecifier ScrewdriverOpenSound = new SoundPathSpecifier("/Audio/Machines/screwdriveropen.ogg");