mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
* ready * oop forgot the HUD stuff * woooo examinable generalized tooooo * oop I forgot to rename the files * bungled the merge, I guess * this is called hubris * Verin's comments * PR comments how did I use `ProtoId<> Hunger` in more than a few spots but then just say "thirst can stay a literal :^)"? also changed a few spots to my beloved ``` functionInvocation( one param per line ) ``` ``` * Satiation effect condition now with ADDED FUNCTIONALITY?? :o * tfw you trust the markov chain recommendation because long generic type names are tough q-q * TFW YOU TRUST THE MARKOV CHAIN BUT IT DOESN'T UNDERSTAND THE DIFFERING SEMANTICS OF MAX AND MIN AHHHHH * tfw you think to yourself "i need to fix the effects", but then forget to and the code doesn't even compile. * centralize satiation constants * other small comments * The enum is dead. But at what cost? * oop * like 12 more ooperses * green checks are mine by right; I have performed the rites; I have done my part; you will not deny me this succor * oops I changed the semantics from `<=` to just `<` and didn't update the tests :) * serialization has bested me :agony: also PR comments * never trust the auto merge :agony: * literally just this * force engine back to master because idk why it's different * fix randomness and update master * submodule fuckery * fall before my might * okay I ran the linter, ARE YOU HAPPY?? * cursed ovis, trouble me no more * protoman stuff * inb4 linter bonks me * break non-critical stuff from the main satiation proto *  inb4 this breaks something somewhere lmao * my two greatest PRs COLLIDE I wonder why robust says it's changed :hmmclueless * sanity is at an all-time low * serialization, I guess * I don't believe you * field deltas on class \w*satiation\w*components * PR comments * 我做泥團子 😔 * doc + kill one property that's trivially accessible after the threshold caching changes * fixes --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: AJCM-git <ajcm_mail@disroot.org>
253 lines
9.2 KiB
C#
253 lines
9.2 KiB
C#
using Content.Shared.Alert;
|
|
using Content.Shared.Nutrition.Components;
|
|
using Content.Shared.Nutrition.Prototypes;
|
|
using Content.Shared.Random.Helpers;
|
|
using Content.Shared.Rejuvenate;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Shared.Nutrition.EntitySystems;
|
|
|
|
/// <summary>
|
|
/// This system manages the <see cref="SatiationComponent"/>. Broadly, what that means is that it handles the decay of
|
|
/// satiations in <see cref="Update"/>, and external changes to satiations through accessors like
|
|
/// <see cref="ModifyValue"/>.
|
|
/// </summary>
|
|
public sealed partial class SatiationSystem : EntitySystem
|
|
{
|
|
[Dependency] private AlertsSystem _alerts = default!;
|
|
[Dependency] private IGameTiming _timing = default!;
|
|
|
|
[Dependency] private EntityQuery<SatiationComponent> _satiationQuery;
|
|
|
|
/// <summary>
|
|
/// The ID of the <c>Hunger</c> satiation type. Provided because it is so commonly used in Content.
|
|
/// </summary>
|
|
public static readonly ProtoId<SatiationTypePrototype> Hunger = "Hunger";
|
|
|
|
/// <summary>
|
|
/// The ID of the <c>Thirst</c> satiation type. Provided because it is so commonly used in Content.
|
|
/// </summary>
|
|
public static readonly ProtoId<SatiationTypePrototype> Thirst = "Thirst";
|
|
|
|
/// <inheritdoc/>
|
|
public override void Update(float frameTime)
|
|
{
|
|
var query = EntityQueryEnumerator<SatiationComponent>();
|
|
while (query.MoveNext(out var uid, out var component))
|
|
{
|
|
Entity<SatiationComponent> entity = (uid, component);
|
|
foreach (var (satiation, proto) in GetSatiationsAndTypes(entity))
|
|
{
|
|
if (_timing.CurTime >= satiation.NextAlertUpdateTime)
|
|
{
|
|
UpdateAlerts(entity, satiation, proto);
|
|
}
|
|
|
|
if (_timing.CurTime >= satiation.NextDecayRateModUpdateTime)
|
|
{
|
|
SetAuthoritativeValue(entity, satiation, proto, CalculateCurrentValue(satiation, proto));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets starting satiation values.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnMapInit(Entity<SatiationComponent> entity, ref MapInitEvent args)
|
|
{
|
|
foreach (var (type, satiation) in entity.Comp.Satiations)
|
|
{
|
|
if (!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
|
continue;
|
|
|
|
satiation.SatiationType = type;
|
|
|
|
// TODO: Replace with RandomPredicted once the engine PR is merged
|
|
var rand = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(entity));
|
|
var value = rand.NextFloat(proto.StartingValueMinimum, proto.StartingValueMaximum);
|
|
|
|
SetAuthoritativeValue(entity, satiation, proto, value);
|
|
}
|
|
|
|
DirtyField(entity.AsNullable(), nameof(SatiationComponent.Satiations));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears alerts.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnShutdown(Entity<SatiationComponent> entity, ref ComponentShutdown args)
|
|
{
|
|
foreach (var (_, proto) in GetSatiationsAndTypes(entity))
|
|
{
|
|
_alerts.ClearAlertCategory(entity.Owner, proto.AlertCategory);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets all satiations to their maximums.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnRejuvenate(Entity<SatiationComponent> entity, ref RejuvenateEvent args)
|
|
{
|
|
foreach (var type in entity.Comp.Satiations.Keys)
|
|
{
|
|
SetValue(entity, type, satiationValue: int.MaxValue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This helper resolves <paramref name="type"/> and returns the corresponding <see cref="Satiation"/> from
|
|
/// <paramref name="satiations"/> along with its <see cref="SatiationPrototype"/>.
|
|
/// Returns null if the prototype fails to resolve, or if the component does not have the specified satiation.
|
|
/// </summary>
|
|
private (Satiation Satiation, SatiationPrototype Proto)? GetAndResolveSatiationOfType(
|
|
SatiationComponent satiations,
|
|
[ForbidLiteral] ProtoId<SatiationTypePrototype> type
|
|
)
|
|
{
|
|
if (satiations.GetOrNull(type) is not { } satiation ||
|
|
!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
|
return null;
|
|
|
|
return (satiation, proto);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to <see cref="GetAndResolveSatiationOfType"/>, this helper returns all <see cref="Satiation"/>s on
|
|
/// <paramref name="satiations"/> along with their corresponding <see cref="SatiationPrototype"/>s.
|
|
/// </summary>
|
|
private IEnumerable<(Satiation, SatiationPrototype)> GetSatiationsAndTypes(SatiationComponent satiations)
|
|
{
|
|
foreach (var satiation in satiations.Satiations.Values)
|
|
{
|
|
if (!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
|
continue;
|
|
|
|
yield return (satiation, proto);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the current value of the given <see cref="Satiation"/> by linearly extrapolating the change of the
|
|
/// value based on <see cref="Satiation.LastAuthoritativeValue"/>, <see cref="Satiation.LastAuthoritativeChangeTime"/>
|
|
/// and <see cref="Satiation.ActualDecayRate"/>
|
|
/// </summary>
|
|
private float CalculateCurrentValue(Satiation satiation, SatiationPrototype proto)
|
|
{
|
|
var dt = _timing.CurTime - satiation.LastAuthoritativeChangeTime;
|
|
var value = satiation.LastAuthoritativeValue - (float)dt.TotalSeconds * satiation.ActualDecayRate;
|
|
return proto.ClampSatiationWithinThresholds(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The beating heart of this system, this function sets the given <paramref name="entity"/>'s
|
|
/// <paramref name="satiation"/> to <paramref name="value"/>. This involves
|
|
/// updating obvious fields on the <see cref="SatiationComponent"/>, but since changes to the value also affect the
|
|
/// current threshold, we need to consider all of the effects that has as well.
|
|
/// </summary>
|
|
private void SetAuthoritativeValue(
|
|
Entity<SatiationComponent> entity,
|
|
Satiation satiation,
|
|
SatiationPrototype proto,
|
|
float value
|
|
)
|
|
{
|
|
// Update the authoritative value itself.
|
|
satiation.LastAuthoritativeChangeTime = _timing.CurTime;
|
|
satiation.LastAuthoritativeValue = proto.ClampSatiationWithinThresholds(value);
|
|
|
|
if (!TryGetValueByThreshold(
|
|
entity,
|
|
satiation.SatiationType,
|
|
proto.DecayModifiers,
|
|
out var currentDecayMod,
|
|
out var nextLowerThreshold
|
|
))
|
|
{
|
|
currentDecayMod = 1f;
|
|
}
|
|
|
|
satiation.ActualDecayRate = proto.BaseDecayRate * currentDecayMod;
|
|
|
|
if (nextLowerThreshold is { } t)
|
|
{
|
|
satiation.NextDecayRateModUpdateTime =
|
|
_timing.CurTime + TimeSpan.FromSeconds((value - t) / satiation.ActualDecayRate);
|
|
}
|
|
else
|
|
{
|
|
satiation.NextDecayRateModUpdateTime = null;
|
|
}
|
|
|
|
var updateEvent = new SatiationUpdateEvent(satiation.SatiationType);
|
|
RaiseLocalEvent(entity, ref updateEvent);
|
|
|
|
DirtyField(entity.AsNullable(), nameof(SatiationComponent.Satiations));
|
|
}
|
|
|
|
/// <remarks>
|
|
/// This is basically a reimplementation of <see cref="BaseSatiationEffectSystem{TComp,T}.OnSatiationUpdate"/>.
|
|
/// </remarks>
|
|
[SubscribeLocalEvent]
|
|
private void UpdateAlertsOnSatiationUpdated(Entity<SatiationComponent> entity, ref SatiationUpdateEvent args)
|
|
{
|
|
if (entity.Comp.GetOrNull(args.Type) is not { } satiation ||
|
|
!ProtoMan.Resolve(satiation.Prototype, out var proto))
|
|
return;
|
|
|
|
UpdateAlerts(entity, satiation, proto);
|
|
}
|
|
|
|
private void UpdateAlerts(
|
|
Entity<SatiationComponent> entity,
|
|
Satiation satiation,
|
|
SatiationPrototype proto
|
|
)
|
|
{
|
|
if (TryGetValueByThreshold(
|
|
entity,
|
|
satiation.SatiationType,
|
|
proto.Alerts,
|
|
out var result,
|
|
out var nextLowerThreshold))
|
|
{
|
|
if (result is { } alert)
|
|
{
|
|
_alerts.ShowAlert(entity.Owner, alert);
|
|
satiation.NextAlertUpdateTime = nextLowerThreshold is { } lower
|
|
? GetTimeToDecay(entity, satiation.SatiationType, lower)
|
|
: null;
|
|
}
|
|
else
|
|
{
|
|
_alerts.ClearAlertCategory(entity.Owner, proto.AlertCategory);
|
|
satiation.NextAlertUpdateTime = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_alerts.ClearAlertCategory(entity.Owner, proto.AlertCategory);
|
|
satiation.NextAlertUpdateTime = null;
|
|
}
|
|
}
|
|
|
|
public TimeSpan? GetTimeToDecay(Entity<SatiationComponent> entity,
|
|
ProtoId<SatiationTypePrototype> type,
|
|
int threshold
|
|
)
|
|
{
|
|
if (GetValueOrNull(entity, type) is not { } value ||
|
|
entity.Comp.GetOrNull(type) is not { } satiation)
|
|
return null;
|
|
|
|
return _timing.CurTime + TimeSpan.FromSeconds((value - threshold) / satiation.ActualDecayRate);
|
|
}
|
|
}
|
|
|
|
// Best effort on change to authoritative value or decay rate
|
|
[ByRefEvent]
|
|
public readonly record struct SatiationUpdateEvent(ProtoId<SatiationTypePrototype> Type);
|