using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Shared.Nutrition.EntitySystems; /// /// This abstract system provides a convenient interface for implementing effects which react to changes in /// thresholds. /// /// /// uses this to maintain a damage descriptor which changes depending on the entity's /// satiation values. When the satiation values change, the damage descriptor is automatically updated by this system. /// /// The type of component this system interacts with /// /// The type of value this system maintains. It must be contained within a /// accessible via TComp. /// /// Note that this is not related to public abstract partial class BaseSatiationEffectSystem : EntitySystem where TComp : Component { [Dependency] private SatiationSystem _satiation = default!; [Dependency] private EntityQuery _satiationQuery; [Dependency] private IGameTiming _timing = default!; /// /// How to access via a . /// protected abstract Dictionary, SatiationThresholds> GetThresholds(TComp comp); /// /// The default value to set our maintained value to in the case that our current satiation /// is outside of . /// protected abstract T DefaultValue(); /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnSatiationUpdate); } /// // Updates maintained values when reaching the projected threshold time. public override void Update(float frameTime) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var ent, out var comp, out var satiation)) { foreach (var (type, thresholds) in GetThresholds(comp)) { if (_timing.CurTime < thresholds.ProjectedThresholdChangeTime) continue; UpdateSatiation((ent, comp), satiation, type); } } } [MustCallBase] protected virtual void OnMapInit(Entity entity, ref MapInitEvent args) { // Make sure we have a satiation component. Realistically, this just exists to cause test failures if an entity // with `TComp` doesn't have a `SatiationComponent`. var comp = EnsureComp(entity); foreach (var type in GetThresholds(entity.Comp).Keys) { UpdateSatiation(entity, comp, type); } } // If a satiation value is changed directly, we react and update our maintained value. [MustCallBase] protected void OnSatiationUpdate(Entity entity, ref SatiationUpdateEvent args) { if (!_satiationQuery.TryComp(entity, out var comp)) return; UpdateSatiation(entity, comp, args.Type); } /// /// Updates the maintained based on the entity's satiation. /// private void UpdateSatiation(Entity entity, SatiationComponent comp, ProtoId type) { // Get the current satiation value... if (!GetThresholds(entity.Comp).TryGetValue(type, out var thresholds)) return; // ... and then use it to get the appropriate threshold T value. if (_satiation.TryGetValueByThreshold( (entity, comp), type, thresholds.Thresholds, out var result, out var nextLowerThreshold)) { thresholds.Current = result ?? DefaultValue(); // Predict when our satiation will decay to the next threshold down. thresholds.ProjectedThresholdChangeTime = nextLowerThreshold is { } lower ? _satiation.GetTimeToDecay((entity, comp), type, lower) : null; } else { thresholds.Current = DefaultValue(); thresholds.ProjectedThresholdChangeTime = null; } Dirty(entity); AfterSatiationUpdate(entity); } /// /// This function is called after completes its work maintaining . /// protected virtual void AfterSatiationUpdate(Entity entity) { } }