// 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;
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;
{
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;
}
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))
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;
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;
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;
/// <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]
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]
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;
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>
{
if (!Resolve(uid, ref component))
return;
- SetHunger(uid, component.CurrentHunger + amount, component);
+ SetHunger(uid, GetHunger(component) + amount, component);
}
/// <summary>
{
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)
return;
component.CurrentThreshold = calculatedHungerThreshold;
DoHungerThresholdEffects(uid, component);
- Dirty(uid, component);
}
private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component = null, bool force = false)
if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier))
{
component.ActualDecayRate = component.BaseDecayRate * modifier;
+ SetAuthoritativeHungerValue((uid, component), GetHunger(component));
}
component.LastThreshold = component.CurrentThreshold;
/// <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)
value = threshold.Value;
}
}
+
return result;
}
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);
}
}
}
-
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;
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;
Dead: 0
baseDecayRate: 0.04
- type: Hunger
- currentHunger: 25 # spawn with Okay hunger state
thresholds:
Overfed: 35
Okay: 25
Dead: 0
baseDecayRate: 0.04
- type: Hunger
- currentHunger: 25 # spawn with Okay hunger state
thresholds:
Overfed: 35
Okay: 25