diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs index 6de63bbf10..19dd6ecb1e 100644 --- a/Content.Client/Audio/AmbientSoundSystem.cs +++ b/Content.Client/Audio/AmbientSoundSystem.cs @@ -36,7 +36,7 @@ namespace Content.Client.Audio private bool _overlayEnabled; private float _maxAmbientRange; private float _cooldown; - private float _accumulator; + private TimeSpan _targetTime = TimeSpan.Zero; private float _ambienceVolume = 0.0f; /// @@ -131,17 +131,16 @@ namespace Content.Client.Audio { base.Update(frameTime); - if (!_gameTiming.IsFirstTimePredicted) return; + if (!_gameTiming.IsFirstTimePredicted) + return; if (_cooldown <= 0f) - { - _accumulator = 0f; return; - } - _accumulator += frameTime; - if (_accumulator < _cooldown) return; - _accumulator -= _cooldown; + if (_gameTiming.CurTime < _targetTime) + return; + + _targetTime = _gameTiming.CurTime+TimeSpan.FromSeconds(_cooldown); var player = _playerManager.LocalPlayer?.ControlledEntity; if (!EntityManager.TryGetComponent(player, out TransformComponent? playerManager)) diff --git a/Content.Client/Movement/Systems/JetpackSystem.cs b/Content.Client/Movement/Systems/JetpackSystem.cs index 02cfb07094..5e8794d116 100644 --- a/Content.Client/Movement/Systems/JetpackSystem.cs +++ b/Content.Client/Movement/Systems/JetpackSystem.cs @@ -46,10 +46,11 @@ public sealed class JetpackSystem : SharedJetpackSystem foreach (var comp in EntityQuery()) { - comp.Accumulator += frameTime; + if (_timing.CurTime < comp.TargetTime) + continue; + + comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown); - if (comp.Accumulator < comp.EffectCooldown) continue; - comp.Accumulator -= comp.EffectCooldown; CreateParticles(comp.Owner); } } diff --git a/Content.Server/Abilities/Mime/MimePowersComponent.cs b/Content.Server/Abilities/Mime/MimePowersComponent.cs index e185756b79..9009120d14 100644 --- a/Content.Server/Abilities/Mime/MimePowersComponent.cs +++ b/Content.Server/Abilities/Mime/MimePowersComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Actions.ActionTypes; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Utility; @@ -48,16 +49,15 @@ namespace Content.Server.Abilities.Mime public bool ReadyToRepent = false; /// - /// Accumulator for when the mime breaks their vows + /// Time when the mime can repent their vow /// - - [DataField("accumulator")] - public float Accumulator = 0f; + [DataField("vowRepentTime", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan VowRepentTime = TimeSpan.Zero; /// /// How long it takes the mime to get their powers back - [DataField("vowCooldown")] + [DataField("vowCooldown", customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan VowCooldown = TimeSpan.FromMinutes(5); } } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index b3601fcdb6..22db7e5321 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Maps; using Content.Shared.MobState.Components; using Robust.Shared.Player; using Robust.Shared.Physics; +using Robust.Shared.Timing; namespace Content.Server.Abilities.Mime { @@ -18,6 +19,8 @@ namespace Content.Server.Abilities.Mime [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + public override void Initialize() { base.Initialize(); @@ -34,8 +37,7 @@ namespace Content.Server.Abilities.Mime if (!mime.VowBroken || mime.ReadyToRepent) continue; - mime.Accumulator += frameTime; - if (mime.Accumulator < mime.VowCooldown.TotalSeconds) + if (_timing.CurTime < mime.VowRepentTime) continue; mime.ReadyToRepent = true; @@ -101,6 +103,7 @@ namespace Content.Server.Abilities.Mime mimePowers.Enabled = false; mimePowers.VowBroken = true; + mimePowers.VowRepentTime = _timing.CurTime + mimePowers.VowCooldown; _alertsSystem.ClearAlert(uid, AlertType.VowOfSilence); _alertsSystem.ShowAlert(uid, AlertType.VowBroken); _actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallAction); @@ -123,7 +126,6 @@ namespace Content.Server.Abilities.Mime mimePowers.Enabled = true; mimePowers.ReadyToRepent = false; mimePowers.VowBroken = false; - mimePowers.Accumulator = 0f; _alertsSystem.ClearAlert(uid, AlertType.VowBroken); _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); _actionsSystem.AddAction(uid, mimePowers.InvisibleWallAction, uid); diff --git a/Content.Server/Afk/AFKSystem.cs b/Content.Server/Afk/AFKSystem.cs index f4226ee6e6..0f2bc52112 100644 --- a/Content.Server/Afk/AFKSystem.cs +++ b/Content.Server/Afk/AFKSystem.cs @@ -6,6 +6,7 @@ using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Player; +using Robust.Shared.Timing; namespace Content.Server.Afk; @@ -17,10 +18,11 @@ public sealed class AFKSystem : EntitySystem [Dependency] private readonly IAfkManager _afkManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly GameTicker _ticker = default!; private float _checkDelay; - private float _accumulator; + private TimeSpan _checkTime; private readonly HashSet _afkPlayers = new(); @@ -50,7 +52,6 @@ public sealed class AFKSystem : EntitySystem { base.Shutdown(); _afkPlayers.Clear(); - _accumulator = 0f; _playerManager.PlayerStatusChanged -= OnPlayerChange; _configManager.UnsubValueChanged(CCVars.AfkTime, SetAfkDelay); } @@ -62,16 +63,15 @@ public sealed class AFKSystem : EntitySystem if (_ticker.RunLevel != GameRunLevel.InRound) { _afkPlayers.Clear(); - _accumulator = 0f; + _checkTime = TimeSpan.Zero; return; } - _accumulator += frameTime; - // TODO: Should also listen to the input events for more accurate timings. - if (_accumulator < _checkDelay) return; + if (_timing.CurTime < _checkTime) + return; - _accumulator -= _checkDelay; + _checkTime = _timing.CurTime + TimeSpan.FromSeconds(_checkDelay); foreach (var session in Filter.GetAllPlayers()) { diff --git a/Content.Server/Atmos/Miasma/MiasmaSystem.cs b/Content.Server/Atmos/Miasma/MiasmaSystem.cs index 3dd465acef..514a85183e 100644 --- a/Content.Server/Atmos/Miasma/MiasmaSystem.cs +++ b/Content.Server/Atmos/Miasma/MiasmaSystem.cs @@ -11,6 +11,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.Atmos.Miasma { @@ -20,7 +21,9 @@ namespace Content.Server.Atmos.Miasma [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly SharedMobStateSystem _mobState = default!; + [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; /// System Variables @@ -62,11 +65,11 @@ namespace Content.Server.Atmos.Miasma /// /// - /// This ticks up to PoolRepickTime. - /// After that, it resets to 0. - /// Any infection will also reset it to 0. + /// The target time it waits until.. + /// After that, it resets current time + _poolRepickTime. + /// Any infection will also reset it to current time + _poolRepickTime. /// - private float _poolAccumulator = 0f; + private TimeSpan _diseaseTime = TimeSpan.FromMinutes(5); /// /// How long without an infection before we pick a new disease. @@ -77,29 +80,26 @@ namespace Content.Server.Atmos.Miasma { base.Update(frameTime); // Disease pool - _poolAccumulator += frameTime; - if (_poolAccumulator > _poolRepickTime.TotalSeconds) + if (_timing.CurTime >= _diseaseTime) { - _poolAccumulator = 0f; + _diseaseTime = _timing.CurTime + _poolRepickTime; _poolDisease = _random.Pick(MiasmaDiseasePool); } // Rotting - foreach (var (rotting, perishable) in EntityQuery()) + foreach (var (rotting, perishable, metadata) in EntityQuery()) { if (!perishable.Progressing) continue; - perishable.DeathAccumulator += frameTime; - if (perishable.DeathAccumulator < perishable.RotAfter.TotalSeconds) + if (!IsRotting(perishable, metadata)) continue; - perishable.RotAccumulator += frameTime; - if (perishable.RotAccumulator < _rotUpdateRate) // This is where it starts to get noticable on larger animals, no need to run every second + if (_timing.CurTime < perishable.RotNextUpdate) // This is where it starts to get noticable on larger animals, no need to run every second continue; - perishable.RotAccumulator -= _rotUpdateRate; + perishable.RotNextUpdate = _timing.CurTime + TimeSpan.FromSeconds(_rotUpdateRate); EnsureComp(perishable.Owner); @@ -152,8 +152,8 @@ namespace Content.Server.Atmos.Miasma RemComp(uid); if (TryComp(uid, out var perishable)) { - perishable.DeathAccumulator = 0; - perishable.RotAccumulator = 0; + perishable.TimeOfDeath = TimeSpan.Zero; + perishable.RotNextUpdate = TimeSpan.Zero; } } @@ -168,7 +168,20 @@ namespace Content.Server.Atmos.Miasma private void OnMobStateChanged(EntityUid uid, PerishableComponent component, MobStateChangedEvent args) { if (_mobState.IsDead(uid)) + { EnsureComp(uid); + component.TimeOfDeath = _timing.CurTime; + } + } + + /// + /// Has enough time passed for to start rotting? + /// + private bool IsRotting(PerishableComponent perishable, MetaDataComponent? metadata = null) + { + if (_timing.CurTime >= perishable.TimeOfDeath + perishable.RotAfter + _metaDataSystem.GetPauseTime(perishable.Owner, metadata)) + return true; + return false; } private void OnGibbed(EntityUid uid, PerishableComponent component, BeingGibbedEvent args) @@ -176,10 +189,10 @@ namespace Content.Server.Atmos.Miasma if (!TryComp(uid, out var physics)) return; - if (!component.Rotting) + if (!IsRotting(component)) return; - var molsToDump = (component.MolsPerSecondPerUnitMass * physics.FixturesMass) * component.DeathAccumulator; + var molsToDump = (component.MolsPerSecondPerUnitMass * physics.FixturesMass) * (float)(_timing.CurTime - component.TimeOfDeath).TotalSeconds; var transform = Transform(uid); var indices = _transformSystem.GetGridOrMapTilePosition(uid, transform); var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true); @@ -192,9 +205,10 @@ namespace Content.Server.Atmos.Miasma private void OnExamined(EntityUid uid, PerishableComponent component, ExaminedEvent args) { - if (!component.Rotting) + if (!IsRotting(component)) return; - var stage = component.DeathAccumulator / component.RotAfter.TotalSeconds; + + var stage = (_timing.CurTime - component.TimeOfDeath).TotalSeconds / component.RotAfter.TotalSeconds; var description = stage switch { >= 3 => "miasma-extremely-bloated", >= 2 => "miasma-bloated", @@ -254,7 +268,7 @@ namespace Content.Server.Atmos.Miasma perishable.Progressing = decompose; - if (!perishable.Rotting) + if (!IsRotting(perishable)) return; if (decompose) @@ -290,7 +304,7 @@ namespace Content.Server.Atmos.Miasma public string RequestPoolDisease() { // We reset the current time on this outbreak so people don't get unlucky at the transition time - _poolAccumulator = 0f; + _diseaseTime = _timing.CurTime + _poolRepickTime; return _poolDisease; } } diff --git a/Content.Server/Atmos/Miasma/PerishableComponent.cs b/Content.Server/Atmos/Miasma/PerishableComponent.cs index 0f46d68d9f..4c60f2a9f6 100644 --- a/Content.Server/Atmos/Miasma/PerishableComponent.cs +++ b/Content.Server/Atmos/Miasma/PerishableComponent.cs @@ -1,3 +1,5 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + namespace Content.Server.Atmos.Miasma { [RegisterComponent] @@ -16,22 +18,20 @@ namespace Content.Server.Atmos.Miasma /// /// How long this creature has been dead. /// - [DataField("deathAccumulator")] + [DataField("timeOfDeath", customTypeSerializer: typeof(TimeOffsetSerializer))] [ViewVariables(VVAccess.ReadWrite)] - public float DeathAccumulator = 0f; + public TimeSpan TimeOfDeath = TimeSpan.Zero; /// /// When DeathAccumulator is greater than this, start rotting. /// public TimeSpan RotAfter = TimeSpan.FromMinutes(5); - public bool Rotting => (DeathAccumulator > RotAfter.TotalSeconds); - /// - /// Gasses are released every second. + /// Gasses are released, this is when the next gas release update will be. /// - [DataField("rotAccumulator")] - public float RotAccumulator = 0f; + [DataField("rotNextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan RotNextUpdate = TimeSpan.Zero; /// /// How many moles of gas released per second, per unit of mass. diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index 8dec5153af..027e6609ba 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Damage; using Content.Shared.Emag.Systems; using Content.Server.Construction; using Robust.Shared.Prototypes; +using Robust.Shared.Timing; namespace Content.Server.Bed { @@ -26,6 +27,7 @@ namespace Content.Server.Bed [Dependency] private readonly SleepingSystem _sleepingSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { @@ -44,6 +46,7 @@ namespace Content.Server.Bed if (args.Buckling) { AddComp(uid); + component.NextHealTime = _timing.CurTime + TimeSpan.FromSeconds(component.HealTime); if (sleepAction != null) _actionsSystem.AddAction(args.BuckledEntity, new InstantAction(sleepAction), null); return; @@ -54,7 +57,6 @@ namespace Content.Server.Bed _sleepingSystem.TryWaking(args.BuckledEntity); RemComp(uid); - component.Accumulator = 0; } public override void Update(float frameTime) @@ -63,12 +65,10 @@ namespace Content.Server.Bed foreach (var (_, bedComponent, strapComponent) in EntityQuery()) { - bedComponent.Accumulator += frameTime; - - if (bedComponent.Accumulator < bedComponent.HealTime) + if (_timing.CurTime < bedComponent.NextHealTime) continue; - bedComponent.Accumulator -= bedComponent.HealTime; + bedComponent.NextHealTime += TimeSpan.FromSeconds(bedComponent.HealTime); if (strapComponent.BuckledEntities.Count == 0) continue; diff --git a/Content.Server/Bed/Components/HealOnBuckleComponent.cs b/Content.Server/Bed/Components/HealOnBuckleComponent.cs index 5e102c4e08..bab5d20282 100644 --- a/Content.Server/Bed/Components/HealOnBuckleComponent.cs +++ b/Content.Server/Bed/Components/HealOnBuckleComponent.cs @@ -15,6 +15,7 @@ namespace Content.Server.Bed.Components [DataField("sleepMultiplier")] public float SleepMultiplier = 3f; - public float Accumulator = 0f; //Time accumulated + + public TimeSpan NextHealTime = TimeSpan.Zero; //Next heal } } diff --git a/Content.Server/Movement/Systems/JetpackSystem.cs b/Content.Server/Movement/Systems/JetpackSystem.cs index f1e7a91aac..5b8ecd29f8 100644 --- a/Content.Server/Movement/Systems/JetpackSystem.cs +++ b/Content.Server/Movement/Systems/JetpackSystem.cs @@ -3,12 +3,14 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Robust.Shared.Collections; +using Robust.Shared.Timing; namespace Content.Server.Movement.Systems; public sealed class JetpackSystem : SharedJetpackSystem { [Dependency] private readonly GasTankSystem _gasTank = default!; + [Dependency] private readonly IGameTiming _timing = default!; private const float UpdateCooldown = 0.5f; @@ -25,10 +27,9 @@ public sealed class JetpackSystem : SharedJetpackSystem foreach (var (active, comp, gasTank) in EntityQuery()) { - active.Accumulator += frameTime; - if (active.Accumulator < UpdateCooldown) continue; + if (_timing.CurTime < active.TargetTime) continue; - active.Accumulator -= UpdateCooldown; + active.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(active.EffectCooldown); var air = _gasTank.RemoveAir(gasTank, comp.MoleUsage); if (air == null || !MathHelper.CloseTo(air.TotalMoles, comp.MoleUsage, 0.001f)) diff --git a/Content.Shared/Movement/Components/ActiveJetpackComponent.cs b/Content.Shared/Movement/Components/ActiveJetpackComponent.cs index b3318e23a0..5237205289 100644 --- a/Content.Shared/Movement/Components/ActiveJetpackComponent.cs +++ b/Content.Shared/Movement/Components/ActiveJetpackComponent.cs @@ -9,5 +9,5 @@ namespace Content.Shared.Movement.Components; public sealed class ActiveJetpackComponent : Component { public float EffectCooldown = 0.3f; - public float Accumulator = 0f; + public TimeSpan TargetTime = TimeSpan.Zero; }