Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce network burden of the hunger system #32986

Merged
merged 9 commits into from
Dec 18, 2024
4 changes: 2 additions & 2 deletions Content.Server/Animals/Systems/EggLayerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace Content.Server.Animals.Systems;

/// <summary>
/// Gives ability to produce eggs, produces endless if the
/// Gives ability to produce eggs, produces endless if the
/// owner has no HungerComponent
/// </summary>
public sealed class EggLayerSystem : EntitySystem
Expand Down Expand Up @@ -79,7 +79,7 @@ public bool TryLayEgg(EntityUid uid, EggLayerComponent? egglayer)
// 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;
Expand Down
4 changes: 2 additions & 2 deletions Content.Server/EntityEffects/EffectConditions/TotalHunger.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<HungerSystem>().GetHunger(hunger);
if (total > Min && total < Max)
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public bool TryGetValidOccupant(EntityUid uid, [NotNullWhen(true)] out EntityUid
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))
Expand Down
4 changes: 2 additions & 2 deletions Content.Server/RatKing/RatKingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
32 changes: 22 additions & 10 deletions Content.Shared/Nutrition/Components/HungerComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
52 changes: 41 additions & 11 deletions Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -72,6 +73,16 @@ private void OnRejuvenate(EntityUid uid, HungerComponent component, RejuvenateEv
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>
Expand All @@ -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);
}

/// <summary>
Expand All @@ -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);
}

/// <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)
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -167,7 +190,7 @@ component.StarvationDamage is { } damage &&
/// <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)
Expand All @@ -178,6 +201,7 @@ public HungerThreshold GetHungerThreshold(HungerComponent component, float? food
value = threshold.Value;
}
}

return result;
}

Expand Down Expand Up @@ -229,20 +253,26 @@ 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);

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);
}
}
}

13 changes: 10 additions & 3 deletions Content.Shared/Sericulture/SericultureSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ private void OnCompRemove(EntityUid uid, SericultureComponent comp, ComponentShu
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;
Expand All @@ -76,8 +79,12 @@ private void OnSericultureDoAfter(EntityUid uid, SericultureComponent comp, Seri
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;
Expand Down
1 change: 0 additions & 1 deletion Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,6 @@
Dead: 0
baseDecayRate: 0.04
- type: Hunger
currentHunger: 25 # spawn with Okay hunger state
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name change definitely sucks, but I don't know a way around it.

It would be good for currentHunger or startingHunger to be the field in the yaml, but it would, I think, not be good to have either of those names show up in-game in something like ViewVariables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name change definitely sucks, but I don't know a way around it.

It would be good for currentHunger or startingHunger to be the field in the yaml, but it would, I think, not be good to have either of those names show up in-game in something like ViewVariables.

[ViewVariables(VVAccess.ReadOnly)] it.

Also I'm looking at the code, does setting this data field from YAML even work? It looks like it would always get replaced by the randomized amount selected in OnMapInit. (Note that this doesn't appear to be an issue with your PR, just an observation).

Copy link
Contributor Author

@Centronias Centronias Oct 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this was changed with the last rewrite of all of this:
https://github.com/space-wizards/space-station-14/pull/14939/files#diff-7ff03c169eb454aed40cb0979bd1d81f65ce0db09590d25191682c783e7ac344L131
(PR line links never work for me, but it's this:

)

Should I get rid of the fields in the yaml, since they don't do anything now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your choice I suppose

thresholds:
Overfed: 35
Okay: 25
Expand Down
1 change: 0 additions & 1 deletion Resources/Prototypes/Entities/Mobs/NPCs/space.yml
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,6 @@
Dead: 0
baseDecayRate: 0.04
- type: Hunger
currentHunger: 25 # spawn with Okay hunger state
thresholds:
Overfed: 35
Okay: 25
Expand Down
Loading