]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add prediction to electric grills (#36241)
authorTayrtahn <tayrtahn@gmail.com>
Tue, 1 Apr 2025 16:43:19 +0000 (12:43 -0400)
committerGitHub <noreply@github.com>
Tue, 1 Apr 2025 16:43:19 +0000 (09:43 -0700)
* Prediction for EntityHeaterSystem

* Switch to Entity<T>

* meh

* Move popup inside ChangeSetting

* Fix grill visually turning on when changing setting while power is off

* Add note about my failed quest

* Why isn't this an IDE warning?

* Move comment above switch expression in SettingPower

Content.Client/Temperature/Systems/EntityHeaterSystem.cs [new file with mode: 0644]
Content.Server/Temperature/Systems/EntityHeaterSystem.cs
Content.Shared/Temperature/Components/EntityHeaterComponent.cs [moved from Content.Server/Temperature/Components/EntityHeaterComponent.cs with 73% similarity]
Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs [new file with mode: 0644]

diff --git a/Content.Client/Temperature/Systems/EntityHeaterSystem.cs b/Content.Client/Temperature/Systems/EntityHeaterSystem.cs
new file mode 100644 (file)
index 0000000..300cfa3
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Temperature.Systems;
+
+namespace Content.Client.Temperature.Systems;
+
+public sealed partial class EntityHeaterSystem : SharedEntityHeaterSystem;
index c4b5b72a9cb759ea5b5d30e6517c86e63a67bfd6..8452edf8e9ad61d370757ac8ffb8e0da2bbc2e09 100644 (file)
@@ -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;
 
 /// <summary>
-/// Handles <see cref="EntityHeaterComponent"/> updating and events.
+/// Handles the server-only parts of <see cref="SharedEntityHeaterSystem"/>
 /// </summary>
-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<EntityHeaterComponent, ExaminedEvent>(OnExamined);
-        SubscribeLocalEvent<EntityHeaterComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
-        SubscribeLocalEvent<EntityHeaterComponent, PowerChangedEvent>(OnPowerChanged);
+        SubscribeLocalEvent<EntityHeaterComponent, MapInitEvent>(OnMapInit);
+    }
+
+    private void OnMapInit(Entity<EntityHeaterComponent> ent, ref MapInitEvent args)
+    {
+        // Set initial power level
+        if (TryComp<ApcPowerReceiverComponent>(ent, out var power))
+            power.Load = SettingPower(ent.Comp.Setting, ent.Comp.Power);
     }
 
     public override void Update(float deltaTime)
     {
         var query = EntityQueryEnumerator<EntityHeaterComponent, ItemPlacerComponent, ApcPowerReceiverComponent>();
-        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)
+    /// <remarks>
+    /// <see cref="ApcPowerReceiverComponent"/> doesn't exist on the client, so we need
+    /// this server-only override to handle setting the network load.
+    /// </remarks>
+    protected override void ChangeSetting(Entity<EntityHeaterComponent> ent, EntityHeaterSetting setting, EntityUid? user = null)
     {
-        if (!args.IsInDetailsRange)
-            return;
-
-        args.PushMarkup(Loc.GetString("entity-heater-examined", ("setting", comp.Setting)));
-    }
+        base.ChangeSetting(ent, setting, user);
 
-    private void OnGetVerbs(EntityUid uid, EntityHeaterComponent comp, GetVerbsEvent<AlternativeVerb> args)
-    {
-        if (!args.CanAccess || !args.CanInteract)
+        if (!TryComp<ApcPowerReceiverComponent>(ent, out var power))
             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);
     }
 }
similarity index 73%
rename from Content.Server/Temperature/Components/EntityHeaterComponent.cs
rename to Content.Shared/Temperature/Components/EntityHeaterComponent.cs
index 0b5acb421a899f4cc888fadc4e09200c8e1ae0ef..6cf97a0534934df08bf8ff64f4e98743b705d18d 100644 (file)
@@ -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;
 
 /// <summary>
 /// Adds thermal energy to entities with <see cref="TemperatureComponent"/> placed on it.
 /// </summary>
-[RegisterComponent, Access(typeof(EntityHeaterSystem))]
+[RegisterComponent, Access(typeof(SharedEntityHeaterSystem))]
+[NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class EntityHeaterComponent : Component
 {
     /// <summary>
     /// Power used when heating at the high setting.
     /// Low and medium are 33% and 66% respectively.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float Power = 2400f;
 
     /// <summary>
     /// Current setting of the heater. If it is off or unpowered it won't heat anything.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public EntityHeaterSetting Setting = EntityHeaterSetting.Off;
 
     /// <summary>
diff --git a/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs b/Content.Shared/Temperature/Systems/SharedEntityHeaterSystem.cs
new file mode 100644 (file)
index 0000000..887047b
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Handles <see cref="EntityHeaterComponent"/> events.
+/// </summary>
+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<EntityHeaterSetting>().Length;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<EntityHeaterComponent, ExaminedEvent>(OnExamined);
+        SubscribeLocalEvent<EntityHeaterComponent, GetVerbsEvent<AlternativeVerb>>(OnGetVerbs);
+        SubscribeLocalEvent<EntityHeaterComponent, PowerChangedEvent>(OnPowerChanged);
+    }
+
+    private void OnExamined(Entity<EntityHeaterComponent> ent, ref ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        args.PushMarkup(Loc.GetString("entity-heater-examined", ("setting", ent.Comp.Setting)));
+    }
+
+    private void OnGetVerbs(Entity<EntityHeaterComponent> ent, ref GetVerbsEvent<AlternativeVerb> 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<EntityHeaterComponent> 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<EntityHeaterComponent> 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,
+        };
+    }
+}