From 3b791459c74c6b56c8ae6204a936f6de06674b93 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 30 Mar 2024 02:40:55 +0100 Subject: [PATCH] Refactor FTL time tracking code to fix a UI bug (#26538) The FTL UI on the shuttle console would reset the FTL progress bar every time you open it. This is because the server only sends "time until completion", not a start/end time. The FTL code now uses a separate start/end time so the exact same progress bar can be preserved. For convenience, I made a StartEndTime record struct that stores the actual tuple. This is now used by the code and has some helpers. --- Content.Client/Shuttles/UI/MapScreen.xaml.cs | 27 ++------ .../Shuttles/Components/FTLComponent.cs | 7 +- .../Shuttles/Systems/ShuttleConsoleSystem.cs | 14 ++-- .../Systems/ShuttleSystem.FasterThanLight.cs | 28 ++++---- .../Shuttles/Systems/ShuttleSystem.cs | 4 +- .../BUIStates/ShuttleMapInterfaceState.cs | 9 +-- Content.Shared/Timing/StartEndTime.cs | 68 +++++++++++++++++++ 7 files changed, 111 insertions(+), 46 deletions(-) create mode 100644 Content.Shared/Timing/StartEndTime.cs diff --git a/Content.Client/Shuttles/UI/MapScreen.xaml.cs b/Content.Client/Shuttles/UI/MapScreen.xaml.cs index 8430699bae..225d1be14e 100644 --- a/Content.Client/Shuttles/UI/MapScreen.xaml.cs +++ b/Content.Client/Shuttles/UI/MapScreen.xaml.cs @@ -5,6 +5,7 @@ using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.UI.MapObjects; +using Content.Shared.Timing; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -38,16 +39,11 @@ public sealed partial class MapScreen : BoxContainer private EntityUid? _shuttleEntity; private FTLState _state; - private float _ftlDuration; + private StartEndTime _ftlTime; private List _beacons = new(); private List _exclusions = new(); - /// - /// When the next FTL state change happens. - /// - private TimeSpan _nextFtlTime; - private TimeSpan _nextPing; private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3); private TimeSpan _nextMapDequeue; @@ -114,8 +110,7 @@ public sealed partial class MapScreen : BoxContainer _beacons = state.Destinations; _exclusions = state.Exclusions; _state = state.FTLState; - _ftlDuration = state.FTLDuration; - _nextFtlTime = _timing.CurTime + TimeSpan.FromSeconds(_ftlDuration); + _ftlTime = state.FTLTime; MapRadar.InFtl = true; MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}"); @@ -511,20 +506,8 @@ public sealed partial class MapScreen : BoxContainer MapRebuildButton.Disabled = false; } - var ftlDiff = (float) (_nextFtlTime - _timing.CurTime).TotalSeconds; - - float ftlRatio; - - if (_ftlDuration.Equals(0f)) - { - ftlRatio = 1f; - } - else - { - ftlRatio = Math.Clamp(1f - (ftlDiff / _ftlDuration), 0f, 1f); - } - - FTLBar.Value = ftlRatio; + var progress = _ftlTime.ProgressAt(curTime); + FTLBar.Value = float.IsFinite(progress) ? progress : 1; } protected override void Draw(DrawingHandleScreen handle) diff --git a/Content.Server/Shuttles/Components/FTLComponent.cs b/Content.Server/Shuttles/Components/FTLComponent.cs index d15f65a355..a3da4855f7 100644 --- a/Content.Server/Shuttles/Components/FTLComponent.cs +++ b/Content.Server/Shuttles/Components/FTLComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Tag; +using Content.Shared.Timing; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -16,15 +17,15 @@ public sealed partial class FTLComponent : Component [ViewVariables] public FTLState State = FTLState.Available; + [ViewVariables(VVAccess.ReadWrite)] + public StartEndTime StateTime; + [ViewVariables(VVAccess.ReadWrite)] public float StartupTime = 0f; [ViewVariables(VVAccess.ReadWrite)] public float TravelTime = 0f; - [ViewVariables(VVAccess.ReadWrite)] - public float Accumulator = 0f; - /// /// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates. /// diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index f0368ed3a9..a4f2c7b4db 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Tag; using Content.Shared.Movement.Systems; using Content.Shared.Shuttles.UI.MapObjects; +using Content.Shared.Timing; using Robust.Server.GameObjects; using Robust.Shared.Collections; using Robust.Shared.GameStates; @@ -257,7 +258,11 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem else { navState = new NavInterfaceState(0f, null, null, new Dictionary>()); - mapState = new ShuttleMapInterfaceState(FTLState.Invalid, 0f, new List(), new List()); + mapState = new ShuttleMapInterfaceState( + FTLState.Invalid, + default, + new List(), + new List()); } if (_ui.TryGetUi(consoleUid, ShuttleConsoleUiKey.Key, out var bui)) @@ -408,12 +413,12 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem public ShuttleMapInterfaceState GetMapState(Entity shuttle) { FTLState ftlState = FTLState.Available; - float stateDuration = 0f; + StartEndTime stateDuration = default; if (Resolve(shuttle, ref shuttle.Comp, false) && shuttle.Comp.LifeStage < ComponentLifeStage.Stopped) { ftlState = shuttle.Comp.State; - stateDuration = _shuttle.GetStateDuration(shuttle.Comp); + stateDuration = _shuttle.GetStateTime(shuttle.Comp); } List? beacons = null; @@ -422,7 +427,8 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem GetExclusions(ref exclusions); return new ShuttleMapInterfaceState( - ftlState, stateDuration, + ftlState, + stateDuration, beacons ?? new List(), exclusions ?? new List()); } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index cb322ac396..5128869103 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -12,6 +12,7 @@ using Content.Shared.Parallax; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; using Content.Shared.StatusEffect; +using Content.Shared.Timing; using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Audio; @@ -131,7 +132,7 @@ public sealed partial class ShuttleSystem return mapUid; } - public float GetStateDuration(FTLComponent component) + public StartEndTime GetStateTime(FTLComponent component) { var state = component.State; @@ -141,9 +142,9 @@ public sealed partial class ShuttleSystem case FTLState.Travelling: case FTLState.Arriving: case FTLState.Cooldown: - return component.Accumulator; + return component.StateTime; case FTLState.Available: - return 0f; + return default; default: throw new NotImplementedException(); } @@ -251,7 +252,9 @@ public sealed partial class ShuttleSystem hyperspace.StartupTime = startupTime; hyperspace.TravelTime = hyperspaceTime; - hyperspace.Accumulator = hyperspace.StartupTime; + hyperspace.StateTime = StartEndTime.FromStartDuration( + _gameTiming.CurTime, + TimeSpan.FromSeconds(hyperspace.StartupTime)); hyperspace.TargetCoordinates = coordinates; hyperspace.TargetAngle = angle; hyperspace.PriorityTag = priorityTag; @@ -282,7 +285,9 @@ public sealed partial class ShuttleSystem var config = _dockSystem.GetDockingConfig(shuttleUid, target, priorityTag); hyperspace.StartupTime = startupTime; hyperspace.TravelTime = hyperspaceTime; - hyperspace.Accumulator = hyperspace.StartupTime; + hyperspace.StateTime = StartEndTime.FromStartDuration( + _gameTiming.CurTime, + TimeSpan.FromSeconds(hyperspace.StartupTime)); hyperspace.PriorityTag = priorityTag; _console.RefreshShuttleConsoles(shuttleUid); @@ -366,7 +371,7 @@ public sealed partial class ShuttleSystem // Reset rotation so they always face the same direction. xform.LocalRotation = Angle.Zero; _index += width + Buffer; - comp.Accumulator += comp.TravelTime - DefaultArrivalTime; + comp.StateTime = StartEndTime.FromCurTime(_gameTiming, comp.TravelTime - DefaultArrivalTime); Enable(uid, component: body); _physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body); @@ -401,7 +406,7 @@ public sealed partial class ShuttleSystem { var shuttle = entity.Comp2; var comp = entity.Comp1; - comp.Accumulator += DefaultArrivalTime; + comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime); comp.State = FTLState.Arriving; // TODO: Arrival effects // For now we'll just use the ss13 bubbles but we can do fancier. @@ -504,7 +509,7 @@ public sealed partial class ShuttleSystem } comp.State = FTLState.Cooldown; - comp.Accumulator += FTLCooldown; + comp.StateTime = StartEndTime.FromCurTime(_gameTiming, FTLCooldown); _console.RefreshShuttleConsoles(uid); _mapManager.SetMapPaused(mapId, false); Smimsh(uid, xform: xform); @@ -519,15 +524,14 @@ public sealed partial class ShuttleSystem _console.RefreshShuttleConsoles(entity); } - private void UpdateHyperspace(float frameTime) + private void UpdateHyperspace() { + var curTime = _gameTiming.CurTime; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var shuttle)) { - comp.Accumulator -= frameTime; - - if (comp.Accumulator > 0f) + if (curTime < comp.StateTime.End) continue; var entity = (uid, comp, shuttle); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 7c42753a7d..6dc25e8d76 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -19,6 +19,7 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.Shuttles.Systems; @@ -30,6 +31,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BiomeSystem _biomes = default!; [Dependency] private readonly BodySystem _bobby = default!; [Dependency] private readonly DockingSystem _dockSystem = default!; @@ -68,7 +70,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem public override void Update(float frameTime) { base.Update(frameTime); - UpdateHyperspace(frameTime); + UpdateHyperspace(); } private void OnGridFixtureChange(EntityUid uid, FixturesComponent manager, GridFixtureChangeEvent args) diff --git a/Content.Shared/Shuttles/BUIStates/ShuttleMapInterfaceState.cs b/Content.Shared/Shuttles/BUIStates/ShuttleMapInterfaceState.cs index cee0daab4b..3cb7cb6412 100644 --- a/Content.Shared/Shuttles/BUIStates/ShuttleMapInterfaceState.cs +++ b/Content.Shared/Shuttles/BUIStates/ShuttleMapInterfaceState.cs @@ -1,5 +1,6 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.UI.MapObjects; +using Content.Shared.Timing; using Robust.Shared.Serialization; namespace Content.Shared.Shuttles.BUIStates; @@ -16,9 +17,9 @@ public sealed class ShuttleMapInterfaceState public readonly FTLState FTLState; /// - /// How long the FTL state takes. + /// When the current FTL state starts and ends. /// - public float FTLDuration; + public StartEndTime FTLTime; public List Destinations; @@ -26,12 +27,12 @@ public sealed class ShuttleMapInterfaceState public ShuttleMapInterfaceState( FTLState ftlState, - float ftlDuration, + StartEndTime ftlTime, List destinations, List exclusions) { FTLState = ftlState; - FTLDuration = ftlDuration; + FTLTime = ftlTime; Destinations = destinations; Exclusions = exclusions; } diff --git a/Content.Shared/Timing/StartEndTime.cs b/Content.Shared/Timing/StartEndTime.cs new file mode 100644 index 0000000000..fde9f7341c --- /dev/null +++ b/Content.Shared/Timing/StartEndTime.cs @@ -0,0 +1,68 @@ +using Robust.Shared.Timing; + +namespace Content.Shared.Timing; + +/// +/// Represents a range of an "action" in time, as start/end times. +/// +/// +/// Positions in time are represented as s, usually from +/// or . +/// +/// The time the action starts. +/// The time action ends. +[Serializable] +public record struct StartEndTime(TimeSpan Start, TimeSpan End) +{ + /// + /// How long the action takes. + /// + public TimeSpan Length => End - Start; + + /// + /// Get how far the action has progressed relative to a time value. + /// + /// The time to get the current progress value for. + /// If true, clamp values outside the time range to 0 through 1. + /// + /// + /// A progress value. Zero means is at , + /// one means is at . + /// + /// + /// This function returns if and are identical. + /// + /// + public float ProgressAt(TimeSpan time, bool clamp = true) + { + var length = Length; + if (length == default) + return float.NaN; + + var progress = (float) ((time - Start) / length); + if (clamp) + progress = MathHelper.Clamp01(progress); + + return progress; + } + + public static StartEndTime FromStartDuration(TimeSpan start, TimeSpan duration) + { + return new StartEndTime(start, start + duration); + } + + public static StartEndTime FromStartDuration(TimeSpan start, float durationSeconds) + { + return new StartEndTime(start, start + TimeSpan.FromSeconds(durationSeconds)); + } + + public static StartEndTime FromCurTime(IGameTiming gameTiming, TimeSpan duration) + { + return FromStartDuration(gameTiming.CurTime, duration); + } + + public static StartEndTime FromCurTime(IGameTiming gameTiming, float durationSeconds) + { + return FromStartDuration(gameTiming.CurTime, durationSeconds); + } +}