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.
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;
private EntityUid? _shuttleEntity;
private FTLState _state;
- private float _ftlDuration;
+ private StartEndTime _ftlTime;
private List<ShuttleBeaconObject> _beacons = new();
private List<ShuttleExclusionObject> _exclusions = new();
- /// <summary>
- /// When the next FTL state change happens.
- /// </summary>
- private TimeSpan _nextFtlTime;
-
private TimeSpan _nextPing;
private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
private TimeSpan _nextMapDequeue;
_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()}");
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)
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;
public FTLState State = FTLState.Available;
[ViewVariables(VVAccess.ReadWrite)]
- public float StartupTime = 0f;
+ public StartEndTime StateTime;
[ViewVariables(VVAccess.ReadWrite)]
- public float TravelTime = 0f;
+ public float StartupTime = 0f;
[ViewVariables(VVAccess.ReadWrite)]
- public float Accumulator = 0f;
+ public float TravelTime = 0f;
/// <summary>
/// Coordinates to arrive it: May be relative to another grid (for docking) or map coordinates.
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;
else
{
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))
public ShuttleMapInterfaceState GetMapState(Entity<FTLComponent?> 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<ShuttleBeaconObject>? beacons = null;
GetExclusions(ref exclusions);
return new ShuttleMapInterfaceState(
- ftlState, stateDuration,
+ ftlState,
+ stateDuration,
beacons ?? new List<ShuttleBeaconObject>(),
exclusions ?? new List<ShuttleExclusionObject>());
}
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;
return mapUid;
}
- public float GetStateDuration(FTLComponent component)
+ public StartEndTime GetStateTime(FTLComponent component)
{
var state = component.State;
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();
}
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;
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);
// 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);
{
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.
}
comp.State = FTLState.Cooldown;
- comp.Accumulator += FTLCooldown;
+ comp.StateTime = StartEndTime.FromCurTime(_gameTiming, FTLCooldown);
_console.RefreshShuttleConsoles(uid);
_mapManager.SetMapPaused(mapId, false);
Smimsh(uid, xform: xform);
_console.RefreshShuttleConsoles(entity);
}
- private void UpdateHyperspace(float frameTime)
+ private void UpdateHyperspace()
{
+ var curTime = _gameTiming.CurTime;
var query = EntityQueryEnumerator<FTLComponent, ShuttleComponent>();
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);
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Random;
+using Robust.Shared.Timing;
namespace Content.Server.Shuttles.Systems;
[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!;
public override void Update(float frameTime)
{
base.Update(frameTime);
- UpdateHyperspace(frameTime);
+ UpdateHyperspace();
}
private void OnGridFixtureChange(EntityUid uid, FixturesComponent manager, GridFixtureChangeEvent args)
using Content.Shared.Shuttles.Systems;
using Content.Shared.Shuttles.UI.MapObjects;
+using Content.Shared.Timing;
using Robust.Shared.Serialization;
namespace Content.Shared.Shuttles.BUIStates;
public readonly FTLState FTLState;
/// <summary>
- /// How long the FTL state takes.
+ /// When the current FTL state starts and ends.
/// </summary>
- public float FTLDuration;
+ public StartEndTime FTLTime;
public List<ShuttleBeaconObject> Destinations;
public ShuttleMapInterfaceState(
FTLState ftlState,
- float ftlDuration,
+ StartEndTime ftlTime,
List<ShuttleBeaconObject> destinations,
List<ShuttleExclusionObject> exclusions)
{
FTLState = ftlState;
- FTLDuration = ftlDuration;
+ FTLTime = ftlTime;
Destinations = destinations;
Exclusions = exclusions;
}
--- /dev/null
+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);
+ }
+}