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.
This commit is contained in:
Pieter-Jan Briers
2024-03-30 02:40:55 +01:00
committed by GitHub
parent 72c6a14d59
commit 3b791459c7
7 changed files with 111 additions and 46 deletions

View File

@@ -5,6 +5,7 @@ using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Content.Shared.Shuttles.UI.MapObjects; using Content.Shared.Shuttles.UI.MapObjects;
using Content.Shared.Timing;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -38,16 +39,11 @@ public sealed partial class MapScreen : BoxContainer
private EntityUid? _shuttleEntity; private EntityUid? _shuttleEntity;
private FTLState _state; private FTLState _state;
private float _ftlDuration; private StartEndTime _ftlTime;
private List<ShuttleBeaconObject> _beacons = new(); private List<ShuttleBeaconObject> _beacons = new();
private List<ShuttleExclusionObject> _exclusions = new(); private List<ShuttleExclusionObject> _exclusions = new();
/// <summary>
/// When the next FTL state change happens.
/// </summary>
private TimeSpan _nextFtlTime;
private TimeSpan _nextPing; private TimeSpan _nextPing;
private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3); private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
private TimeSpan _nextMapDequeue; private TimeSpan _nextMapDequeue;
@@ -114,8 +110,7 @@ public sealed partial class MapScreen : BoxContainer
_beacons = state.Destinations; _beacons = state.Destinations;
_exclusions = state.Exclusions; _exclusions = state.Exclusions;
_state = state.FTLState; _state = state.FTLState;
_ftlDuration = state.FTLDuration; _ftlTime = state.FTLTime;
_nextFtlTime = _timing.CurTime + TimeSpan.FromSeconds(_ftlDuration);
MapRadar.InFtl = true; MapRadar.InFtl = true;
MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}"); MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
@@ -511,20 +506,8 @@ public sealed partial class MapScreen : BoxContainer
MapRebuildButton.Disabled = false; MapRebuildButton.Disabled = false;
} }
var ftlDiff = (float) (_nextFtlTime - _timing.CurTime).TotalSeconds; var progress = _ftlTime.ProgressAt(curTime);
FTLBar.Value = float.IsFinite(progress) ? progress : 1;
float ftlRatio;
if (_ftlDuration.Equals(0f))
{
ftlRatio = 1f;
}
else
{
ftlRatio = Math.Clamp(1f - (ftlDiff / _ftlDuration), 0f, 1f);
}
FTLBar.Value = ftlRatio;
} }
protected override void Draw(DrawingHandleScreen handle) protected override void Draw(DrawingHandleScreen handle)

View File

@@ -1,5 +1,6 @@
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Timing;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -16,15 +17,15 @@ public sealed partial class FTLComponent : Component
[ViewVariables] [ViewVariables]
public FTLState State = FTLState.Available; public FTLState State = FTLState.Available;
[ViewVariables(VVAccess.ReadWrite)]
public StartEndTime StateTime;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float StartupTime = 0f; public float StartupTime = 0f;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float TravelTime = 0f; public float TravelTime = 0f;
[ViewVariables(VVAccess.ReadWrite)]
public float Accumulator = 0f;
/// <summary> /// <summary>
/// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates. /// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates.
/// </summary> /// </summary>

View File

@@ -13,6 +13,7 @@ using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.Shuttles.UI.MapObjects; using Content.Shared.Shuttles.UI.MapObjects;
using Content.Shared.Timing;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -257,7 +258,11 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
else else
{ {
navState = new NavInterfaceState(0f, null, null, new Dictionary<NetEntity, List<DockingPortState>>()); navState = new NavInterfaceState(0f, null, null, new Dictionary<NetEntity, List<DockingPortState>>());
mapState = new ShuttleMapInterfaceState(FTLState.Invalid, 0f, new List<ShuttleBeaconObject>(), new List<ShuttleExclusionObject>()); mapState = new ShuttleMapInterfaceState(
FTLState.Invalid,
default,
new List<ShuttleBeaconObject>(),
new List<ShuttleExclusionObject>());
} }
if (_ui.TryGetUi(consoleUid, ShuttleConsoleUiKey.Key, out var bui)) if (_ui.TryGetUi(consoleUid, ShuttleConsoleUiKey.Key, out var bui))
@@ -408,12 +413,12 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
public ShuttleMapInterfaceState GetMapState(Entity<FTLComponent?> shuttle) public ShuttleMapInterfaceState GetMapState(Entity<FTLComponent?> shuttle)
{ {
FTLState ftlState = FTLState.Available; FTLState ftlState = FTLState.Available;
float stateDuration = 0f; StartEndTime stateDuration = default;
if (Resolve(shuttle, ref shuttle.Comp, false) && shuttle.Comp.LifeStage < ComponentLifeStage.Stopped) if (Resolve(shuttle, ref shuttle.Comp, false) && shuttle.Comp.LifeStage < ComponentLifeStage.Stopped)
{ {
ftlState = shuttle.Comp.State; ftlState = shuttle.Comp.State;
stateDuration = _shuttle.GetStateDuration(shuttle.Comp); stateDuration = _shuttle.GetStateTime(shuttle.Comp);
} }
List<ShuttleBeaconObject>? beacons = null; List<ShuttleBeaconObject>? beacons = null;
@@ -422,7 +427,8 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
GetExclusions(ref exclusions); GetExclusions(ref exclusions);
return new ShuttleMapInterfaceState( return new ShuttleMapInterfaceState(
ftlState, stateDuration, ftlState,
stateDuration,
beacons ?? new List<ShuttleBeaconObject>(), beacons ?? new List<ShuttleBeaconObject>(),
exclusions ?? new List<ShuttleExclusionObject>()); exclusions ?? new List<ShuttleExclusionObject>());
} }

View File

@@ -12,6 +12,7 @@ using Content.Shared.Parallax;
using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Content.Shared.StatusEffect; using Content.Shared.StatusEffect;
using Content.Shared.Timing;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -131,7 +132,7 @@ public sealed partial class ShuttleSystem
return mapUid; return mapUid;
} }
public float GetStateDuration(FTLComponent component) public StartEndTime GetStateTime(FTLComponent component)
{ {
var state = component.State; var state = component.State;
@@ -141,9 +142,9 @@ public sealed partial class ShuttleSystem
case FTLState.Travelling: case FTLState.Travelling:
case FTLState.Arriving: case FTLState.Arriving:
case FTLState.Cooldown: case FTLState.Cooldown:
return component.Accumulator; return component.StateTime;
case FTLState.Available: case FTLState.Available:
return 0f; return default;
default: default:
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -251,7 +252,9 @@ public sealed partial class ShuttleSystem
hyperspace.StartupTime = startupTime; hyperspace.StartupTime = startupTime;
hyperspace.TravelTime = hyperspaceTime; hyperspace.TravelTime = hyperspaceTime;
hyperspace.Accumulator = hyperspace.StartupTime; hyperspace.StateTime = StartEndTime.FromStartDuration(
_gameTiming.CurTime,
TimeSpan.FromSeconds(hyperspace.StartupTime));
hyperspace.TargetCoordinates = coordinates; hyperspace.TargetCoordinates = coordinates;
hyperspace.TargetAngle = angle; hyperspace.TargetAngle = angle;
hyperspace.PriorityTag = priorityTag; hyperspace.PriorityTag = priorityTag;
@@ -282,7 +285,9 @@ public sealed partial class ShuttleSystem
var config = _dockSystem.GetDockingConfig(shuttleUid, target, priorityTag); var config = _dockSystem.GetDockingConfig(shuttleUid, target, priorityTag);
hyperspace.StartupTime = startupTime; hyperspace.StartupTime = startupTime;
hyperspace.TravelTime = hyperspaceTime; hyperspace.TravelTime = hyperspaceTime;
hyperspace.Accumulator = hyperspace.StartupTime; hyperspace.StateTime = StartEndTime.FromStartDuration(
_gameTiming.CurTime,
TimeSpan.FromSeconds(hyperspace.StartupTime));
hyperspace.PriorityTag = priorityTag; hyperspace.PriorityTag = priorityTag;
_console.RefreshShuttleConsoles(shuttleUid); _console.RefreshShuttleConsoles(shuttleUid);
@@ -366,7 +371,7 @@ public sealed partial class ShuttleSystem
// Reset rotation so they always face the same direction. // Reset rotation so they always face the same direction.
xform.LocalRotation = Angle.Zero; xform.LocalRotation = Angle.Zero;
_index += width + Buffer; _index += width + Buffer;
comp.Accumulator += comp.TravelTime - DefaultArrivalTime; comp.StateTime = StartEndTime.FromCurTime(_gameTiming, comp.TravelTime - DefaultArrivalTime);
Enable(uid, component: body); Enable(uid, component: body);
_physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body); _physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body);
@@ -401,7 +406,7 @@ public sealed partial class ShuttleSystem
{ {
var shuttle = entity.Comp2; var shuttle = entity.Comp2;
var comp = entity.Comp1; var comp = entity.Comp1;
comp.Accumulator += DefaultArrivalTime; comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime);
comp.State = FTLState.Arriving; comp.State = FTLState.Arriving;
// TODO: Arrival effects // TODO: Arrival effects
// For now we'll just use the ss13 bubbles but we can do fancier. // 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.State = FTLState.Cooldown;
comp.Accumulator += FTLCooldown; comp.StateTime = StartEndTime.FromCurTime(_gameTiming, FTLCooldown);
_console.RefreshShuttleConsoles(uid); _console.RefreshShuttleConsoles(uid);
_mapManager.SetMapPaused(mapId, false); _mapManager.SetMapPaused(mapId, false);
Smimsh(uid, xform: xform); Smimsh(uid, xform: xform);
@@ -519,15 +524,14 @@ public sealed partial class ShuttleSystem
_console.RefreshShuttleConsoles(entity); _console.RefreshShuttleConsoles(entity);
} }
private void UpdateHyperspace(float frameTime) private void UpdateHyperspace()
{ {
var curTime = _gameTiming.CurTime;
var query = EntityQueryEnumerator<FTLComponent, ShuttleComponent>(); var query = EntityQueryEnumerator<FTLComponent, ShuttleComponent>();
while (query.MoveNext(out var uid, out var comp, out var shuttle)) while (query.MoveNext(out var uid, out var comp, out var shuttle))
{ {
comp.Accumulator -= frameTime; if (curTime < comp.StateTime.End)
if (comp.Accumulator > 0f)
continue; continue;
var entity = (uid, comp, shuttle); var entity = (uid, comp, shuttle);

View File

@@ -19,6 +19,7 @@ using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Systems;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Shuttles.Systems; namespace Content.Server.Shuttles.Systems;
@@ -30,6 +31,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly BiomeSystem _biomes = default!; [Dependency] private readonly BiomeSystem _biomes = default!;
[Dependency] private readonly BodySystem _bobby = default!; [Dependency] private readonly BodySystem _bobby = default!;
[Dependency] private readonly DockingSystem _dockSystem = default!; [Dependency] private readonly DockingSystem _dockSystem = default!;
@@ -68,7 +70,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
UpdateHyperspace(frameTime); UpdateHyperspace();
} }
private void OnGridFixtureChange(EntityUid uid, FixturesComponent manager, GridFixtureChangeEvent args) private void OnGridFixtureChange(EntityUid uid, FixturesComponent manager, GridFixtureChangeEvent args)

View File

@@ -1,5 +1,6 @@
using Content.Shared.Shuttles.Systems; using Content.Shared.Shuttles.Systems;
using Content.Shared.Shuttles.UI.MapObjects; using Content.Shared.Shuttles.UI.MapObjects;
using Content.Shared.Timing;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Shuttles.BUIStates; namespace Content.Shared.Shuttles.BUIStates;
@@ -16,9 +17,9 @@ public sealed class ShuttleMapInterfaceState
public readonly FTLState FTLState; public readonly FTLState FTLState;
/// <summary> /// <summary>
/// How long the FTL state takes. /// When the current FTL state starts and ends.
/// </summary> /// </summary>
public float FTLDuration; public StartEndTime FTLTime;
public List<ShuttleBeaconObject> Destinations; public List<ShuttleBeaconObject> Destinations;
@@ -26,12 +27,12 @@ public sealed class ShuttleMapInterfaceState
public ShuttleMapInterfaceState( public ShuttleMapInterfaceState(
FTLState ftlState, FTLState ftlState,
float ftlDuration, StartEndTime ftlTime,
List<ShuttleBeaconObject> destinations, List<ShuttleBeaconObject> destinations,
List<ShuttleExclusionObject> exclusions) List<ShuttleExclusionObject> exclusions)
{ {
FTLState = ftlState; FTLState = ftlState;
FTLDuration = ftlDuration; FTLTime = ftlTime;
Destinations = destinations; Destinations = destinations;
Exclusions = exclusions; Exclusions = exclusions;
} }

View File

@@ -0,0 +1,68 @@
using Robust.Shared.Timing;
namespace Content.Shared.Timing;
/// <summary>
/// Represents a range of an "action" in time, as start/end times.
/// </summary>
/// <remarks>
/// Positions in time are represented as <see cref="TimeSpan"/>s, usually from <see cref="IGameTiming.CurTime"/>
/// or <see cref="IGameTiming.RealTime"/>.
/// </remarks>
/// <param name="Start">The time the action starts.</param>
/// <param name="End">The time action ends.</param>
[Serializable]
public record struct StartEndTime(TimeSpan Start, TimeSpan End)
{
/// <summary>
/// How long the action takes.
/// </summary>
public TimeSpan Length => End - Start;
/// <summary>
/// Get how far the action has progressed relative to a time value.
/// </summary>
/// <param name="time">The time to get the current progress value for.</param>
/// <param name="clamp">If true, clamp values outside the time range to 0 through 1.</param>
/// <returns>
/// <para>
/// A progress value. Zero means <paramref name="time"/> is at <see cref="Start"/>,
/// one means <paramref name="time"/> is at <see cref="End"/>.
/// </para>
/// <para>
/// This function returns <see cref="float.NaN"/> if <see cref="Start"/> and <see cref="End"/> are identical.
/// </para>
/// </returns>
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);
}
}