diff --git a/Content.Client/CharacterInfo/ProgressTextureRect.cs b/Content.Client/CharacterInfo/ProgressTextureRect.cs index 74113fe2aa..4f178f3856 100644 --- a/Content.Client/CharacterInfo/ProgressTextureRect.cs +++ b/Content.Client/CharacterInfo/ProgressTextureRect.cs @@ -1,8 +1,6 @@ -using System; -using Content.Client.DoAfter.UI; +using Content.Client.DoAfter; using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Maths; namespace Content.Client.CharacterInfo { @@ -14,7 +12,7 @@ namespace Content.Client.CharacterInfo { var dims = Texture != null ? GetDrawDimensions(Texture) : UIBox2.FromDimensions(Vector2.Zero, PixelSize); dims.Top = Math.Max(dims.Bottom - dims.Bottom * Progress,0); - handle.DrawRect(dims, DoAfterHelpers.GetProgressColor(Progress)); + handle.DrawRect(dims, DoAfterOverlay.GetProgressColor(Progress)); base.Draw(handle); } diff --git a/Content.Client/DoAfter/DoAfterComponent.cs b/Content.Client/DoAfter/DoAfterComponent.cs index 6123b0729a..8857b373c7 100644 --- a/Content.Client/DoAfter/DoAfterComponent.cs +++ b/Content.Client/DoAfter/DoAfterComponent.cs @@ -1,4 +1,3 @@ -using Content.Client.DoAfter.UI; using Content.Shared.DoAfter; namespace Content.Client.DoAfter @@ -8,8 +7,6 @@ namespace Content.Client.DoAfter { public readonly Dictionary DoAfters = new(); - public readonly List<(TimeSpan CancelTime, ClientDoAfter Message)> CancelledDoAfters = new(); - - public DoAfterGui? Gui { get; set; } + public readonly Dictionary CancelledDoAfters = new(); } } diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs new file mode 100644 index 0000000000..b16835c46f --- /dev/null +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -0,0 +1,141 @@ +using Content.Client.Resources; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client.DoAfter; + +public sealed class DoAfterOverlay : Overlay +{ + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + private readonly SharedTransformSystem _transform; + + private Texture _barTexture; + private ShaderInstance _shader; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + + public DoAfterOverlay() + { + IoCManager.InjectDependencies(this); + _transform = _entManager.EntitySysManager.GetEntitySystem(); + _barTexture = IoCManager.Resolve() + .GetTexture("/Textures/Interface/Misc/progress_bar.rsi/icon.png"); + + _shader = IoCManager.Resolve().Index("unshaded").Instance(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var handle = args.WorldHandle; + var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero; + var spriteQuery = _entManager.GetEntityQuery(); + var xformQuery = _entManager.GetEntityQuery(); + + var scale = _configManager.GetCVar(CVars.DisplayUIScale); + var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); + 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 worldPosition = _transform.GetWorldPosition(xform); + + if (!args.WorldAABB.Contains(worldPosition)) + continue; + + var index = 0; + var worldMatrix = Matrix3.CreateTranslation(worldPosition); + + foreach (var (_, doAfter) in comp.DoAfters) + { + var elapsed = doAfter.Accumulator; + var displayRatio = MathF.Min(1.0f, + elapsed / 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; + } + + // 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. + var position = new Vector2(-_barTexture.Width / 2f / EyeManager.PixelsPerMeter, + yOffset / scale + offset / EyeManager.PixelsPerMeter * scale); + + // Draw the underlying bar texture + handle.DrawTexture(_barTexture, position); + + // Draw the bar itself + var cancelled = doAfter.Cancelled; + Color color; + const float flashTime = 0.125f; + + // if we're cancelled then flick red / off. + if (cancelled) + { + var flash = Math.Floor(doAfter.CancelledAccumulator / flashTime) % 2 == 0; + color = new Color(1f, 0f, 0f, flash ? 1f : 0f); + } + else + { + color = GetProgressColor(displayRatio); + } + + // 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); + box = box.Translated(position); + handle.DrawRect(box, color); + + index++; + } + } + + handle.UseShader(null); + handle.SetTransform(Matrix3.Identity); + } + + public static Color GetProgressColor(float progress) + { + if (progress >= 1.0f) + { + return new Color(0f, 1f, 0f); + } + // lerp + var hue = (5f / 18f) * progress; + return Color.FromHsv((hue, 1f, 0.75f, 1f)); + } +} diff --git a/Content.Client/DoAfter/DoAfterSystem.cs b/Content.Client/DoAfter/DoAfterSystem.cs index af457e3da9..20a6367f80 100644 --- a/Content.Client/DoAfter/DoAfterSystem.cs +++ b/Content.Client/DoAfter/DoAfterSystem.cs @@ -1,13 +1,11 @@ -using System.Linq; -using Content.Client.DoAfter.UI; using Content.Shared.DoAfter; using Content.Shared.Examine; using JetBrains.Annotations; -using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Player; -using Robust.Client.UserInterface.Controls; +using Robust.Shared.Collections; using Robust.Shared.GameStates; +using Robust.Shared.Network; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -20,18 +18,8 @@ namespace Content.Client.DoAfter [UsedImplicitly] public sealed class DoAfterSystem : EntitySystem { - /* - * How this is currently setup (client-side): - * DoAfterGui handles the actual bars displayed above heads. It also uses FrameUpdate to flash cancellations - * DoAfterEntitySystem handles checking predictions every tick as well as removing / cancelling DoAfters due to time elapsed. - * DoAfterComponent handles network messages inbound as well as storing the DoAfter data. - * It'll also handle overall cleanup when one is removed (i.e. removing it from DoAfterGui). - */ - [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly ExamineSystemShared _examineSystem = default!; - [Dependency] private readonly SharedTransformSystem _xformSystem = default!; /// /// We'll use an excess time so stuff like finishing effects can show. @@ -43,9 +31,14 @@ namespace Content.Client.DoAfter base.Initialize(); UpdatesOutsidePrediction = true; SubscribeNetworkEvent(OnCancelledDoAfter); - SubscribeLocalEvent(OnDoAfterStartup); - SubscribeLocalEvent(OnDoAfterShutdown); SubscribeLocalEvent(OnDoAfterHandleState); + IoCManager.Resolve().AddOverlay(new DoAfterOverlay()); + } + + public override void Shutdown() + { + base.Shutdown(); + IoCManager.Resolve().RemoveOverlay(); } private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args) @@ -86,24 +79,6 @@ namespace Content.Client.DoAfter component.DoAfters.Add(doAfter.ID, doAfter); } - - if (component.Gui == null || component.Gui.Disposed) - return; - - foreach (var (_, doAfter) in component.DoAfters) - { - component.Gui.AddDoAfter(doAfter); - } - } - - private void OnDoAfterStartup(EntityUid uid, DoAfterComponent component, ComponentStartup args) - { - Enable(component); - } - - private void OnDoAfterShutdown(EntityUid uid, DoAfterComponent component, ComponentShutdown args) - { - Disable(component); } private void OnCancelledDoAfter(CancelledDoAfterMessage ev) @@ -113,33 +88,6 @@ namespace Content.Client.DoAfter Cancel(doAfter, ev.ID); } - /// - /// For handling PVS so we dispose of controls if they go out of range - /// - public void Enable(DoAfterComponent component) - { - if (component.Gui?.Disposed == false) - return; - - component.Gui = new DoAfterGui {AttachedEntity = component.Owner}; - - foreach (var (_, doAfter) in component.DoAfters) - { - component.Gui.AddDoAfter(doAfter); - } - - foreach (var (_, cancelled) in component.CancelledDoAfters) - { - component.Gui.CancelDoAfter(cancelled.ID); - } - } - - public void Disable(DoAfterComponent component) - { - component.Gui?.Dispose(); - component.Gui = null; - } - /// /// Remove a DoAfter without showing a cancellation graphic. /// @@ -150,22 +98,10 @@ namespace Content.Client.DoAfter var found = false; - for (var i = component.CancelledDoAfters.Count - 1; i >= 0; i--) - { - var cancelled = component.CancelledDoAfters[i]; - - if (cancelled.Message == clientDoAfter) - { - component.CancelledDoAfters.RemoveAt(i); - found = true; - break; - } - } + component.CancelledDoAfters.Remove(clientDoAfter.ID); if (!found) component.DoAfters.Remove(clientDoAfter.ID); - - component.Gui?.RemoveDoAfter(clientDoAfter.ID); } /// @@ -174,98 +110,70 @@ namespace Content.Client.DoAfter /// Actual removal is handled by DoAfterEntitySystem. public void Cancel(DoAfterComponent component, byte id) { - foreach (var (_, cancelled) in component.CancelledDoAfters) - { - if (cancelled.ID == id) - return; - } + if (component.CancelledDoAfters.ContainsKey(id)) + return; if (!component.DoAfters.ContainsKey(id)) return; var doAfterMessage = component.DoAfters[id]; - component.CancelledDoAfters.Add((_gameTiming.CurTime, doAfterMessage)); - component.Gui?.CancelDoAfter(id); + doAfterMessage.Cancelled = true; + component.CancelledDoAfters.Add(id, doAfterMessage); } - // TODO move this to an overlay // TODO separate DoAfter & ActiveDoAfter components for the entity query. public override void Update(float frameTime) { - base.Update(frameTime); - - var currentTime = _gameTiming.CurTime; - var attached = _player.LocalPlayer?.ControlledEntity; - - // Can't see any I guess? - if (attached == null || Deleted(attached)) + if (!_gameTiming.IsFirstTimePredicted) return; - // ReSharper disable once ConvertToLocalFunction - var predicate = static (EntityUid uid, (EntityUid compOwner, EntityUid? attachedEntity) data) - => uid == data.compOwner || uid == data.attachedEntity; + var playerEntity = _player.LocalPlayer?.ControlledEntity; - var occluded = _examineSystem.IsOccluded(attached.Value); - var viewbox = _eyeManager.GetWorldViewport().Enlarged(2.0f); - var xforms = GetEntityQuery(); - var entXform = xforms.GetComponent(attached.Value); - var playerPos = _xformSystem.GetWorldPosition(entXform, xforms); - - foreach (var (comp, xform) in EntityManager.EntityQuery(true)) + foreach (var (comp, xform) in EntityQuery()) { var doAfters = comp.DoAfters; - if (doAfters.Count == 0 || xform.MapID != entXform.MapID) + if (doAfters.Count == 0) { - Disable(comp); continue; } - var compPos = _xformSystem.GetWorldPosition(xform, xforms); - - if (!viewbox.Contains(compPos)) - { - Disable(comp); - continue; - } - - var range = (compPos - playerPos).Length + 0.01f; - - if (occluded && - comp.Owner != attached && - // Static ExamineSystemShared.InRangeUnOccluded has to die. - !ExamineSystemShared.InRangeUnOccluded( - new(playerPos, entXform.MapID), - new(compPos, entXform.MapID), range, - (comp.Owner, attached), predicate, - entMan: EntityManager)) - { - Disable(comp); - continue; - } - - Enable(comp); - var userGrid = xform.Coordinates; var toRemove = new RemQueue(); // Check cancellations / finishes foreach (var (id, doAfter) in doAfters) { - var elapsedTime = (currentTime - doAfter.StartTime).TotalSeconds; - // If we've passed the final time (after the excess to show completion graphic) then remove. - if (elapsedTime > doAfter.Delay + ExcessTime) + if ((doAfter.Accumulator + doAfter.CancelledAccumulator) > doAfter.Delay + ExcessTime) { toRemove.Add(doAfter); continue; } - // Don't predict cancellation if it's already finished. - if (elapsedTime > doAfter.Delay) + if (doAfter.Cancelled) + { + doAfter.CancelledAccumulator += frameTime; continue; + } + + doAfter.Accumulator += frameTime; + + // Well we finished so don't try to predict cancels. + if (doAfter.Accumulator > doAfter.Delay) + { + continue; + } // Predictions + if (comp.Owner != playerEntity) + continue; + + // TODO: Add these back in when I work out some system for changing the accumulation rate + // based on ping. Right now these would show as cancelled near completion if we moved at the end + // despite succeeding. + continue; + if (doAfter.BreakOnUserMove) { if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold)) @@ -292,16 +200,21 @@ namespace Content.Client.DoAfter Remove(comp, doAfter); } - var count = comp.CancelledDoAfters.Count; // Remove cancelled DoAfters after ExcessTime has elapsed - for (var i = count - 1; i >= 0; i--) + var toRemoveCancelled = new List(); + + foreach (var (_, doAfter) in comp.CancelledDoAfters) { - var cancelled = comp.CancelledDoAfters[i]; - if ((currentTime - cancelled.CancelTime).TotalSeconds > ExcessTime) + if (doAfter.CancelledAccumulator > ExcessTime) { - Remove(comp, cancelled.Message); + toRemoveCancelled.Add(doAfter); } } + + foreach (var doAfter in toRemoveCancelled) + { + Remove(comp, doAfter); + } } } } diff --git a/Content.Client/DoAfter/UI/DoAfterBar.cs b/Content.Client/DoAfter/UI/DoAfterBar.cs deleted file mode 100644 index 69efc91388..0000000000 --- a/Content.Client/DoAfter/UI/DoAfterBar.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using Robust.Client.Graphics; -using Robust.Client.UserInterface; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; - -namespace Content.Client.DoAfter.UI -{ - public sealed class DoAfterBar : Control - { - private IGameTiming _gameTiming = default!; - - private readonly ShaderInstance _shader; - - /// - /// Set from 0.0f to 1.0f to reflect bar progress - /// - public float Ratio - { - get => _ratio; - set => _ratio = value; - } - - private float _ratio = 1.0f; - - /// - /// Flash red until removed - /// - public bool Cancelled - { - get => _cancelled; - set - { - if (_cancelled == value) - { - return; - } - - _cancelled = value; - if (_cancelled) - { - _gameTiming = IoCManager.Resolve(); - _lastFlash = _gameTiming.CurTime; - } - } - } - - private bool _cancelled; - - /// - /// Is the cancellation bar red? - /// - private bool _flash = true; - - /// - /// Last time we swapped the flash. - /// - private TimeSpan _lastFlash; - - /// - /// How long each cancellation bar flash lasts in seconds. - /// - private const float FlashTime = 0.125f; - - private const int XPixelDiff = 20 * DoAfterBarScale; - - public const byte DoAfterBarScale = 2; - - public DoAfterBar() - { - IoCManager.InjectDependencies(this); - _shader = IoCManager.Resolve().Index("unshaded").Instance(); - VerticalAlignment = VAlignment.Center; - } - - protected override void FrameUpdate(FrameEventArgs args) - { - base.FrameUpdate(args); - if (Cancelled) - { - if ((_gameTiming.CurTime - _lastFlash).TotalSeconds > FlashTime) - { - _lastFlash = _gameTiming.CurTime; - _flash = !_flash; - } - } - } - - protected override void Draw(DrawingHandleScreen handle) - { - base.Draw(handle); - - Color color; - - if (Cancelled) - { - if ((_gameTiming.CurTime - _lastFlash).TotalSeconds > FlashTime) - { - _lastFlash = _gameTiming.CurTime; - _flash = !_flash; - } - - color = new Color(1.0f, 0.0f, 0.0f, _flash ? 1.0f : 0.0f); - } - else - { - color = DoAfterHelpers.GetProgressColor(Ratio); - } - - handle.UseShader(_shader); - // If you want to make this less hard-coded be my guest - var leftOffset = 2 * DoAfterBarScale; - var box = new UIBox2i( - leftOffset, - -2 + 2 * DoAfterBarScale, - leftOffset + (int) (XPixelDiff * Ratio * UIScale), - -2); - handle.DrawRect(box, color); - handle.UseShader(null); - } - } - - public static class DoAfterHelpers - { - public static Color GetProgressColor(float progress) - { - if (progress >= 1.0f) - { - return new Color(0f, 1f, 0f); - } - // lerp - var hue = (5f / 18f) * progress; - return Color.FromHsv((hue, 1f, 0.75f, 1f)); - } - } -} diff --git a/Content.Client/DoAfter/UI/DoAfterControl.cs b/Content.Client/DoAfter/UI/DoAfterControl.cs deleted file mode 100644 index 1f7d4dbcdf..0000000000 --- a/Content.Client/DoAfter/UI/DoAfterControl.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Client.Resources; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Map; -using Robust.Shared.Timing; - -namespace Content.Client.DoAfter.UI; - -public sealed class DoAfterControl : PanelContainer -{ - public float Ratio - { - get => _bar.Ratio; - set => _bar.Ratio = value; - } - - public bool Cancelled - { - get => _bar.Cancelled; - set => _bar.Cancelled = value; - } - - private DoAfterBar _bar; - - public DoAfterControl() - { - IoCManager.InjectDependencies(this); - - var cache = IoCManager.Resolve(); - - AddChild(new TextureRect - { - Texture = cache.GetTexture("/Textures/Interface/Misc/progress_bar.rsi/icon.png"), - TextureScale = Vector2.One * DoAfterBar.DoAfterBarScale, - VerticalAlignment = VAlignment.Center, - }); - - _bar = new DoAfterBar(); - AddChild(_bar); - VerticalAlignment = VAlignment.Bottom; - _bar.Measure(Vector2.Infinity); - Measure(Vector2.Infinity); - } -} diff --git a/Content.Client/DoAfter/UI/DoAfterGui.cs b/Content.Client/DoAfter/UI/DoAfterGui.cs deleted file mode 100644 index 7b40b20bf2..0000000000 --- a/Content.Client/DoAfter/UI/DoAfterGui.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Client.Graphics; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Timing; - -namespace Content.Client.DoAfter.UI -{ - public sealed class DoAfterGui : BoxContainer - { - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IEyeManager _eyeManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - - private readonly Dictionary _doAfterControls = new(); - - // We'll store cancellations for a little bit just so we can flash the graphic to indicate it's cancelled - private readonly Dictionary _cancelledDoAfters = new(); - - public EntityUid? AttachedEntity { get; set; } - - public DoAfterGui() - { - Orientation = LayoutOrientation.Vertical; - - IoCManager.InjectDependencies(this); - IoCManager.Resolve().StateRoot.AddChild(this); - SeparationOverride = 0; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (Disposed) - return; - - foreach (var (_, control) in _doAfterControls) - { - control.Dispose(); - } - - _doAfterControls.Clear(); - _cancelledDoAfters.Clear(); - } - - /// - /// Add the necessary control for a DoAfter progress bar. - /// - public void AddDoAfter(ClientDoAfter message) - { - if (_doAfterControls.ContainsKey(message.ID)) - return; - - var doAfterBar = new DoAfterControl(); - AddChild(doAfterBar); - _doAfterControls.Add(message.ID, doAfterBar); - Measure(Vector2.Infinity); - } - - // NOTE THAT THE BELOW ONLY HANDLES THE UI SIDE - - /// - /// Removes a DoAfter without showing a cancel graphic. - /// - /// - public void RemoveDoAfter(byte id) - { - if (!_doAfterControls.ContainsKey(id)) - return; - - var control = _doAfterControls[id]; - RemoveChild(control); - control.DisposeAllChildren(); - _doAfterControls.Remove(id); - _cancelledDoAfters.Remove(id); - } - - /// - /// Cancels a DoAfter and shows a graphic indicating it has been cancelled to the player. - /// - /// Can be called multiple times on the 1 DoAfter because of the client predicting the cancellation. - /// - public void CancelDoAfter(byte id) - { - if (_cancelledDoAfters.ContainsKey(id)) - return; - - if (!_doAfterControls.TryGetValue(id, out var doAfterControl)) - { - doAfterControl = new DoAfterControl(); - AddChild(doAfterControl); - } - - doAfterControl.Cancelled = true; - _cancelledDoAfters.Add(id, _gameTiming.CurTime); - } - - protected override void FrameUpdate(FrameEventArgs args) - { - base.FrameUpdate(args); - - if (!_entityManager.TryGetComponent(AttachedEntity, out DoAfterComponent? doAfterComponent)) - { - Visible = false; - return; - } - - var doAfters = doAfterComponent.DoAfters; - - if (doAfters.Count == 0) - { - Visible = false; - return; - } - - var transform = _entityManager.GetComponent(AttachedEntity.Value); - - if (_eyeManager.CurrentMap != transform.MapID || !transform.Coordinates.IsValid(_entityManager)) - { - Visible = false; - return; - } - - Visible = true; - var currentTime = _gameTiming.CurTime; - var toRemove = new List(); - - // Cleanup cancelled DoAfters - foreach (var (id, cancelTime) in _cancelledDoAfters) - { - if ((currentTime - cancelTime).TotalSeconds > DoAfterSystem.ExcessTime) - toRemove.Add(id); - } - - foreach (var id in toRemove) - { - RemoveDoAfter(id); - } - - toRemove.Clear(); - - // Update 0 -> 1.0f of the things - foreach (var (id, message) in doAfters) - { - if (_cancelledDoAfters.ContainsKey(id) || !_doAfterControls.ContainsKey(id)) - continue; - - var control = _doAfterControls[id]; - var ratio = (currentTime - message.StartTime).TotalSeconds; - control.Ratio = MathF.Min(1.0f, - (float) ratio / message.Delay); - - // Just in case it doesn't get cleaned up by the system for whatever reason. - if (ratio > message.Delay + DoAfterSystem.ExcessTime) - { - toRemove.Add(id); - continue; - } - } - - foreach (var id in toRemove) - { - RemoveDoAfter(id); - } - - UpdatePosition(transform); - } - - public void UpdatePosition(TransformComponent xform) - { - var screenCoordinates = _eyeManager.CoordinatesToScreen(xform.Coordinates); - var position = screenCoordinates.Position / UIScale - DesiredSize / 2f; - LayoutContainer.SetPosition(this, position - new Vector2(0, 40f)); - } - } -} diff --git a/Content.Shared/DoAfter/SharedDoAfterComponent.cs b/Content.Shared/DoAfter/SharedDoAfterComponent.cs index 29c6079b95..3d75916d72 100644 --- a/Content.Shared/DoAfter/SharedDoAfterComponent.cs +++ b/Content.Shared/DoAfter/SharedDoAfterComponent.cs @@ -34,12 +34,20 @@ namespace Content.Shared.DoAfter } } + // TODO: Merge this with the actual DoAfter /// /// We send a trimmed-down version of the DoAfter for the client for it to use. /// [Serializable, NetSerializable] public sealed class ClientDoAfter { + public bool Cancelled = false; + + /// + /// Accrued time when cancelled. + /// + public float CancelledAccumulator; + // To see what these do look at DoAfter and DoAfterEventArgs public byte ID { get; } @@ -51,6 +59,8 @@ namespace Content.Shared.DoAfter public EntityUid? Target { get; } + public float Accumulator; + public float Delay { get; } // TODO: The other ones need predicting