diff --git a/Content.Client/Temperature/Systems/EntityHeaterSystem.cs b/Content.Client/Temperature/Systems/EntityHeaterSystem.cs new file mode 100644 index 0000000000..300cfa3d44 --- /dev/null +++ b/Content.Client/Temperature/Systems/EntityHeaterSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Temperature.Systems; + +namespace Content.Client.Temperature.Systems; + +public sealed partial class EntityHeaterSystem : SharedEntityHeaterSystem; diff --git a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs index c4b5b72a9c..8452edf8e9 100644 --- a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs +++ b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs @@ -1,45 +1,41 @@ using Content.Server.Power.Components; -using Content.Server.Temperature.Components; -using Content.Shared.Examine; using Content.Shared.Placeable; -using Content.Shared.Popups; -using Content.Shared.Power; using Content.Shared.Temperature; -using Content.Shared.Verbs; -using Robust.Server.Audio; +using Content.Shared.Temperature.Components; +using Content.Shared.Temperature.Systems; namespace Content.Server.Temperature.Systems; /// -/// Handles updating and events. +/// Handles the server-only parts of /// -public sealed class EntityHeaterSystem : EntitySystem +public sealed class EntityHeaterSystem : SharedEntityHeaterSystem { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TemperatureSystem _temperature = default!; - [Dependency] private readonly AudioSystem _audio = default!; - - private readonly int SettingCount = Enum.GetValues(typeof(EntityHeaterSetting)).Length; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent>(OnGetVerbs); - SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + // Set initial power level + if (TryComp(ent, out var power)) + power.Load = SettingPower(ent.Comp.Setting, ent.Comp.Power); } public override void Update(float deltaTime) { var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp, out var placer, out var power)) + while (query.MoveNext(out _, out _, out var placer, out var power)) { if (!power.Powered) continue; - // don't divide by total entities since its a big grill + // don't divide by total entities since it's a big grill // excess would just be wasted in the air but that's not worth simulating // if you want a heater thermomachine just use that... var energy = power.PowerReceived * deltaTime; @@ -50,66 +46,17 @@ public sealed class EntityHeaterSystem : EntitySystem } } - private void OnExamined(EntityUid uid, EntityHeaterComponent comp, ExaminedEvent args) + /// + /// doesn't exist on the client, so we need + /// this server-only override to handle setting the network load. + /// + protected override void ChangeSetting(Entity ent, EntityHeaterSetting setting, EntityUid? user = null) { - if (!args.IsInDetailsRange) + base.ChangeSetting(ent, setting, user); + + if (!TryComp(ent, out var power)) return; - args.PushMarkup(Loc.GetString("entity-heater-examined", ("setting", comp.Setting))); - } - - private void OnGetVerbs(EntityUid uid, EntityHeaterComponent comp, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - var setting = (int) comp.Setting; - setting++; - setting %= SettingCount; - var nextSetting = (EntityHeaterSetting) setting; - - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString("entity-heater-switch-setting", ("setting", nextSetting)), - Act = () => - { - ChangeSetting(uid, nextSetting, comp); - _popup.PopupEntity(Loc.GetString("entity-heater-switched-setting", ("setting", nextSetting)), uid, args.User); - } - }); - } - - private void OnPowerChanged(EntityUid uid, EntityHeaterComponent comp, ref PowerChangedEvent args) - { - // disable heating element glowing layer if theres no power - // doesn't actually turn it off since that would be annoying - var setting = args.Powered ? comp.Setting : EntityHeaterSetting.Off; - _appearance.SetData(uid, EntityHeaterVisuals.Setting, setting); - } - - private void ChangeSetting(EntityUid uid, EntityHeaterSetting setting, EntityHeaterComponent? comp = null, ApcPowerReceiverComponent? power = null) - { - if (!Resolve(uid, ref comp, ref power)) - return; - - comp.Setting = setting; - power.Load = SettingPower(setting, comp.Power); - _appearance.SetData(uid, EntityHeaterVisuals.Setting, setting); - _audio.PlayPvs(comp.SettingSound, uid); - } - - private float SettingPower(EntityHeaterSetting setting, float max) - { - switch (setting) - { - case EntityHeaterSetting.Low: - return max / 3f; - case EntityHeaterSetting.Medium: - return max * 2f / 3f; - case EntityHeaterSetting.High: - return max; - default: - return 0f; - } + power.Load = SettingPower(setting, ent.Comp.Power); } } diff --git a/Content.Server/Temperature/Components/EntityHeaterComponent.cs b/Content.Shared/Temperature/Components/EntityHeaterComponent.cs similarity index 73% rename from Content.Server/Temperature/Components/EntityHeaterComponent.cs rename to Content.Shared/Temperature/Components/EntityHeaterComponent.cs index 0b5acb421a..6cf97a0534 100644 --- a/Content.Server/Temperature/Components/EntityHeaterComponent.cs +++ b/Content.Shared/Temperature/Components/EntityHeaterComponent.cs @@ -1,26 +1,27 @@ -using Content.Server.Temperature.Systems; -using Content.Shared.Temperature; +using Content.Shared.Temperature.Systems; using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Server.Temperature.Components; +namespace Content.Shared.Temperature.Components; /// /// Adds thermal energy to entities with placed on it. /// -[RegisterComponent, Access(typeof(EntityHeaterSystem))] +[RegisterComponent, Access(typeof(SharedEntityHeaterSystem))] +[NetworkedComponent, AutoGenerateComponentState] public sealed partial class EntityHeaterComponent : Component { /// /// Power used when heating at the high setting. /// Low and medium are 33% and 66% respectively. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Power = 2400f; /// /// Current setting of the heater. If it is off or unpowered it won't heat anything. /// - [DataField] + [DataField, AutoNetworkedField] public EntityHeaterSetting Setting = EntityHeaterSetting.Off; /// diff --git a/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs new file mode 100644 index 0000000000..887047bfa1 --- /dev/null +++ b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs @@ -0,0 +1,97 @@ +using Content.Shared.Examine; +using Content.Shared.Popups; +using Content.Shared.Power; +using Content.Shared.Power.EntitySystems; +using Content.Shared.Temperature.Components; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared.Temperature.Systems; + +/// +/// Handles events. +/// +public abstract partial class SharedEntityHeaterSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + private readonly int _settingCount = Enum.GetValues().Length; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent>(OnGetVerbs); + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + args.PushMarkup(Loc.GetString("entity-heater-examined", ("setting", ent.Comp.Setting))); + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var nextSettingIndex = ((int)ent.Comp.Setting + 1) % _settingCount; + var nextSetting = (EntityHeaterSetting)nextSettingIndex; + + var user = args.User; + args.Verbs.Add(new AlternativeVerb() + { + Text = Loc.GetString("entity-heater-switch-setting", ("setting", nextSetting)), + Act = () => + { + ChangeSetting(ent, nextSetting, user); + } + }); + } + + private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) + { + // disable heating element glowing layer if theres no power + // doesn't actually change the setting since that would be annoying + var setting = args.Powered ? ent.Comp.Setting : EntityHeaterSetting.Off; + _appearance.SetData(ent, EntityHeaterVisuals.Setting, setting); + } + + protected virtual void ChangeSetting(Entity ent, EntityHeaterSetting setting, EntityUid? user = null) + { + // Still allow changing the setting without power + ent.Comp.Setting = setting; + _audio.PlayPredicted(ent.Comp.SettingSound, ent, user); + _popup.PopupClient(Loc.GetString("entity-heater-switched-setting", ("setting", setting)), ent, user); + Dirty(ent); + + // Only show the glowing heating element layer if there's power + if (_receiver.IsPowered(ent.Owner)) + _appearance.SetData(ent, EntityHeaterVisuals.Setting, setting); + } + + protected float SettingPower(EntityHeaterSetting setting, float max) + { + // Power use while off needs to be non-zero so powernet doesn't consider the device powered + // by an unpowered network while in the off state. Otherwise, when we increase the load, + // the clientside APC receiver will think the device is powered until it gets the next + // update from the server, which will cause the heating element to glow for a moment. + // I spent several hours trying to figure out a better way to do this using PowerDisabled + // or something, but nothing worked as well as this. + // Just think of the load as a little LED, or bad wiring, or something. + return setting switch + { + EntityHeaterSetting.Low => max / 3f, + EntityHeaterSetting.Medium => max * 2f / 3f, + EntityHeaterSetting.High => max, + _ => 0.01f, + }; + } +}