From ab201b6e82cef998eabf69af6fa5a0dbfe559c65 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sat, 5 Jul 2025 20:59:31 -0400 Subject: [PATCH] Stasis bed cleanup and bugfixes. (#38762) * Stasis bed sent to shed * Code Review * Code Review 2 --- Content.Client/Bed/StasisBedSystem.cs | 21 ------ .../Body/Systems/MetabolizerSystem.cs | 6 ++ Content.Server/Bed/BedSystem.cs | 69 ------------------ .../Bed/Components/StasisBedComponent.cs | 13 ---- .../Body/Components/MetabolizerComponent.cs | 20 ++++-- .../Body/Components/RespiratorComponent.cs | 17 ++++- .../Body/Systems/MetabolizerSystem.cs | 23 ++---- .../Body/Systems/RespiratorSystem.cs | 36 ++-------- .../Power/EntitySystems/PowerNetSystem.cs | 6 ++ .../Components/StasisBedBuckledComponent.cs | 10 +++ .../Bed/Components/StasisBedComponent.cs | 18 +++++ Content.Shared/Bed/SharedBedSystem.cs | 71 +++++++++++++++++++ Content.Shared/Bed/StasisBedVisuals.cs | 10 --- .../Body/Components/BloodstreamComponent.cs | 12 ++++ .../Body/Components/StomachComponent.cs | 12 ++++ .../Events/ApplyMetabolicMultiplierEvent.cs | 26 ------- .../Body/Events/MetabolizerEvents.cs | 26 +++++++ .../Body/Systems/SharedBloodstreamSystem.cs | 21 ++---- .../Body/Systems/SharedMetabolizerSystem.cs | 20 ++++++ Content.Shared/Body/Systems/StomachSystem.cs | 19 ++--- .../Structures/Machines/stasisbed.yml | 9 ++- 21 files changed, 243 insertions(+), 222 deletions(-) delete mode 100644 Content.Client/Bed/StasisBedSystem.cs create mode 100644 Content.Client/Body/Systems/MetabolizerSystem.cs delete mode 100644 Content.Server/Bed/Components/StasisBedComponent.cs create mode 100644 Content.Shared/Bed/Components/StasisBedBuckledComponent.cs create mode 100644 Content.Shared/Bed/Components/StasisBedComponent.cs delete mode 100644 Content.Shared/Bed/StasisBedVisuals.cs delete mode 100644 Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs create mode 100644 Content.Shared/Body/Events/MetabolizerEvents.cs create mode 100644 Content.Shared/Body/Systems/SharedMetabolizerSystem.cs diff --git a/Content.Client/Bed/StasisBedSystem.cs b/Content.Client/Bed/StasisBedSystem.cs deleted file mode 100644 index 6065ec839f..0000000000 --- a/Content.Client/Bed/StasisBedSystem.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared.Bed; -using Robust.Client.GameObjects; - -namespace Content.Client.Bed; - -public sealed class StasisBedSystem : VisualizerSystem -{ - protected override void OnAppearanceChange(EntityUid uid, StasisBedVisualsComponent component, ref AppearanceChangeEvent args) - { - if (args.Sprite != null - && AppearanceSystem.TryGetData(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 index 0000000000..2a0ba4606d --- /dev/null +++ b/Content.Client/Body/Systems/MetabolizerSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared.Body.Systems; + +namespace Content.Client.Body.Systems; + +/// +public sealed class MetabolizerSystem : SharedMetabolizerSystem; diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index 91b31c782b..8cfb28acb6 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -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 _sleepingQuery; @@ -27,11 +19,6 @@ namespace Content.Server.Bed base.Initialize(); _sleepingQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnStasisStrapped); - SubscribeLocalEvent(OnStasisUnstrapped); - SubscribeLocalEvent(OnPowerChanged); - SubscribeLocalEvent(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 bed, ref StrappedEvent args) - { - if (!HasComp(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 bed, ref UnstrappedEvent args) - { - if (!HasComp(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(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 index 160bd0ac67..0000000000 --- a/Content.Server/Bed/Components/StasisBedComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Content.Server.Bed.Components -{ - [RegisterComponent] - public sealed partial class StasisBedComponent : Component - { - /// - /// What the metabolic update rate will be multiplied by (higher = slower metabolism) - /// - [ViewVariables(VVAccess.ReadOnly)] // Writing is is not supported. ApplyMetabolicMultiplierEvent needs to be refactored first - [DataField] - public float Multiplier = 10f; - } -} diff --git a/Content.Server/Body/Components/MetabolizerComponent.cs b/Content.Server/Body/Components/MetabolizerComponent.cs index 3699267ebf..5821b0d944 100644 --- a/Content.Server/Body/Components/MetabolizerComponent.cs +++ b/Content.Server/Body/Components/MetabolizerComponent.cs @@ -26,6 +26,18 @@ namespace Content.Server.Body.Components [DataField] public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1); + /// + /// Multiplier applied to for adjusting based on metabolic rate multiplier. + /// + [DataField] + public float UpdateIntervalMultiplier = 1f; + + /// + /// Adjusted update interval based off of the multiplier value. + /// + [ViewVariables] + public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier; + /// /// From which solution will this metabolizer attempt to metabolize chemicals /// @@ -46,14 +58,14 @@ namespace Content.Server.Body.Components /// [DataField] [Access(typeof(MetabolizerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends - public HashSet>? MetabolizerTypes = null; + public HashSet>? MetabolizerTypes; /// /// Should this metabolizer remove chemicals that have no metabolisms defined? /// As a stop-gap, basically. /// [DataField] - public bool RemoveEmpty = false; + public bool RemoveEmpty; /// /// 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. /// [DataField("groups")] - public List? MetabolismGroups = default!; + public List? MetabolismGroups; } /// @@ -78,7 +90,7 @@ namespace Content.Server.Body.Components public sealed partial class MetabolismGroupEntry { [DataField(required: true)] - public ProtoId Id = default!; + public ProtoId Id; [DataField("rateModifier")] public FixedPoint2 MetabolismRateModifier = 1.0; diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs index 19585e9f00..24f4ae85e1 100644 --- a/Content.Server/Body/Components/RespiratorComponent.cs +++ b/Content.Server/Body/Components/RespiratorComponent.cs @@ -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 { /// @@ -36,7 +35,7 @@ namespace Content.Server.Body.Components /// /// The next time that this body will inhale or exhale. /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] public TimeSpan NextUpdate; /// @@ -46,6 +45,18 @@ namespace Content.Server.Body.Components [DataField] public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2); + /// + /// Multiplier applied to for adjusting based on metabolic rate multiplier. + /// + [DataField] + public float UpdateIntervalMultiplier = 1f; + + /// + /// Adjusted update interval based off of the multiplier value. + /// + [ViewVariables] + public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier; + /// /// Saturation level. Reduced by UpdateInterval each tick. /// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration. diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index 5577b42a06..c59f87f576 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -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 + /// + 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 ent, ref MapInitEvent args) { - ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval; + ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval; } private void OnUnpaused(Entity ent, ref EntityUnpausedEvent args) @@ -65,20 +67,9 @@ namespace Content.Server.Body.Systems } } - private void OnApplyMetabolicMultiplier( - Entity ent, - ref ApplyMetabolicMultiplierEvent args) + private void OnApplyMetabolicMultiplier(Entity 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)); } } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index bc57108720..4e21236a77 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -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(OnMapInit); - SubscribeLocalEvent(OnUnpaused); SubscribeLocalEvent(OnApplyMetabolicMultiplier); SubscribeLocalEvent(OnGasInhaled); SubscribeLocalEvent(OnGasExhaled); @@ -56,25 +55,20 @@ public sealed class RespiratorSystem : EntitySystem private void OnMapInit(Entity ent, ref MapInitEvent args) { - ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval; - } - - private void OnUnpaused(Entity 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(); - while (query.MoveNext(out var uid, out var respirator, out var body)) + var query = EntityQueryEnumerator(); + 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 ent, - ref ApplyMetabolicMultiplierEvent args) + private void OnApplyMetabolicMultiplier(Entity 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 entity, ref InhaledGasEvent args) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 9dc0c2c498..1262e231d7 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -45,6 +45,7 @@ namespace Content.Server.Power.EntitySystems UpdatesAfter.Add(typeof(NodeGroupSystem)); _solver = new(_cfg.GetCVar(CCVars.DebugPow3rDisableParallel)); + SubscribeLocalEvent(ApcPowerReceiverMapInit); SubscribeLocalEvent(ApcPowerReceiverInit); SubscribeLocalEvent(ApcPowerReceiverShutdown); SubscribeLocalEvent(ApcPowerReceiverRemove); @@ -74,6 +75,11 @@ namespace Content.Server.Power.EntitySystems _solver = new(val); } + private void ApcPowerReceiverMapInit(Entity 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 index 0000000000..3a1c991b1e --- /dev/null +++ b/Content.Shared/Bed/Components/StasisBedBuckledComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Bed.Components; + +/// +/// Tracking component added to entities buckled to stasis beds. +/// +[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 index 0000000000..97936261d6 --- /dev/null +++ b/Content.Shared/Bed/Components/StasisBedComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Buckle.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Bed.Components; + +/// +/// A that modifies a strapped entity's metabolic rate by the given multiplier +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedBedSystem))] +public sealed partial class StasisBedComponent : Component +{ + /// + /// What the metabolic update rate will be multiplied by (higher = slower metabolism) + /// + [DataField, AutoNetworkedField] + public float Multiplier = 10f; +} diff --git a/Content.Shared/Bed/SharedBedSystem.cs b/Content.Shared/Bed/SharedBedSystem.cs index cdf3ffcbd4..7e798111cf 100644 --- a/Content.Shared/Bed/SharedBedSystem.cs +++ b/Content.Shared/Bed/SharedBedSystem.cs @@ -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(OnHealMapInit); SubscribeLocalEvent(OnStrapped); SubscribeLocalEvent(OnUnstrapped); + + SubscribeLocalEvent(OnStasisStrapped); + SubscribeLocalEvent(OnStasisUnstrapped); + SubscribeLocalEvent(OnStasisEmagged); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnStasisGetMetabolicMultiplier); } private void OnHealMapInit(Entity ent, ref MapInitEvent args) @@ -46,4 +60,61 @@ public abstract class SharedBedSystem : EntitySystem _sleepingSystem.TryWaking(args.Buckle.Owner); RemComp(bed); } + + private void OnStasisStrapped(Entity ent, ref StrappedEvent args) + { + EnsureComp(args.Buckle); + _metabolizer.UpdateMetabolicMultiplier(args.Buckle); + } + + private void OnStasisUnstrapped(Entity ent, ref UnstrappedEvent args) + { + RemComp(ent); + _metabolizer.UpdateMetabolicMultiplier(args.Buckle); + } + + private void OnStasisEmagged(Entity 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 ent, ref PowerChangedEvent args) + { + UpdateMetabolisms(ent.Owner); + } + + private void OnStasisGetMetabolicMultiplier(Entity ent, ref GetMetabolicMultiplierEvent args) + { + if (!TryComp(ent, out var buckle) || buckle.BuckledTo is not { } buckledTo) + return; + + if (!TryComp(buckledTo, out var stasis)) + return; + + if (!_powerReceiver.IsPowered(buckledTo)) + return; + + args.Multiplier *= stasis.Multiplier; + } + + protected void UpdateMetabolisms(Entity 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 index cf7bc06460..0000000000 --- a/Content.Shared/Bed/StasisBedVisuals.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Bed -{ - [Serializable, NetSerializable] - public enum StasisBedVisuals : byte - { - IsOn, - } -} diff --git a/Content.Shared/Body/Components/BloodstreamComponent.cs b/Content.Shared/Body/Components/BloodstreamComponent.cs index 7997d92066..0139932262 100644 --- a/Content.Shared/Body/Components/BloodstreamComponent.cs +++ b/Content.Shared/Body/Components/BloodstreamComponent.cs @@ -37,6 +37,18 @@ public sealed partial class BloodstreamComponent : Component [DataField, AutoNetworkedField] public TimeSpan UpdateInterval = TimeSpan.FromSeconds(3); + /// + /// Multiplier applied to for adjusting based on metabolic rate multiplier. + /// + [DataField, AutoNetworkedField] + public float UpdateIntervalMultiplier = 1f; + + /// + /// Adjusted update interval based off of the multiplier value. + /// + [ViewVariables] + public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier; + /// /// How much is this entity currently bleeding? /// Higher numbers mean more blood lost every tick. diff --git a/Content.Shared/Body/Components/StomachComponent.cs b/Content.Shared/Body/Components/StomachComponent.cs index b76dff0902..1b2f587824 100644 --- a/Content.Shared/Body/Components/StomachComponent.cs +++ b/Content.Shared/Body/Components/StomachComponent.cs @@ -23,6 +23,18 @@ namespace Content.Shared.Body.Components [DataField] public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1); + /// + /// Multiplier applied to for adjusting based on metabolic rate multiplier. + /// + [DataField] + public float UpdateIntervalMultiplier = 1f; + + /// + /// Adjusted update interval based off of the multiplier value. + /// + [ViewVariables] + public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier; + /// /// The solution inside of this stomach this transfers reagents to the body. /// diff --git a/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs b/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs deleted file mode 100644 index 1a7b589392..0000000000 --- a/Content.Shared/Body/Events/ApplyMetabolicMultiplierEvent.cs +++ /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) -{ - /// - /// The entity whose metabolism is being modified. - /// - public readonly EntityUid Uid = Uid; - - /// - /// What the metabolism's update rate will be multiplied by. - /// - public readonly float Multiplier = Multiplier; - - /// - /// If true, apply the multiplier. If false, revert it. - /// - 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 index 0000000000..f001c83f88 --- /dev/null +++ b/Content.Shared/Body/Events/MetabolizerEvents.cs @@ -0,0 +1,26 @@ +namespace Content.Shared.Body.Events; + +/// +/// Raised on an entity to determine their metabolic multiplier. +/// +[ByRefEvent] +public record struct GetMetabolicMultiplierEvent() +{ + /// + /// What the metabolism's update rate will be multiplied by. + /// + public float Multiplier = 1f; +} + +/// +/// 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. +/// +[ByRefEvent] +public readonly record struct ApplyMetabolicMultiplierEvent(float Multiplier) +{ + /// + /// What the metabolism's update rate will be multiplied by. + /// + public readonly float Multiplier = Multiplier; +} diff --git a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs index 3b1674cf3c..b5d5100189 100644 --- a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs +++ b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs @@ -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 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 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 ent, ref RejuvenateEvent args) diff --git a/Content.Shared/Body/Systems/SharedMetabolizerSystem.cs b/Content.Shared/Body/Systems/SharedMetabolizerSystem.cs new file mode 100644 index 0000000000..24ab438f6d --- /dev/null +++ b/Content.Shared/Body/Systems/SharedMetabolizerSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Body.Events; + +namespace Content.Shared.Body.Systems; + +public abstract class SharedMetabolizerSystem : EntitySystem +{ + /// + /// Updates the metabolic rate multiplier for a given entity, + /// raising both to determine what the multiplier is and to update relevant components. + /// + /// + public void UpdateMetabolicMultiplier(EntityUid uid) + { + var getEv = new GetMetabolicMultiplierEvent(); + RaiseLocalEvent(uid, ref getEv); + + var applyEv = new ApplyMetabolicMultiplierEvent(getEv.Multiplier); + RaiseLocalEvent(uid, ref applyEv); + } +} diff --git a/Content.Shared/Body/Systems/StomachSystem.cs b/Content.Shared/Body/Systems/StomachSystem.cs index 8b2df453a0..3d8647b14d 100644 --- a/Content.Shared/Body/Systems/StomachSystem.cs +++ b/Content.Shared/Body/Systems/StomachSystem.cs @@ -27,7 +27,7 @@ namespace Content.Shared.Body.Systems private void OnMapInit(Entity ent, ref MapInitEvent args) { - ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval; + ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval; } private void OnUnpaused(Entity 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(); 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 ent, - ref ApplyMetabolicMultiplierEvent args) + private void OnApplyMetabolicMultiplier(Entity 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( diff --git a/Resources/Prototypes/Entities/Structures/Machines/stasisbed.yml b/Resources/Prototypes/Entities/Structures/Machines/stasisbed.yml index 4dd1197593..999cd7bf2a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/stasisbed.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/stasisbed.yml @@ -18,8 +18,13 @@ - 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