CatStation/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs
Centronias 8ad82dc665
Refactor hunger and thirst systems (#34166)
* 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

* ![live crundle reaction](https://preview.redd.it/xh68xqcz518g1.png?width=677&format=png&auto=webp&s=79ba10fe49ec3f8306217732321a7c5429bdfb14)

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>
2026-08-08 22:44:24 +00:00

165 lines
5.5 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Nutrition.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Storage.Components;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Nutrition.Prototypes;
using Content.Shared.Power;
using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Nutrition.EntitySystems;
/// <summary>
/// This handles logic and interactions relating to <see cref="FatExtractorComponent"/>
/// </summary>
public sealed partial class FatExtractorSystem : EntitySystem
{
[Dependency] private IGameTiming _timing = default!;
[Dependency] private EmagSystem _emag = default!;
[Dependency] private SatiationSystem _satiation = default!;
[Dependency] private SharedAppearanceSystem _appearance = default!;
[Dependency] private SharedAudioSystem _audio = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<FatExtractorComponent, GotEmaggedEvent>(OnGotEmagged);
SubscribeLocalEvent<FatExtractorComponent, StorageAfterCloseEvent>(OnClosed);
SubscribeLocalEvent<FatExtractorComponent, StorageAfterOpenEvent>(OnOpen);
SubscribeLocalEvent<FatExtractorComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
if (_emag.CheckFlag(uid, EmagType.Interaction))
return;
args.Handled = true;
}
private void OnClosed(EntityUid uid, FatExtractorComponent component, ref StorageAfterCloseEvent args)
{
StartProcessing(uid, component);
}
private void OnOpen(EntityUid uid, FatExtractorComponent component, ref StorageAfterOpenEvent args)
{
StopProcessing(uid, component);
}
private void OnPowerChanged(EntityUid uid, FatExtractorComponent component, ref PowerChangedEvent args)
{
if (!args.Powered)
StopProcessing(uid, component);
}
public void StartProcessing(EntityUid uid, FatExtractorComponent? component = null, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref component, ref storage))
return;
if (component.Processing)
return;
if (!this.IsPowered(uid, EntityManager))
return;
if (!TryGetValidOccupant(uid, out _, component, storage))
return;
component.Processing = true;
_appearance.SetData(uid, FatExtractorVisuals.Processing, true);
component.Stream = _audio.PlayPvs(component.ProcessSound, uid)?.Entity;
component.NextUpdate = _timing.CurTime + component.UpdateTime;
}
public void StopProcessing(EntityUid uid, FatExtractorComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (!component.Processing)
return;
component.Processing = false;
_appearance.SetData(uid, FatExtractorVisuals.Processing, false);
component.Stream = _audio.Stop(component.Stream);
}
public bool TryGetValidOccupant(EntityUid uid,
[NotNullWhen(true)] out Entity<SatiationComponent>? occupant,
FatExtractorComponent? component = null,
EntityStorageComponent? storage = null)
{
occupant = null;
if (!Resolve(uid, ref component, ref storage))
return false;
var firstEntity = storage.Contents.ContainedEntities.FirstOrDefault();
if (firstEntity is not { Valid: true })
{
return false;
}
if (!TryComp<SatiationComponent>(firstEntity, out var satiation))
return false;
Entity<SatiationComponent> entity = (firstEntity, satiation);
if (_satiation.GetValueOrNull(entity, SatiationSystem.Hunger) < component.NutritionPerSecond &&
!_emag.CheckFlag(uid, EmagType.Interaction))
return false;
if (_satiation.IsValueInRange(entity, SatiationSystem.Hunger, below: component.MinHungerThreshold) &&
!HasComp<EmaggedComponent>(uid))
return false;
occupant = entity;
return true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<FatExtractorComponent, EntityStorageComponent>();
while (query.MoveNext(out var uid, out var fat, out var storage))
{
if (TryGetValidOccupant(uid, out var occupant, fat, storage))
{
if (!fat.Processing)
StartProcessing(uid, fat, storage);
}
else
{
StopProcessing(uid, fat);
continue;
}
if (!fat.Processing)
continue;
if (_timing.CurTime < fat.NextUpdate)
continue;
fat.NextUpdate += fat.UpdateTime;
_satiation.ModifyValue(occupant.Value, SatiationSystem.Hunger, -fat.NutritionPerSecond);
fat.NutrientAccumulator += fat.NutritionPerSecond;
if (fat.NutrientAccumulator >= fat.NutrientPerMeat)
{
fat.NutrientAccumulator -= fat.NutrientPerMeat;
Spawn(fat.MeatPrototype, Transform(uid).Coordinates);
}
}
}
}