]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Reduce network burden of the hunger system (#32986)
authorCentronias <me@centronias.com>
Wed, 18 Dec 2024 13:06:02 +0000 (05:06 -0800)
committerGitHub <noreply@github.com>
Wed, 18 Dec 2024 13:06:02 +0000 (14:06 +0100)
* reduce network burden of the hunger system

* explicit start + last updated

* remove auto reformat changes to otherwise untouched code

add clamp helper

* imagine making breaking changes, documenting them, and then not thinking to check the yaml

* comments

* Remove unused net manager in hunger system
Remove lastAuthoritativeHungerValue from prototypes

Content.Server/Animals/Systems/EggLayerSystem.cs
Content.Server/EntityEffects/EffectConditions/TotalHunger.cs
Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs
Content.Server/RatKing/RatKingSystem.cs
Content.Shared/Nutrition/Components/HungerComponent.cs
Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
Content.Shared/Sericulture/SericultureSystem.cs
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/space.yml

index 3e552f1b387f641f6274e361206ed99a603cb08e..accbda281bec8a92773be2f80bbc3ab60303cfd8 100644 (file)
@@ -84,7 +84,7 @@ public sealed class EggLayerSystem : EntitySystem
         // Allow infinitely laying eggs if they can't get hungry.
         if (TryComp<HungerComponent>(uid, out var hunger))
         {
-            if (hunger.CurrentHunger < egglayer.HungerUsage)
+            if (_hunger.GetHunger(hunger) < egglayer.HungerUsage)
             {
                 _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-too-hungry"), uid, uid);
                 return false;
index 84ad4c22403b0e284d97d3159166393657abeac4..c4f69b60de67c7cd1a6c1406ce07a88b2141a183 100644 (file)
@@ -1,6 +1,6 @@
 using Content.Shared.EntityEffects;
 using Content.Shared.Nutrition.Components;
-using Content.Shared.FixedPoint;
+using Content.Shared.Nutrition.EntitySystems;
 using Robust.Shared.Prototypes;
 
 namespace Content.Server.EntityEffects.EffectConditions;
@@ -17,7 +17,7 @@ public sealed partial class Hunger : EntityEffectCondition
     {
         if (args.EntityManager.TryGetComponent(args.TargetEntity, out HungerComponent? hunger))
         {
-            var total = hunger.CurrentHunger;
+            var total = args.EntityManager.System<HungerSystem>().GetHunger(hunger);
             if (total > Min && total < Max)
                 return true;
         }
index c91a6f795b2c03559230c961e0384ffffaa8f0fb..6e9856a61dc0b9a3d9209ba9ef36d7ffeaa299c7 100644 (file)
@@ -100,7 +100,7 @@ public sealed class FatExtractorSystem : EntitySystem
         if (!TryComp<HungerComponent>(occupant, out var hunger))
             return false;
 
-        if (hunger.CurrentHunger < component.NutritionPerSecond)
+        if (_hunger.GetHunger(hunger) < component.NutritionPerSecond)
             return false;
 
         if (hunger.CurrentThreshold < component.MinHungerThreshold && !HasComp<EmaggedComponent>(uid))
index 4b82dba33590f4883eceb177a8ed47081f8ac487..01c213d5403e377cc80bfac2b6878777011c035c 100644 (file)
@@ -47,7 +47,7 @@ namespace Content.Server.RatKing
                 return;
 
             //make sure the hunger doesn't go into the negatives
-            if (hunger.CurrentHunger < component.HungerPerArmyUse)
+            if (_hunger.GetHunger(hunger) < component.HungerPerArmyUse)
             {
                 _popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
                 return;
@@ -77,7 +77,7 @@ namespace Content.Server.RatKing
                 return;
 
             //make sure the hunger doesn't go into the negatives
-            if (hunger.CurrentHunger < component.HungerPerDomainUse)
+            if (_hunger.GetHunger(hunger) < component.HungerPerDomainUse)
             {
                 _popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
                 return;
index 79d895ddae66d6c3d7fb1a7bfdea6294e401044e..33abb257dd10c03f3b1e709995c364e9a959668f 100644 (file)
@@ -14,22 +14,33 @@ namespace Content.Shared.Nutrition.Components;
 public sealed partial class HungerComponent : Component
 {
     /// <summary>
-    /// The current hunger amount of the entity
+    /// The hunger value as authoritatively set by the server as of <see cref="LastAuthoritativeHungerChangeTime"/>.
+    /// This value should be updated relatively infrequently. To get the current hunger, which changes with each update,
+    /// use <see cref="HungerSystem.GetHunger"/>.
     /// </summary>
-    [DataField("currentHunger"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField, ViewVariables(VVAccess.ReadOnly)]
     [AutoNetworkedField]
-    public float CurrentHunger;
+    public float LastAuthoritativeHungerValue;
 
     /// <summary>
-    /// The base amount at which <see cref="CurrentHunger"/> decays.
+    /// The time at which <see cref="LastAuthoritativeHungerValue"/> was last updated.
     /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public TimeSpan LastAuthoritativeHungerChangeTime;
+
+    /// <summary>
+    /// The base amount at which <see cref="LastAuthoritativeHungerValue"/> decays.
+    /// </summary>
+    /// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
     [DataField("baseDecayRate"), ViewVariables(VVAccess.ReadWrite)]
     public float BaseDecayRate = 0.01666666666f;
 
     /// <summary>
-    /// The actual amount at which <see cref="CurrentHunger"/> decays.
+    /// The actual amount at which <see cref="LastAuthoritativeHungerValue"/> decays.
     /// Affected by <seealso cref="CurrentThreshold"/>
     /// </summary>
+    /// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
     [DataField("actualDecayRate"), ViewVariables(VVAccess.ReadWrite)]
     [AutoNetworkedField]
     public float ActualDecayRate;
@@ -45,12 +56,13 @@ public sealed partial class HungerComponent : Component
     /// <summary>
     /// The current hunger threshold the entity is at
     /// </summary>
+    /// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
     [DataField("currentThreshold"), ViewVariables(VVAccess.ReadWrite)]
     [AutoNetworkedField]
     public HungerThreshold CurrentThreshold;
 
     /// <summary>
-    /// A dictionary relating HungerThreshold to the amount of <see cref="CurrentHunger"/> needed for each one
+    /// A dictionary relating HungerThreshold to the amount of <see cref="HungerSystem.GetHunger">current hunger</see> needed for each one
     /// </summary>
     [DataField("thresholds", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, float>))]
     [AutoNetworkedField]
@@ -106,19 +118,19 @@ public sealed partial class HungerComponent : Component
     public DamageSpecifier? StarvationDamage;
 
     /// <summary>
-    /// The time when the hunger will update next.
+    /// The time when the hunger threshold will update next.
     /// </summary>
     [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
     [AutoNetworkedField]
     [AutoPausedField]
-    public TimeSpan NextUpdateTime;
+    public TimeSpan NextThresholdUpdateTime;
 
     /// <summary>
-    /// The time between each update.
+    /// The time between each hunger threshold update.
     /// </summary>
     [ViewVariables(VVAccess.ReadWrite)]
     [AutoNetworkedField]
-    public TimeSpan UpdateRate = TimeSpan.FromSeconds(1);
+    public TimeSpan ThresholdUpdateRate = TimeSpan.FromSeconds(1);
 }
 
 [Serializable, NetSerializable]
index 6a6dd7af7820b9b2d86b49f6ca8c8f8ecacff2d7..063c1f6bb1a691db7c85082a2bb954fe9ba2276d 100644 (file)
@@ -6,6 +6,7 @@ using Content.Shared.Movement.Systems;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Rejuvenate;
 using Content.Shared.StatusIcon;
+using Robust.Shared.Network;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
@@ -72,6 +73,16 @@ public sealed class HungerSystem : EntitySystem
         SetHunger(uid, component.Thresholds[HungerThreshold.Okay], component);
     }
 
+    /// <summary>
+    /// Gets the current hunger value of the given <see cref="HungerComponent"/>.
+    /// </summary>
+    public float GetHunger(HungerComponent component)
+    {
+        var dt = _timing.CurTime - component.LastAuthoritativeHungerChangeTime;
+        var value = component.LastAuthoritativeHungerValue - (float)dt.TotalSeconds * component.ActualDecayRate;
+        return ClampHungerWithinThresholds(component, value);
+    }
+
     /// <summary>
     /// Adds to the current hunger of an entity by the specified value
     /// </summary>
@@ -82,7 +93,7 @@ public sealed class HungerSystem : EntitySystem
     {
         if (!Resolve(uid, ref component))
             return;
-        SetHunger(uid, component.CurrentHunger + amount, component);
+        SetHunger(uid, GetHunger(component) + amount, component);
     }
 
     /// <summary>
@@ -95,11 +106,23 @@ public sealed class HungerSystem : EntitySystem
     {
         if (!Resolve(uid, ref component))
             return;
-        component.CurrentHunger = Math.Clamp(amount,
-            component.Thresholds[HungerThreshold.Dead],
-            component.Thresholds[HungerThreshold.Overfed]);
+
+        SetAuthoritativeHungerValue((uid, component), amount);
         UpdateCurrentThreshold(uid, component);
-        Dirty(uid, component);
+    }
+
+    /// <summary>
+    /// Sets <see cref="HungerComponent.LastAuthoritativeHungerValue"/> and
+    /// <see cref="HungerComponent.LastAuthoritativeHungerChangeTime"/>, and dirties this entity. This "resets" the
+    /// starting point for <see cref="GetHunger"/>'s calculation.
+    /// </summary>
+    /// <param name="entity">The entity whose hunger will be set.</param>
+    /// <param name="value">The value to set the entity's hunger to.</param>
+    private void SetAuthoritativeHungerValue(Entity<HungerComponent> entity, float value)
+    {
+        entity.Comp.LastAuthoritativeHungerChangeTime = _timing.CurTime;
+        entity.Comp.LastAuthoritativeHungerValue = ClampHungerWithinThresholds(entity.Comp, value);
+        Dirty(entity);
     }
 
     private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = null)
@@ -112,7 +135,6 @@ public sealed class HungerSystem : EntitySystem
             return;
         component.CurrentThreshold = calculatedHungerThreshold;
         DoHungerThresholdEffects(uid, component);
-        Dirty(uid, component);
     }
 
     private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component = null, bool force = false)
@@ -140,6 +162,7 @@ public sealed class HungerSystem : EntitySystem
         if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier))
         {
             component.ActualDecayRate = component.BaseDecayRate * modifier;
+            SetAuthoritativeHungerValue((uid, component), GetHunger(component));
         }
 
         component.LastThreshold = component.CurrentThreshold;
@@ -167,7 +190,7 @@ public sealed class HungerSystem : EntitySystem
     /// <returns></returns>
     public HungerThreshold GetHungerThreshold(HungerComponent component, float? food = null)
     {
-        food ??= component.CurrentHunger;
+        food ??= GetHunger(component);
         var result = HungerThreshold.Dead;
         var value = component.Thresholds[HungerThreshold.Overfed];
         foreach (var threshold in component.Thresholds)
@@ -178,6 +201,7 @@ public sealed class HungerSystem : EntitySystem
                 value = threshold.Value;
             }
         }
+
         return result;
     }
 
@@ -229,6 +253,13 @@ public sealed class HungerSystem : EntitySystem
         return prototype != null;
     }
 
+    private static float ClampHungerWithinThresholds(HungerComponent component, float hungerValue)
+    {
+        return Math.Clamp(hungerValue,
+            component.Thresholds[HungerThreshold.Dead],
+            component.Thresholds[HungerThreshold.Overfed]);
+    }
+
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
@@ -236,13 +267,12 @@ public sealed class HungerSystem : EntitySystem
         var query = EntityQueryEnumerator<HungerComponent>();
         while (query.MoveNext(out var uid, out var hunger))
         {
-            if (_timing.CurTime < hunger.NextUpdateTime)
+            if (_timing.CurTime < hunger.NextThresholdUpdateTime)
                 continue;
-            hunger.NextUpdateTime = _timing.CurTime + hunger.UpdateRate;
+            hunger.NextThresholdUpdateTime = _timing.CurTime + hunger.ThresholdUpdateRate;
 
-            ModifyHunger(uid, -hunger.ActualDecayRate, hunger);
+            UpdateCurrentThreshold(uid, hunger);
             DoContinuousHungerEffects(uid, hunger);
         }
     }
 }
-
index f7586cc1ec309ece3ba09a014aa3a0dca6d126b8..8c10d0f3d05074aad2d650590d9df94a37dab435 100644 (file)
@@ -53,7 +53,10 @@ public abstract partial class SharedSericultureSystem : EntitySystem
     private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args)
     {
         if (TryComp<HungerComponent>(uid, out var hungerComp)
-        && _hungerSystem.IsHungerBelowState(uid, comp.MinHungerThreshold, hungerComp.CurrentHunger - comp.HungerCost, hungerComp))
+            && _hungerSystem.IsHungerBelowState(uid,
+                comp.MinHungerThreshold,
+                _hungerSystem.GetHunger(hungerComp) - comp.HungerCost,
+                hungerComp))
         {
             _popupSystem.PopupClient(Loc.GetString(comp.PopupText), uid, uid);
             return;
@@ -76,8 +79,12 @@ public abstract partial class SharedSericultureSystem : EntitySystem
         if (args.Cancelled || args.Handled || comp.Deleted)
             return;
 
-        if (TryComp<HungerComponent>(uid, out var hungerComp) // A check, just incase the doafter is somehow performed when the entity is not in the right hunger state.
-        && _hungerSystem.IsHungerBelowState(uid, comp.MinHungerThreshold, hungerComp.CurrentHunger - comp.HungerCost, hungerComp))
+        if (TryComp<HungerComponent>(uid,
+                out var hungerComp) // A check, just incase the doafter is somehow performed when the entity is not in the right hunger state.
+            && _hungerSystem.IsHungerBelowState(uid,
+                comp.MinHungerThreshold,
+                _hungerSystem.GetHunger(hungerComp) - comp.HungerCost,
+                hungerComp))
         {
             _popupSystem.PopupClient(Loc.GetString(comp.PopupText), uid, uid);
             return;
index f8fb3e34ce5400b6c6aee76f523f7042be7db86c..d801530c6bc5c7467af08470ebd81880c6be325f 100644 (file)
       Dead: 0
     baseDecayRate: 0.04
   - type: Hunger
-    currentHunger: 25   # spawn with Okay hunger state
     thresholds:
       Overfed: 35
       Okay: 25
index a51fe522381a2b3ab35a83b4331ec43c6f9254df..0dc96ba6f31cbbc8d6f203e5c4a673ad8c277a47 100644 (file)
       Dead: 0
     baseDecayRate: 0.04
   - type: Hunger
-    currentHunger: 25   # spawn with Okay hunger state
     thresholds:
       Overfed: 35
       Okay: 25