using System.Linq; using Content.Shared.Chemistry.Components; using Content.Shared.Clothing; using Content.Shared.Containers.ItemSlots; using Content.Shared.Fluids.Components; using Content.Shared.Interaction.Components; using Content.Shared.Inventory; using Content.Shared.Nutrition.Components; using Content.Shared.Storage; namespace Content.Shared.Nutrition.EntitySystems; public sealed partial class IngestionSystem { [Dependency] private readonly OpenableSystem _openable = default!; public void InitializeBlockers() { SubscribeLocalEvent(OnUnremovableIngestion); SubscribeLocalEvent(OnBlockerMaskToggled); SubscribeLocalEvent(OnIngestionBlockerAttempt); SubscribeLocalEvent>(OnIngestionBlockerAttempt); // Edible Event SubscribeLocalEvent(OnEdible); SubscribeLocalEvent(OnStorageEdible); SubscribeLocalEvent(OnItemSlotsEdible); SubscribeLocalEvent(OnOpenableEdible); // Digestion Events SubscribeLocalEvent(OnEdibleIsDigestible); SubscribeLocalEvent(OnDrainableIsDigestible); SubscribeLocalEvent(OnPuddleIsDigestible); SubscribeLocalEvent(OnPillBeforeEaten); } private void OnUnremovableIngestion(Entity entity, ref IngestibleEvent args) { // If we can't remove it we probably shouldn't be able to eat it. // TODO: Separate glue and Unremovable component. args.Cancelled = true; } private void OnBlockerMaskToggled(Entity ent, ref ItemMaskToggledEvent args) { ent.Comp.Enabled = !args.Mask.Comp.IsToggled; } private void OnIngestionBlockerAttempt(Entity entity, ref IngestionAttemptEvent args) { if (!args.Cancelled && entity.Comp.Enabled) args.Cancelled = true; } /// /// Block ingestion attempts based on the equipped mask or head-wear /// private void OnIngestionBlockerAttempt(Entity entity, ref InventoryRelayedEvent args) { if (args.Args.Cancelled || !entity.Comp.Enabled) return; args.Args.Cancelled = true; args.Args.Blocker = entity; } private void OnEdible(Entity entity, ref EdibleEvent args) { if (args.Cancelled || args.Solution != null) return; if (entity.Comp.UtensilRequired && !HasRequiredUtensils(args.User, entity.Comp.Utensil)) { args.Cancelled = true; return; } // Check this last if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out args.Solution) || IsEmpty(entity) && !entity.Comp.DestroyOnEmpty) { args.Cancelled = true; _popup.PopupClient(Loc.GetString("ingestion-try-use-is-empty", ("entity", entity)), entity, args.User); return; } // Time is additive because I said so. args.Time += entity.Comp.Delay; } private void OnStorageEdible(Entity ent, ref EdibleEvent args) { if (args.Cancelled) return; if (!ent.Comp.Container.ContainedEntities.Any()) return; args.Cancelled = true; _popup.PopupClient(Loc.GetString("edible-has-used-storage", ("food", ent), ("verb", GetEdibleVerb(ent.Owner))), args.User, args.User); } private void OnItemSlotsEdible(Entity ent, ref EdibleEvent args) { if (args.Cancelled) return; if (!ent.Comp.Slots.Any(slot => slot.Value.HasItem)) return; args.Cancelled = true; _popup.PopupClient(Loc.GetString("edible-has-used-storage", ("food", ent), ("verb", GetEdibleVerb(ent.Owner))), args.User, args.User); } private void OnOpenableEdible(Entity ent, ref EdibleEvent args) { if (_openable.IsClosed(ent, args.User, ent.Comp)) args.Cancelled = true; } private void OnEdibleIsDigestible(Entity ent, ref IsDigestibleEvent args) { if (ent.Comp.RequireDead && _mobState.IsAlive(ent)) return; args.AddDigestible(ent.Comp.RequiresSpecialDigestion); } /// /// Both of these assume that having this component means there's nothing stopping you from slurping up /// pure reagent juice with absolutely nothing to stop you. /// private void OnDrainableIsDigestible(Entity ent, ref IsDigestibleEvent args) { args.UniversalDigestion(); } private void OnPuddleIsDigestible(Entity ent, ref IsDigestibleEvent args) { args.UniversalDigestion(); } /// /// I mean you have to eat the *whole* pill no? /// private void OnPillBeforeEaten(Entity ent, ref BeforeIngestedEvent args) { if (args.Cancelled || args.Solution is not { } sol) return; if (args.TryNewMinimum(sol.Volume)) return; args.Cancelled = true; } }