diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 8bd2252b25..cc5af10676 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -34,6 +34,7 @@ namespace Content.Server.Nutrition.EntitySystems [UsedImplicitly] public class DrinkSystem : EntitySystem { + [Dependency] private readonly FoodSystem _foodSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; @@ -262,6 +263,14 @@ namespace Content.Server.Nutrition.EntitySystems return true; } + if (_foodSystem.IsMouthBlocked(userUid, out var blocker)) + { + var name = EntityManager.GetComponent(blocker.Value).EntityName; + _popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)), + userUid, Filter.Entities(userUid)); + return true; + } + var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable); var drain = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount); var firstStomach = stomachs.FirstOrNull( @@ -327,6 +336,14 @@ namespace Content.Server.Nutrition.EntitySystems return true; } + if (_foodSystem.IsMouthBlocked(targetUid, out var blocker)) + { + var name = EntityManager.GetComponent(blocker.Value).EntityName; + _popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)), + userUid, Filter.Entities(userUid)); + return true; + } + EntityManager.TryGetComponent(userUid, out MetaDataComponent? meta); var userName = meta?.EntityName ?? string.Empty; diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index f82c744399..e85f1df558 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -25,6 +25,10 @@ using Robust.Shared.Player; using System.Collections.Generic; using System.Linq; using Robust.Shared.Utility; +using Content.Server.Inventory.Components; +using Content.Shared.Inventory; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Tag; namespace Content.Server.Nutrition.EntitySystems { @@ -135,6 +139,14 @@ namespace Content.Server.Nutrition.EntitySystems !_bodySystem.TryGetComponentsOnMechanisms(userUid, out var stomachs, body)) return false; + if (IsMouthBlocked(userUid, out var blocker)) + { + var name = EntityManager.GetComponent(blocker.Value).EntityName; + _popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)), + userUid, Filter.Entities(userUid)); + return true; + } + var usedUtensils = new List(); if (!TryGetRequiredUtensils(userUid, component, out var utensils)) @@ -252,6 +264,14 @@ namespace Content.Server.Nutrition.EntitySystems return true; } + if (IsMouthBlocked(targetUid, out var blocker)) + { + var name = EntityManager.GetComponent(blocker.Value).EntityName; + _popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)), + userUid, Filter.Entities(userUid)); + return true; + } + if (!TryGetRequiredUtensils(userUid, food, out var utensils)) return true; @@ -344,6 +364,9 @@ namespace Content.Server.Nutrition.EntitySystems if (!Resolve(uid, ref food) || !Resolve(target, ref body, false)) return; + if (IsMouthBlocked(target, out _)) + return; + if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var foodSolution)) return; @@ -367,7 +390,9 @@ namespace Content.Server.Nutrition.EntitySystems _logSystem.Add(LogType.ForceFeed, $"{edible} was thrown into the mouth of {targetEntity}"); else _logSystem.Add(LogType.ForceFeed, $"{userEntity} threw {edible} into the mouth of {targetEntity}"); - _popupSystem.PopupEntity(Loc.GetString(food.EatMessage), target, Filter.Entities(target)); + + var filter = (user == null) ? Filter.Entities(target) : Filter.Entities(target, user.Value); + _popupSystem.PopupEntity(Loc.GetString(food.EatMessage), target, filter); foodSolution.DoEntityReaction(uid, ReactionMethod.Ingestion); _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.OwnerUid, foodSolution, firstStomach.Value.Comp); @@ -421,6 +446,39 @@ namespace Content.Server.Nutrition.EntitySystems { args.Food.InUse = false; } + + /// + /// Is an entity's mouth accessible, or is it blocked by something like a mask? Does not actually check if + /// the user has a mouth. Body system when? + /// + public bool IsMouthBlocked(EntityUid uid, [NotNullWhen(true)] out EntityUid? blockingEntity, + InventoryComponent? inventory = null) + { + blockingEntity = null; + + if (!Resolve(uid, ref inventory, false)) + return false; + + // check masks + if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.MASK, out ItemComponent? mask)) + { + // For now, lets just assume that any masks always covers the mouth + // TODO MASKS if the ability is added to raise/lower masks, this needs to be updated. + blockingEntity = mask.OwnerUid; + return true; + } + + // check helmets. Note that not all helmets cover the face. + if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.HEAD, out ItemComponent? head) && + EntityManager.TryGetComponent(head.OwnerUid, out TagComponent tag) && + tag.HasTag("ConcealsFace")) + { + blockingEntity = head.OwnerUid; + return true; + } + + return false; + } } public sealed class ForceFeedEvent : EntityEventArgs diff --git a/Resources/Locale/en-US/nutrition/components/food-component.ftl b/Resources/Locale/en-US/nutrition/components/food-component.ftl index cd56a690e3..48c595cb39 100644 --- a/Resources/Locale/en-US/nutrition/components/food-component.ftl +++ b/Resources/Locale/en-US/nutrition/components/food-component.ftl @@ -7,6 +7,8 @@ food-you-need-to-hold-utensil = You need to be holding a {$utensil} to eat that! food-nom = Nom food-swallow = You swallow the {$food}. +food-system-remove-mask = You need to take off the {$entity} first. + ## System food-system-you-cannot-eat-any-more = You can't eat any more! diff --git a/Resources/Prototypes/Entities/Clothing/Head/base.yml b/Resources/Prototypes/Entities/Clothing/Head/base.yml index e9e0dbfb5b..1fd8f34843 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base.yml @@ -41,6 +41,9 @@ Piercing: 0.95 Heat: 0.90 Radiation: 0.25 + - type: Tag + tags: + - ConcealsFace - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index b314dac781..80f6d6c27d 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -8,6 +8,9 @@ sprite: Clothing/Head/Helmets/bombsuit.rsi - type: Clothing sprite: Clothing/Head/Helmets/bombsuit.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -19,6 +22,9 @@ sprite: Clothing/Head/Helmets/cosmonaut.rsi - type: Clothing sprite: Clothing/Head/Helmets/cosmonaut.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -30,6 +36,9 @@ sprite: Clothing/Head/Helmets/cult.rsi - type: Clothing sprite: Clothing/Head/Helmets/cult.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -70,6 +79,9 @@ sprite: Clothing/Head/Helmets/light_riot.rsi - type: Clothing sprite: Clothing/Head/Helmets/light_riot.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -81,6 +93,9 @@ sprite: Clothing/Head/Helmets/scaf.rsi - type: Clothing sprite: Clothing/Head/Helmets/scaf.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -92,6 +107,9 @@ sprite: Clothing/Head/Helmets/spaceninja.rsi - type: Clothing sprite: Clothing/Head/Helmets/spaceninja.rsi + - type: Tag + tags: + - ConcealsFace # well only partially? - type: entity parent: ClothingHeadBase @@ -103,6 +121,9 @@ sprite: Clothing/Head/Helmets/syndicate.rsi - type: Clothing sprite: Clothing/Head/Helmets/syndicate.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -114,6 +135,9 @@ sprite: Clothing/Head/Helmets/templar.rsi - type: Clothing sprite: Clothing/Head/Helmets/templar.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -136,3 +160,6 @@ sprite: Clothing/Head/Helmets/wizardhelm.rsi - type: Clothing sprite: Clothing/Head/Helmets/wizardhelm.rsi + - type: Tag + tags: + - ConcealsFace \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Head/misc.yml b/Resources/Prototypes/Entities/Clothing/Head/misc.yml index 63e2ac67ed..0a4d15f11b 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml @@ -30,6 +30,9 @@ sprite: Clothing/Head/Misc/chickenhead.rsi - type: Clothing sprite: Clothing/Head/Misc/chickenhead.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -52,6 +55,9 @@ sprite: Clothing/Head/Misc/pumpkin.rsi - type: Clothing sprite: Clothing/Head/Misc/pumpkin.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -74,6 +80,9 @@ sprite: Clothing/Head/Misc/richard.rsi - type: Clothing sprite: Clothing/Head/Misc/richard.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -85,6 +94,9 @@ sprite: Clothing/Head/Misc/skubhead.rsi - type: Clothing sprite: Clothing/Head/Misc/skubhead.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -96,6 +108,9 @@ sprite: Clothing/Head/Misc/xenom.rsi - type: Clothing sprite: Clothing/Head/Misc/xenom.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase @@ -107,6 +122,9 @@ sprite: Clothing/Head/Misc/xenos.rsi - type: Clothing sprite: Clothing/Head/Misc/xenos.rsi + - type: Tag + tags: + - ConcealsFace - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Clothing/Head/welding.yml b/Resources/Prototypes/Entities/Clothing/Head/welding.yml index 7b57503afb..375f3ecebd 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/welding.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/welding.yml @@ -1,5 +1,15 @@ - type: entity parent: ClothingHeadBase + id: WeldingMaskBase + name: welding mask + abstract: true + components: + - type: Tag + tags: + - ConcealsFace + +- type: entity + parent: WeldingMaskBase id: ClothingHeadHatWelding name: welding mask description: A head-mounted face cover designed to protect the wearer completely from space-arc eye. @@ -10,7 +20,7 @@ sprite: Clothing/Head/Welding/welding.rsi - type: entity - parent: ClothingHeadBase + parent: WeldingMaskBase id: ClothingHeadHatWeldingMaskFlame name: flame welding mask description: A painted welding helmet, this one has flames on it. @@ -21,7 +31,7 @@ sprite: Clothing/Head/Welding/flame_welding_mask.rsi - type: entity - parent: ClothingHeadBase + parent: WeldingMaskBase id: ClothingHeadHatWeldingMaskFlameBlue name: blue-flame welding mask description: A painted welding helmet, this one has blue flames on it. @@ -32,7 +42,7 @@ sprite: Clothing/Head/Welding/blue_flame_welding_mask.rsi - type: entity - parent: ClothingHeadBase + parent: WeldingMaskBase id: ClothingHeadHatWeldingMaskPainted name: painted welding mask description: A welding helmet, painted in crimson. diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index ea594aba85..b9f6328282 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -242,3 +242,7 @@ - type: Tag id: Write + +# for head wear & masks that cover the face. +- type: Tag + id: ConcealsFace