]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Stasis bed cleanup and bugfixes. (#38762)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Sun, 6 Jul 2025 00:59:31 +0000 (20:59 -0400)
committerGitHub <noreply@github.com>
Sun, 6 Jul 2025 00:59:31 +0000 (02:59 +0200)
* Stasis bed sent to shed

* Code Review

* Code Review 2

21 files changed:
Content.Client/Bed/StasisBedSystem.cs [deleted file]
Content.Client/Body/Systems/MetabolizerSystem.cs [new file with mode: 0644]
Content.Server/Bed/BedSystem.cs
Content.Server/Bed/Components/StasisBedComponent.cs [deleted file]
Content.Server/Body/Components/MetabolizerComponent.cs
Content.Server/Body/Components/RespiratorComponent.cs
Content.Server/Body/Systems/MetabolizerSystem.cs
Content.Server/Body/Systems/RespiratorSystem.cs
Content.Server/Power/EntitySystems/PowerNetSystem.cs
Content.Shared/Bed/Components/StasisBedBuckledComponent.cs [new file with mode: 0644]
Content.Shared/Bed/Components/StasisBedComponent.cs [new file with mode: 0644]
Content.Shared/Bed/SharedBedSystem.cs
Content.Shared/Bed/StasisBedVisuals.cs [deleted file]
Content.Shared/Body/Components/BloodstreamComponent.cs
Content.Shared/Body/Components/StomachComponent.cs
Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs [deleted file]
Content.Shared/Body/Events/MetabolizerEvents.cs [new file with mode: 0644]
Content.Shared/Body/Systems/SharedBloodstreamSystem.cs
Content.Shared/Body/Systems/SharedMetabolizerSystem.cs [new file with mode: 0644]
Content.Shared/Body/Systems/StomachSystem.cs
Resources/Prototypes/Entities/Structures/Machines/stasisbed.yml

diff --git a/Content.Client/Bed/StasisBedSystem.cs b/Content.Client/Bed/StasisBedSystem.cs
deleted file mode 100644 (file)
index 6065ec8..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Shared.Bed;
-using Robust.Client.GameObjects;
-
-namespace Content.Client.Bed;
-
-public sealed class StasisBedSystem : VisualizerSystem<StasisBedVisualsComponent>
-{
-    protected override void OnAppearanceChange(EntityUid uid, StasisBedVisualsComponent component, ref AppearanceChangeEvent args)
-    {
-        if (args.Sprite != null
-            && AppearanceSystem.TryGetData<bool>(uid, StasisBedVisuals.IsOn, out var isOn, args.Component))
-        {
-            SpriteSystem.LayerSetVisible((uid, args.Sprite), StasisBedVisualLayers.IsOn, isOn);
-        }
-    }
-}
-
-public enum StasisBedVisualLayers : byte
-{
-    IsOn,
-}
diff --git a/Content.Client/Body/Systems/MetabolizerSystem.cs b/Content.Client/Body/Systems/MetabolizerSystem.cs
new file mode 100644 (file)
index 0000000..2a0ba46
--- /dev/null
@@ -0,0 +1,6 @@
+using Content.Shared.Body.Systems;
+
+namespace Content.Client.Body.Systems;
+
+/// <inheritdoc/>
+public sealed class MetabolizerSystem : SharedMetabolizerSystem;
index 91b31c782be718d865e63f6463351c15f795ee3a..8cfb28acb61b9f7bc2975a3163f399280c7c13c7 100644 (file)
@@ -1,23 +1,15 @@
-using Content.Server.Bed.Components;
-using Content.Server.Power.EntitySystems;
 using Content.Shared.Bed;
 using Content.Shared.Bed.Components;
 using Content.Shared.Bed.Sleep;
-using Content.Shared.Body.Components;
-using Content.Shared.Body.Events;
 using Content.Shared.Buckle.Components;
 using Content.Shared.Damage;
-using Content.Shared.Emag.Systems;
 using Content.Shared.Mobs.Systems;
-using Content.Shared.Power;
 
 namespace Content.Server.Bed
 {
     public sealed class BedSystem : SharedBedSystem
     {
         [Dependency] private readonly DamageableSystem _damageableSystem = default!;
-        [Dependency] private readonly EmagSystem _emag = default!;
-        [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
         [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
 
         private EntityQuery<SleepingComponent> _sleepingQuery;
@@ -27,11 +19,6 @@ namespace Content.Server.Bed
             base.Initialize();
 
             _sleepingQuery = GetEntityQuery<SleepingComponent>();
-
-            SubscribeLocalEvent<StasisBedComponent, StrappedEvent>(OnStasisStrapped);
-            SubscribeLocalEvent<StasisBedComponent, UnstrappedEvent>(OnStasisUnstrapped);
-            SubscribeLocalEvent<StasisBedComponent, PowerChangedEvent>(OnPowerChanged);
-            SubscribeLocalEvent<StasisBedComponent, GotEmaggedEvent>(OnEmagged);
         }
 
         public override void Update(float frameTime)
@@ -63,61 +50,5 @@ namespace Content.Server.Bed
                 }
             }
         }
-
-        private void UpdateAppearance(EntityUid uid, bool isOn)
-        {
-            _appearance.SetData(uid, StasisBedVisuals.IsOn, isOn);
-        }
-
-        private void OnStasisStrapped(Entity<StasisBedComponent> bed, ref StrappedEvent args)
-        {
-            if (!HasComp<BodyComponent>(args.Buckle) || !this.IsPowered(bed, EntityManager))
-                return;
-
-            var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.Buckle, bed.Comp.Multiplier, true);
-            RaiseLocalEvent(args.Buckle, ref metabolicEvent);
-        }
-
-        private void OnStasisUnstrapped(Entity<StasisBedComponent> bed, ref UnstrappedEvent args)
-        {
-            if (!HasComp<BodyComponent>(args.Buckle) || !this.IsPowered(bed, EntityManager))
-                return;
-
-            var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.Buckle, bed.Comp.Multiplier, false);
-            RaiseLocalEvent(args.Buckle, ref metabolicEvent);
-        }
-
-        private void OnPowerChanged(EntityUid uid, StasisBedComponent component, ref PowerChangedEvent args)
-        {
-            UpdateAppearance(uid, args.Powered);
-            UpdateMetabolisms(uid, component, args.Powered);
-        }
-
-        private void OnEmagged(EntityUid uid, StasisBedComponent component, ref GotEmaggedEvent args)
-        {
-            if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
-                return;
-
-            if (_emag.CheckFlag(uid, EmagType.Interaction))
-                return;
-
-            // Reset any metabolisms first so they receive the multiplier correctly
-            UpdateMetabolisms(uid, component, false);
-            component.Multiplier = 1 / component.Multiplier;
-            UpdateMetabolisms(uid, component, true);
-            args.Handled = true;
-        }
-
-        private void UpdateMetabolisms(EntityUid uid, StasisBedComponent component, bool shouldApply)
-        {
-            if (!TryComp<StrapComponent>(uid, out var strap) || strap.BuckledEntities.Count == 0)
-                return;
-
-            foreach (var buckledEntity in strap.BuckledEntities)
-            {
-                var metabolicEvent = new ApplyMetabolicMultiplierEvent(buckledEntity, component.Multiplier, shouldApply);
-                RaiseLocalEvent(buckledEntity, ref metabolicEvent);
-            }
-        }
     }
 }
diff --git a/Content.Server/Bed/Components/StasisBedComponent.cs b/Content.Server/Bed/Components/StasisBedComponent.cs
deleted file mode 100644 (file)
index 160bd0a..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Content.Server.Bed.Components
-{
-    [RegisterComponent]
-    public sealed partial class StasisBedComponent : Component
-    {
-        /// <summary>
-        /// What the metabolic update rate will be multiplied by (higher = slower metabolism)
-        /// </summary>
-        [ViewVariables(VVAccess.ReadOnly)] // Writing is is not supported. ApplyMetabolicMultiplierEvent needs to be refactored first
-        [DataField]
-        public float Multiplier = 10f;
-    }
-}
index 3699267ebf299e54caa659c9728230e3e58714d9..5821b0d9447d7cb01cbf6609b773514f21012521 100644 (file)
@@ -26,6 +26,18 @@ namespace Content.Server.Body.Components
         [DataField]
         public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
 
+        /// <summary>
+        /// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier.
+        /// </summary>
+        [DataField]
+        public float UpdateIntervalMultiplier = 1f;
+
+        /// <summary>
+        /// Adjusted update interval based off of the multiplier value.
+        /// </summary>
+        [ViewVariables]
+        public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
+
         /// <summary>
         ///     From which solution will this metabolizer attempt to metabolize chemicals
         /// </summary>
@@ -46,14 +58,14 @@ namespace Content.Server.Body.Components
         /// </summary>
         [DataField]
         [Access(typeof(MetabolizerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
-        public HashSet<ProtoId<MetabolizerTypePrototype>>? MetabolizerTypes = null;
+        public HashSet<ProtoId<MetabolizerTypePrototype>>? MetabolizerTypes;
 
         /// <summary>
         ///     Should this metabolizer remove chemicals that have no metabolisms defined?
         ///     As a stop-gap, basically.
         /// </summary>
         [DataField]
-        public bool RemoveEmpty = false;
+        public bool RemoveEmpty;
 
         /// <summary>
         ///     How many reagents can this metabolizer process at once?
@@ -67,7 +79,7 @@ namespace Content.Server.Body.Components
         ///     A list of metabolism groups that this metabolizer will act on, in order of precedence.
         /// </summary>
         [DataField("groups")]
-        public List<MetabolismGroupEntry>? MetabolismGroups = default!;
+        public List<MetabolismGroupEntry>? MetabolismGroups;
     }
 
     /// <summary>
@@ -78,7 +90,7 @@ namespace Content.Server.Body.Components
     public sealed partial class MetabolismGroupEntry
     {
         [DataField(required: true)]
-        public ProtoId<MetabolismGroupPrototype> Id = default!;
+        public ProtoId<MetabolismGroupPrototype> Id;
 
         [DataField("rateModifier")]
         public FixedPoint2 MetabolismRateModifier = 1.0;
index 19585e9f0016ab367762d12a82801cf911598a8a..24f4ae85e1bad0f47180213713097396e3520025 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Body.Systems;
-using Content.Shared.Alert;
 using Content.Shared.Atmos;
 using Content.Shared.Chat.Prototypes;
 using Content.Shared.Damage;
@@ -8,7 +7,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Server.Body.Components
 {
-    [RegisterComponent, Access(typeof(RespiratorSystem))]
+    [RegisterComponent, Access(typeof(RespiratorSystem)), AutoGenerateComponentPause]
     public sealed partial class RespiratorComponent : Component
     {
         /// <summary>
@@ -36,7 +35,7 @@ namespace Content.Server.Body.Components
         /// <summary>
         ///     The next time that this body will inhale or exhale.
         /// </summary>
-        [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+        [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
         public TimeSpan NextUpdate;
 
         /// <summary>
@@ -46,6 +45,18 @@ namespace Content.Server.Body.Components
         [DataField]
         public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
 
+        /// <summary>
+        /// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier.
+        /// </summary>
+        [DataField]
+        public float UpdateIntervalMultiplier = 1f;
+
+        /// <summary>
+        /// Adjusted update interval based off of the multiplier value.
+        /// </summary>
+        [ViewVariables]
+        public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
+
         /// <summary>
         ///     Saturation level. Reduced by UpdateInterval each tick.
         ///     Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
index 5577b42a069334adc9db087f509a4c3d8b7fe41a..c59f87f576d21d644c3817445d9a4f0d98a68dc7 100644 (file)
@@ -2,6 +2,7 @@ using Content.Server.Body.Components;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Body.Events;
 using Content.Shared.Body.Organ;
+using Content.Shared.Body.Systems;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Components.SolutionManager;
 using Content.Shared.Chemistry.EntitySystems;
@@ -18,7 +19,8 @@ using Robust.Shared.Timing;
 
 namespace Content.Server.Body.Systems
 {
-    public sealed class MetabolizerSystem : EntitySystem
+    /// <inheritdoc/>
+    public sealed class MetabolizerSystem : SharedMetabolizerSystem
     {
         [Dependency] private readonly IGameTiming _gameTiming = default!;
         [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -45,7 +47,7 @@ namespace Content.Server.Body.Systems
 
         private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
         {
-            ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
+            ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
         }
 
         private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
@@ -65,20 +67,9 @@ namespace Content.Server.Body.Systems
             }
         }
 
-        private void OnApplyMetabolicMultiplier(
-            Entity<MetabolizerComponent> ent,
-            ref ApplyMetabolicMultiplierEvent args)
+        private void OnApplyMetabolicMultiplier(Entity<MetabolizerComponent> ent, ref ApplyMetabolicMultiplierEvent args)
         {
-            // TODO REFACTOR THIS
-            // This will slowly drift over time due to floating point errors.
-            // Instead, raise an event with the base rates and allow modifiers to get applied to it.
-            if (args.Apply)
-            {
-                ent.Comp.UpdateInterval *= args.Multiplier;
-                return;
-            }
-
-            ent.Comp.UpdateInterval /= args.Multiplier;
+            ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
         }
 
         public override void Update(float frameTime)
@@ -99,7 +90,7 @@ namespace Content.Server.Body.Systems
                 if (_gameTiming.CurTime < metab.NextUpdate)
                     continue;
 
-                metab.NextUpdate += metab.UpdateInterval;
+                metab.NextUpdate += metab.AdjustedUpdateInterval;
                 TryMetabolize((uid, metab));
             }
         }
index bc571087206a4e0248444a6c9774ced019343612..4e21236a778d951242577cf916da80a3d5651ca0 100644 (file)
@@ -48,7 +48,6 @@ public sealed class RespiratorSystem : EntitySystem
         // We want to process lung reagents before we inhale new reagents.
         UpdatesAfter.Add(typeof(MetabolizerSystem));
         SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<RespiratorComponent, EntityUnpausedEvent>(OnUnpaused);
         SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
         SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled);
         SubscribeLocalEvent<BodyComponent, ExhaledGasEvent>(OnGasExhaled);
@@ -56,25 +55,20 @@ public sealed class RespiratorSystem : EntitySystem
 
     private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
     {
-        ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
-    }
-
-    private void OnUnpaused(Entity<RespiratorComponent> ent, ref EntityUnpausedEvent args)
-    {
-        ent.Comp.NextUpdate += args.PausedTime;
+        ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
     }
 
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
 
-        var query = EntityQueryEnumerator<RespiratorComponent, BodyComponent>();
-        while (query.MoveNext(out var uid, out var respirator, out var body))
+        var query = EntityQueryEnumerator<RespiratorComponent>();
+        while (query.MoveNext(out var uid, out var respirator))
         {
             if (_gameTiming.CurTime < respirator.NextUpdate)
                 continue;
 
-            respirator.NextUpdate += respirator.UpdateInterval;
+            respirator.NextUpdate += respirator.AdjustedUpdateInterval;
 
             if (_mobState.IsDead(uid))
                 continue;
@@ -396,27 +390,9 @@ public sealed class RespiratorSystem : EntitySystem
             Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation);
     }
 
-    private void OnApplyMetabolicMultiplier(
-        Entity<RespiratorComponent> ent,
-        ref ApplyMetabolicMultiplierEvent args)
+    private void OnApplyMetabolicMultiplier(Entity<RespiratorComponent> ent, ref ApplyMetabolicMultiplierEvent args)
     {
-        // TODO REFACTOR THIS
-        // This will slowly drift over time due to floating point errors.
-        // Instead, raise an event with the base rates and allow modifiers to get applied to it.
-        if (args.Apply)
-        {
-            ent.Comp.UpdateInterval *= args.Multiplier;
-            ent.Comp.Saturation *= args.Multiplier;
-            ent.Comp.MaxSaturation *= args.Multiplier;
-            ent.Comp.MinSaturation *= args.Multiplier;
-            return;
-        }
-
-        // This way we don't have to worry about it breaking if the stasis bed component is destroyed
-        ent.Comp.UpdateInterval /= args.Multiplier;
-        ent.Comp.Saturation /= args.Multiplier;
-        ent.Comp.MaxSaturation /= args.Multiplier;
-        ent.Comp.MinSaturation /= args.Multiplier;
+        ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
     }
 
     private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
index 9dc0c2c498eacb6bde09f07bbecedff09be3df5c..1262e231d755d2675244965c8cbfe3a7a8150e3d 100644 (file)
@@ -45,6 +45,7 @@ namespace Content.Server.Power.EntitySystems
             UpdatesAfter.Add(typeof(NodeGroupSystem));
             _solver = new(_cfg.GetCVar(CCVars.DebugPow3rDisableParallel));
 
+            SubscribeLocalEvent<ApcPowerReceiverComponent, MapInitEvent>(ApcPowerReceiverMapInit);
             SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentInit>(ApcPowerReceiverInit);
             SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentShutdown>(ApcPowerReceiverShutdown);
             SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentRemove>(ApcPowerReceiverRemove);
@@ -74,6 +75,11 @@ namespace Content.Server.Power.EntitySystems
             _solver = new(val);
         }
 
+        private void ApcPowerReceiverMapInit(Entity<ApcPowerReceiverComponent> ent, ref MapInitEvent args)
+        {
+            _appearance.SetData(ent, PowerDeviceVisuals.Powered, ent.Comp.Powered);
+        }
+
         private void ApcPowerReceiverInit(EntityUid uid, ApcPowerReceiverComponent component, ComponentInit args)
         {
             AllocLoad(component.NetworkLoad);
diff --git a/Content.Shared/Bed/Components/StasisBedBuckledComponent.cs b/Content.Shared/Bed/Components/StasisBedBuckledComponent.cs
new file mode 100644 (file)
index 0000000..3a1c991
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Bed.Components;
+
+/// <summary>
+/// Tracking component added to entities buckled to stasis beds.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedBedSystem))]
+public sealed partial class StasisBedBuckledComponent : Component;
diff --git a/Content.Shared/Bed/Components/StasisBedComponent.cs b/Content.Shared/Bed/Components/StasisBedComponent.cs
new file mode 100644 (file)
index 0000000..9793626
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Buckle.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Bed.Components;
+
+/// <summary>
+/// A <see cref="StrapComponent"/> that modifies a strapped entity's metabolic rate by the given multiplier
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedBedSystem))]
+public sealed partial class StasisBedComponent : Component
+{
+    /// <summary>
+    /// What the metabolic update rate will be multiplied by (higher = slower metabolism)
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Multiplier = 10f;
+}
index cdf3ffcbd40bd438894a341011a6c4955bdb22b0..7e798111cfd87f16d1c49a6413fd05d6b89b0df7 100644 (file)
@@ -1,7 +1,12 @@
 using Content.Shared.Actions;
 using Content.Shared.Bed.Components;
 using Content.Shared.Bed.Sleep;
+using Content.Shared.Body.Events;
+using Content.Shared.Body.Systems;
 using Content.Shared.Buckle.Components;
+using Content.Shared.Emag.Systems;
+using Content.Shared.Power;
+using Content.Shared.Power.EntitySystems;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
@@ -12,6 +17,9 @@ public abstract class SharedBedSystem : EntitySystem
     [Dependency] protected readonly IGameTiming Timing = default!;
     [Dependency] private readonly ActionContainerSystem _actConts = default!;
     [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+    [Dependency] private readonly EmagSystem _emag = default!;
+    [Dependency] private readonly SharedMetabolizerSystem _metabolizer = default!;
+    [Dependency] private readonly SharedPowerReceiverSystem _powerReceiver = default!;
     [Dependency] private readonly SleepingSystem _sleepingSystem = default!;
 
     public override void Initialize()
@@ -21,6 +29,12 @@ public abstract class SharedBedSystem : EntitySystem
         SubscribeLocalEvent<HealOnBuckleComponent, MapInitEvent>(OnHealMapInit);
         SubscribeLocalEvent<HealOnBuckleComponent, StrappedEvent>(OnStrapped);
         SubscribeLocalEvent<HealOnBuckleComponent, UnstrappedEvent>(OnUnstrapped);
+
+        SubscribeLocalEvent<StasisBedComponent, StrappedEvent>(OnStasisStrapped);
+        SubscribeLocalEvent<StasisBedComponent, UnstrappedEvent>(OnStasisUnstrapped);
+        SubscribeLocalEvent<StasisBedComponent, GotEmaggedEvent>(OnStasisEmagged);
+        SubscribeLocalEvent<StasisBedComponent, PowerChangedEvent>(OnPowerChanged);
+        SubscribeLocalEvent<StasisBedBuckledComponent, GetMetabolicMultiplierEvent>(OnStasisGetMetabolicMultiplier);
     }
 
     private void OnHealMapInit(Entity<HealOnBuckleComponent> ent, ref MapInitEvent args)
@@ -46,4 +60,61 @@ public abstract class SharedBedSystem : EntitySystem
         _sleepingSystem.TryWaking(args.Buckle.Owner);
         RemComp<HealOnBuckleHealingComponent>(bed);
     }
+
+    private void OnStasisStrapped(Entity<StasisBedComponent> ent, ref StrappedEvent args)
+    {
+        EnsureComp<StasisBedBuckledComponent>(args.Buckle);
+        _metabolizer.UpdateMetabolicMultiplier(args.Buckle);
+    }
+
+    private void OnStasisUnstrapped(Entity<StasisBedComponent> ent, ref UnstrappedEvent args)
+    {
+        RemComp<StasisBedBuckledComponent>(ent);
+        _metabolizer.UpdateMetabolicMultiplier(args.Buckle);
+    }
+
+    private void OnStasisEmagged(Entity<StasisBedComponent> ent, ref GotEmaggedEvent args)
+    {
+        if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
+            return;
+
+        if (_emag.CheckFlag(ent, EmagType.Interaction))
+            return;
+
+        ent.Comp.Multiplier = 1f / ent.Comp.Multiplier;
+        UpdateMetabolisms(ent.Owner);
+        Dirty(ent);
+
+        args.Handled = true;
+    }
+
+    private void OnPowerChanged(Entity<StasisBedComponent> ent, ref PowerChangedEvent args)
+    {
+        UpdateMetabolisms(ent.Owner);
+    }
+
+    private void OnStasisGetMetabolicMultiplier(Entity<StasisBedBuckledComponent> ent, ref GetMetabolicMultiplierEvent args)
+    {
+        if (!TryComp<BuckleComponent>(ent, out var buckle) || buckle.BuckledTo is not { } buckledTo)
+            return;
+
+        if (!TryComp<StasisBedComponent>(buckledTo, out var stasis))
+            return;
+
+        if (!_powerReceiver.IsPowered(buckledTo))
+            return;
+
+        args.Multiplier *= stasis.Multiplier;
+    }
+
+    protected void UpdateMetabolisms(Entity<StrapComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp, false))
+            return;
+
+        foreach (var buckledEntity in ent.Comp.BuckledEntities)
+        {
+            _metabolizer.UpdateMetabolicMultiplier(buckledEntity);
+        }
+    }
 }
diff --git a/Content.Shared/Bed/StasisBedVisuals.cs b/Content.Shared/Bed/StasisBedVisuals.cs
deleted file mode 100644 (file)
index cf7bc06..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Bed
-{
-    [Serializable, NetSerializable]
-    public enum StasisBedVisuals : byte
-    {
-        IsOn,
-    }
-}
index 7997d92066e0404ab049dbf46b7a2fec654c7481..013993226214573137280d4b8932424a01738701 100644 (file)
@@ -37,6 +37,18 @@ public sealed partial class BloodstreamComponent : Component
     [DataField, AutoNetworkedField]
     public TimeSpan UpdateInterval = TimeSpan.FromSeconds(3);
 
+    /// <summary>
+    /// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float UpdateIntervalMultiplier = 1f;
+
+    /// <summary>
+    /// Adjusted update interval based off of the multiplier value.
+    /// </summary>
+    [ViewVariables]
+    public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
+
     /// <summary>
     /// How much is this entity currently bleeding?
     /// Higher numbers mean more blood lost every tick.
index b76dff0902997ea342abed9195a9c5d28c4dab64..1b2f58782409e6e681dc024f0c9b21d28b7adc2c 100644 (file)
@@ -23,6 +23,18 @@ namespace Content.Shared.Body.Components
         [DataField]
         public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
 
+        /// <summary>
+        /// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier.
+        /// </summary>
+        [DataField]
+        public float UpdateIntervalMultiplier = 1f;
+
+        /// <summary>
+        /// Adjusted update interval based off of the multiplier value.
+        /// </summary>
+        [ViewVariables]
+        public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
+
         /// <summary>
         ///     The solution inside of this stomach this transfers reagents to the body.
         /// </summary>
diff --git a/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs b/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs
deleted file mode 100644 (file)
index 1a7b589..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-namespace Content.Shared.Body.Events;
-
-// TODO REFACTOR THIS
-// This will cause rates to slowly drift over time due to floating point errors.
-// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
-[ByRefEvent]
-public readonly record struct ApplyMetabolicMultiplierEvent(
-    EntityUid Uid,
-    float Multiplier,
-    bool Apply)
-{
-    /// <summary>
-    /// The entity whose metabolism is being modified.
-    /// </summary>
-    public readonly EntityUid Uid = Uid;
-
-    /// <summary>
-    /// What the metabolism's update rate will be multiplied by.
-    /// </summary>
-    public readonly float Multiplier = Multiplier;
-
-    /// <summary>
-    /// If true, apply the multiplier. If false, revert it.
-    /// </summary>
-    public readonly bool Apply = Apply;
-}
diff --git a/Content.Shared/Body/Events/MetabolizerEvents.cs b/Content.Shared/Body/Events/MetabolizerEvents.cs
new file mode 100644 (file)
index 0000000..f001c83
--- /dev/null
@@ -0,0 +1,26 @@
+namespace Content.Shared.Body.Events;
+
+/// <summary>
+/// Raised on an entity to determine their metabolic multiplier.
+/// </summary>
+[ByRefEvent]
+public record struct GetMetabolicMultiplierEvent()
+{
+    /// <summary>
+    /// What the metabolism's update rate will be multiplied by.
+    /// </summary>
+    public float Multiplier = 1f;
+}
+
+/// <summary>
+/// Raised on an entity to apply their metabolic multiplier to relevant systems.
+/// Note that you should be storing this value as to not accrue precision errors when it's modified.
+/// </summary>
+[ByRefEvent]
+public readonly record struct ApplyMetabolicMultiplierEvent(float Multiplier)
+{
+    /// <summary>
+    /// What the metabolism's update rate will be multiplied by.
+    /// </summary>
+    public readonly float Multiplier = Multiplier;
+}
index 3b1674cf3c1dba5c31104d7e93483629e048b99b..b5d510018996c8cba91a4ea0ccd528edf414d6d6 100644 (file)
@@ -64,7 +64,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
             if (curTime < bloodstream.NextUpdate)
                 continue;
 
-            bloodstream.NextUpdate += bloodstream.UpdateInterval;
+            bloodstream.NextUpdate += bloodstream.AdjustedUpdateInterval;
             DirtyField(uid, bloodstream, nameof(BloodstreamComponent.NextUpdate)); // needs to be dirtied on the client so it can be rerolled during prediction
 
             if (!SolutionContainer.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution))
@@ -101,12 +101,12 @@ public abstract class SharedBloodstreamSystem : EntitySystem
                 // Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out
                 _drunkSystem.TryApplyDrunkenness(
                     uid,
-                    (float)bloodstream.UpdateInterval.TotalSeconds * 2,
+                    (float)bloodstream.AdjustedUpdateInterval.TotalSeconds * 2,
                     applySlur: false);
-                _stutteringSystem.DoStutter(uid, bloodstream.UpdateInterval * 2, refresh: false);
+                _stutteringSystem.DoStutter(uid, bloodstream.AdjustedUpdateInterval * 2, refresh: false);
 
                 // storing the drunk and stutter time so we can remove it independently from other effects additions
-                bloodstream.StatusTime += bloodstream.UpdateInterval * 2;
+                bloodstream.StatusTime += bloodstream.AdjustedUpdateInterval * 2;
                 DirtyField(uid, bloodstream, nameof(BloodstreamComponent.StatusTime));
             }
             else if (!_mobStateSystem.IsDead(uid))
@@ -129,7 +129,7 @@ public abstract class SharedBloodstreamSystem : EntitySystem
 
     private void OnMapInit(Entity<BloodstreamComponent> ent, ref MapInitEvent args)
     {
-        ent.Comp.NextUpdate = _timing.CurTime + ent.Comp.UpdateInterval;
+        ent.Comp.NextUpdate = _timing.CurTime + ent.Comp.AdjustedUpdateInterval;
         DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.NextUpdate));
     }
 
@@ -289,15 +289,8 @@ public abstract class SharedBloodstreamSystem : EntitySystem
 
     private void OnApplyMetabolicMultiplier(Entity<BloodstreamComponent> ent, ref ApplyMetabolicMultiplierEvent args)
     {
-        // TODO REFACTOR THIS
-        // This will slowly drift over time due to floating point errors.
-        // Instead, raise an event with the base rates and allow modifiers to get applied to it.
-        if (args.Apply)
-            ent.Comp.UpdateInterval *= args.Multiplier;
-        else
-            ent.Comp.UpdateInterval /= args.Multiplier;
-
-        DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.UpdateInterval));
+        ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
+        DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.UpdateIntervalMultiplier));
     }
 
     private void OnRejuvenate(Entity<BloodstreamComponent> ent, ref RejuvenateEvent args)
diff --git a/Content.Shared/Body/Systems/SharedMetabolizerSystem.cs b/Content.Shared/Body/Systems/SharedMetabolizerSystem.cs
new file mode 100644 (file)
index 0000000..24ab438
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Shared.Body.Events;
+
+namespace Content.Shared.Body.Systems;
+
+public abstract class SharedMetabolizerSystem : EntitySystem
+{
+    /// <summary>
+    /// Updates the metabolic rate multiplier for a given entity,
+    /// raising both <see cref="GetMetabolicMultiplierEvent"/> to determine what the multiplier is and <see cref="ApplyMetabolicMultiplierEvent"/> to update relevant components.
+    /// </summary>
+    /// <param name="uid"></param>
+    public void UpdateMetabolicMultiplier(EntityUid uid)
+    {
+        var getEv = new GetMetabolicMultiplierEvent();
+        RaiseLocalEvent(uid, ref getEv);
+
+        var applyEv = new ApplyMetabolicMultiplierEvent(getEv.Multiplier);
+        RaiseLocalEvent(uid, ref applyEv);
+    }
+}
index 8b2df453a02f16dbf5d31c4a6233fc83641cbc4f..3d8647b14d98ffc163303789b2f724436954c197 100644 (file)
@@ -27,7 +27,7 @@ namespace Content.Shared.Body.Systems
 
         private void OnMapInit(Entity<StomachComponent> ent, ref MapInitEvent args)
         {
-            ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
+            ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
         }
 
         private void OnUnpaused(Entity<StomachComponent> ent, ref EntityUnpausedEvent args)
@@ -53,7 +53,7 @@ namespace Content.Shared.Body.Systems
                 if (_gameTiming.CurTime < stomach.NextUpdate)
                     continue;
 
-                stomach.NextUpdate += stomach.UpdateInterval;
+                stomach.NextUpdate += stomach.AdjustedUpdateInterval;
 
                 // Get our solutions
                 if (!_solutionContainerSystem.ResolveSolution((uid, sol), DefaultSolutionName, ref stomach.Solution, out var stomachSolution))
@@ -67,7 +67,7 @@ namespace Content.Shared.Body.Systems
                 var queue = new RemQueue<StomachComponent.ReagentDelta>();
                 foreach (var delta in stomach.ReagentDeltas)
                 {
-                    delta.Increment(stomach.UpdateInterval);
+                    delta.Increment(stomach.AdjustedUpdateInterval);
                     if (delta.Lifetime > stomach.DigestionDelay)
                     {
                         if (stomachSolution.TryGetReagent(delta.ReagentQuantity.Reagent, out var reagent))
@@ -95,18 +95,9 @@ namespace Content.Shared.Body.Systems
             }
         }
 
-        private void OnApplyMetabolicMultiplier(
-            Entity<StomachComponent> ent,
-            ref ApplyMetabolicMultiplierEvent args)
+        private void OnApplyMetabolicMultiplier(Entity<StomachComponent> ent, ref ApplyMetabolicMultiplierEvent args)
         {
-            if (args.Apply)
-            {
-                ent.Comp.UpdateInterval *= args.Multiplier;
-                return;
-            }
-
-            // This way we don't have to worry about it breaking if the stasis bed component is destroyed
-            ent.Comp.UpdateInterval /= args.Multiplier;
+            ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
         }
 
         public bool CanTransferSolution(
index 4dd1197593cffa5a6e97f000381d776a7e0dbc30..999cd7bf2ac9bf95ef75df5f94fc20334b312e04 100644 (file)
     - state: icon
     - state: unlit
       shader: unshaded
-      map: ["enum.StasisBedVisualLayers.IsOn"]
-  - type: StasisBedVisuals
+      map: ["unlit"]
+  - type: GenericVisualizer
+    visuals:
+      enum.PowerDeviceVisuals.Powered:
+        unlit:
+          True: { visible: true }
+          False: { visible: false }
   - type: Appearance
   - type: ApcPowerReceiver
     powerLoad: 1000