From 6b99493e80440511e242733a90732f909c77e137 Mon Sep 17 00:00:00 2001 From: Centronias Date: Wed, 18 Dec 2024 05:06:02 -0800 Subject: [PATCH] Reduce network burden of the hunger system (#32986) * 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 --- .../Animals/Systems/EggLayerSystem.cs | 2 +- .../EffectConditions/TotalHunger.cs | 4 +- .../EntitySystems/FatExtractorSystem.cs | 2 +- Content.Server/RatKing/RatKingSystem.cs | 4 +- .../Nutrition/Components/HungerComponent.cs | 32 ++++++++---- .../Nutrition/EntitySystems/HungerSystem.cs | 52 +++++++++++++++---- .../Sericulture/SericultureSystem.cs | 13 +++-- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 1 - .../Prototypes/Entities/Mobs/NPCs/space.yml | 1 - 9 files changed, 79 insertions(+), 32 deletions(-) diff --git a/Content.Server/Animals/Systems/EggLayerSystem.cs b/Content.Server/Animals/Systems/EggLayerSystem.cs index 3e552f1b387f64..accbda281bec8a 100644 --- a/Content.Server/Animals/Systems/EggLayerSystem.cs +++ b/Content.Server/Animals/Systems/EggLayerSystem.cs @@ -84,7 +84,7 @@ public bool TryLayEgg(EntityUid uid, EggLayerComponent? egglayer) // Allow infinitely laying eggs if they can't get hungry. if (TryComp(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; diff --git a/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs b/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs index 84ad4c22403b0e..c4f69b60de67c7 100644 --- a/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs +++ b/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs @@ -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 override bool Condition(EntityEffectBaseArgs args) { if (args.EntityManager.TryGetComponent(args.TargetEntity, out HungerComponent? hunger)) { - var total = hunger.CurrentHunger; + var total = args.EntityManager.System().GetHunger(hunger); if (total > Min && total < Max) return true; } diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs index c91a6f795b2c03..6e9856a61dc0b9 100644 --- a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -100,7 +100,7 @@ public bool TryGetValidOccupant(EntityUid uid, [NotNullWhen(true)] out EntityUid if (!TryComp(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(uid)) diff --git a/Content.Server/RatKing/RatKingSystem.cs b/Content.Server/RatKing/RatKingSystem.cs index 4b82dba33590f4..01c213d5403e37 100644 --- a/Content.Server/RatKing/RatKingSystem.cs +++ b/Content.Server/RatKing/RatKingSystem.cs @@ -47,7 +47,7 @@ private void OnRaiseArmy(EntityUid uid, RatKingComponent component, RatKingRaise 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 @@ private void OnDomain(EntityUid uid, RatKingComponent component, RatKingDomainAc 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; diff --git a/Content.Shared/Nutrition/Components/HungerComponent.cs b/Content.Shared/Nutrition/Components/HungerComponent.cs index 79d895ddae66d6..33abb257dd10c0 100644 --- a/Content.Shared/Nutrition/Components/HungerComponent.cs +++ b/Content.Shared/Nutrition/Components/HungerComponent.cs @@ -14,22 +14,33 @@ namespace Content.Shared.Nutrition.Components; public sealed partial class HungerComponent : Component { /// - /// The current hunger amount of the entity + /// The hunger value as authoritatively set by the server as of . + /// This value should be updated relatively infrequently. To get the current hunger, which changes with each update, + /// use . /// - [DataField("currentHunger"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadOnly)] [AutoNetworkedField] - public float CurrentHunger; + public float LastAuthoritativeHungerValue; /// - /// The base amount at which decays. + /// The time at which was last updated. /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public TimeSpan LastAuthoritativeHungerChangeTime; + + /// + /// The base amount at which decays. + /// + /// Any time this is modified, should be called. [DataField("baseDecayRate"), ViewVariables(VVAccess.ReadWrite)] public float BaseDecayRate = 0.01666666666f; /// - /// The actual amount at which decays. + /// The actual amount at which decays. /// Affected by /// + /// Any time this is modified, should be called. [DataField("actualDecayRate"), ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] public float ActualDecayRate; @@ -45,12 +56,13 @@ public sealed partial class HungerComponent : Component /// /// The current hunger threshold the entity is at /// + /// Any time this is modified, should be called. [DataField("currentThreshold"), ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] public HungerThreshold CurrentThreshold; /// - /// A dictionary relating HungerThreshold to the amount of needed for each one + /// A dictionary relating HungerThreshold to the amount of current hunger needed for each one /// [DataField("thresholds", customTypeSerializer: typeof(DictionarySerializer))] [AutoNetworkedField] @@ -106,19 +118,19 @@ public sealed partial class HungerComponent : Component public DamageSpecifier? StarvationDamage; /// - /// The time when the hunger will update next. + /// The time when the hunger threshold will update next. /// [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] [AutoPausedField] - public TimeSpan NextUpdateTime; + public TimeSpan NextThresholdUpdateTime; /// - /// The time between each update. + /// The time between each hunger threshold update. /// [ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] - public TimeSpan UpdateRate = TimeSpan.FromSeconds(1); + public TimeSpan ThresholdUpdateRate = TimeSpan.FromSeconds(1); } [Serializable, NetSerializable] diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index 6a6dd7af7820b9..063c1f6bb1a691 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -6,6 +6,7 @@ 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 @@ private void OnRejuvenate(EntityUid uid, HungerComponent component, RejuvenateEv SetHunger(uid, component.Thresholds[HungerThreshold.Okay], component); } + /// + /// Gets the current hunger value of the given . + /// + public float GetHunger(HungerComponent component) + { + var dt = _timing.CurTime - component.LastAuthoritativeHungerChangeTime; + var value = component.LastAuthoritativeHungerValue - (float)dt.TotalSeconds * component.ActualDecayRate; + return ClampHungerWithinThresholds(component, value); + } + /// /// Adds to the current hunger of an entity by the specified value /// @@ -82,7 +93,7 @@ public void ModifyHunger(EntityUid uid, float amount, HungerComponent? component { if (!Resolve(uid, ref component)) return; - SetHunger(uid, component.CurrentHunger + amount, component); + SetHunger(uid, GetHunger(component) + amount, component); } /// @@ -95,11 +106,23 @@ public void SetHunger(EntityUid uid, float amount, HungerComponent? component = { 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); + } + + /// + /// Sets and + /// , and dirties this entity. This "resets" the + /// starting point for 's calculation. + /// + /// The entity whose hunger will be set. + /// The value to set the entity's hunger to. + private void SetAuthoritativeHungerValue(Entity 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 @@ private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = 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 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component 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 @@ component.StarvationDamage is { } damage && /// 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 HungerThreshold GetHungerThreshold(HungerComponent component, float? food value = threshold.Value; } } + return result; } @@ -229,6 +253,13 @@ public bool TryGetStatusIconPrototype(HungerComponent component, [NotNullWhen(tr 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 override void Update(float frameTime) var query = EntityQueryEnumerator(); 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); } } } - diff --git a/Content.Shared/Sericulture/SericultureSystem.cs b/Content.Shared/Sericulture/SericultureSystem.cs index f7586cc1ec309e..8c10d0f3d05074 100644 --- a/Content.Shared/Sericulture/SericultureSystem.cs +++ b/Content.Shared/Sericulture/SericultureSystem.cs @@ -53,7 +53,10 @@ private void OnCompRemove(EntityUid uid, SericultureComponent comp, ComponentShu private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args) { if (TryComp(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 @@ private void OnSericultureDoAfter(EntityUid uid, SericultureComponent comp, Seri if (args.Cancelled || args.Handled || comp.Deleted) return; - if (TryComp(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(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; diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index f8fb3e34ce5400..d801530c6bc5c7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1686,7 +1686,6 @@ Dead: 0 baseDecayRate: 0.04 - type: Hunger - currentHunger: 25 # spawn with Okay hunger state thresholds: Overfed: 35 Okay: 25 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index a51fe522381a2b..0dc96ba6f31cbb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -448,7 +448,6 @@ Dead: 0 baseDecayRate: 0.04 - type: Hunger - currentHunger: 25 # spawn with Okay hunger state thresholds: Overfed: 35 Okay: 25