using Content.Shared.Containers.ItemSlots; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Nutrition.Components; using Content.Shared.Random.Helpers; using Content.Shared.Tools.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Shared.Nutrition.EntitySystems; public sealed partial class IngestionSystem { [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly IGameTiming _timing = default!; private EntityQuery _utensilsQuery; public void InitializeUtensils() { SubscribeLocalEvent(OnAfterInteract, after: new[] { typeof(ToolOpenableSystem) }); SubscribeLocalEvent(OnGetEdibleUtensils); _utensilsQuery = GetEntityQuery(); } /// /// Clicked with utensil /// private void OnAfterInteract(Entity entity, ref AfterInteractEvent ev) { if (ev.Handled || ev.Target == null || !ev.CanReach) return; ev.Handled = TryUseUtensil(ev.User, ev.Target.Value, entity); } public bool TryUseUtensil(EntityUid user, EntityUid target, Entity utensil) { var ev = new GetUtensilsEvent(); RaiseLocalEvent(target, ref ev); //Prevents food usage with a wrong utensil if ((ev.Types & utensil.Comp.Types) == 0) { _popup.PopupClient(Loc.GetString("ingestion-try-use-wrong-utensil", ("verb", GetEdibleVerb(target)),("food", target), ("utensil", utensil.Owner)), user, user); return true; } if (!_interactionSystem.InRangeUnobstructed(user, target, popup: true)) return true; return TryIngest(user, user, target); } /// /// Attempt to break the utensil after interaction. /// /// Utensil. /// User of the utensil. public void TryBreak(Entity entity, EntityUid userUid) { if (!Resolve(entity, ref entity.Comp)) return; // TODO: Once we have predicted randomness delete this for something sane... var seed = SharedRandomExtensions.HashCodeCombine(new() {(int)_timing.CurTick.Value, GetNetEntity(entity).Id, GetNetEntity(userUid).Id }); var rand = new System.Random(seed); if (!rand.Prob(entity.Comp.BreakChance)) return; _audio.PlayPredicted(entity.Comp.BreakSound, userUid, userUid, AudioParams.Default.WithVolume(-2f)); // Not prediced because no random predicted PredictedDel(entity.Owner); } /// /// Checks if we have the utensils required to eat a certain food item. /// /// Entity that is trying to eat. /// The types of utensils we need. /// The utensils needed to eat the food item. /// True if we are able to eat the item. public bool TryGetUtensils(Entity entity, EntityUid food, out List utensils) { var ev = new GetUtensilsEvent(); RaiseLocalEvent(food, ref ev); return TryGetUtensils(entity, ev.Types, ev.RequiredTypes, out utensils); } public bool TryGetUtensils(Entity entity, UtensilType types, UtensilType requiredTypes, out List utensils) { utensils = new List(); var required = requiredTypes != UtensilType.None; // Why are we even here? Just to suffer? if (types == UtensilType.None) return true; // If you don't have hands you can eat anything I guess. if (!Resolve(entity, ref entity.Comp, false)) // You aren't allowed to eat with your hands in this hellish dystopia. return true; var usedTypes = UtensilType.None; foreach (var item in _hands.EnumerateHeld(entity)) { // Is utensil? if (!_utensilsQuery.TryComp(item, out var utensil)) continue; // Do we have a new and unused utensil type? if ((utensil.Types & types) == 0 || (usedTypes & utensil.Types) == utensil.Types) continue; // Add to used list usedTypes |= utensil.Types; utensils.Add(item); } // If "required" field is set, try to block eating without proper utensils used if (!required || (usedTypes & requiredTypes) == requiredTypes) return true; _popup.PopupClient(Loc.GetString("ingestion-you-need-to-hold-utensil", ("utensil", requiredTypes ^ usedTypes)), entity, entity); return false; } /// /// Checks if you have the required utensils based on a list of types. /// Note it is assumed if you're calling this method that you need utensils. /// /// The entity doing the action who has the utensils. /// The types of utensils we need. /// Returns true if we have the utensils we need. public bool HasRequiredUtensils(EntityUid entity, UtensilType types) { return TryGetUtensils(entity, types, types, out _); } private void OnGetEdibleUtensils(Entity entity, ref GetUtensilsEvent args) { if (entity.Comp.Utensil == UtensilType.None) return; if (entity.Comp.UtensilRequired) args.AddRequiredTypes(entity.Comp.Utensil); else args.Types |= entity.Comp.Utensil; } }