diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 48d30e0eea..e5ba6ea452 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -8,6 +8,7 @@ namespace Content.Client.Entry "Anchorable", "AmmoBox", "Pickaxe", + "IngestionBlocker", "Interactable", "CloningPod", "Destructible", diff --git a/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs b/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs new file mode 100644 index 0000000000..6e556d0a02 --- /dev/null +++ b/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Analyzers; +using Robust.Shared.ViewVariables; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Nutrition.EntitySystems; + +/// +/// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking. +/// +/// +/// In the event that more head-wear & mask functionality is added (like identity systems, or raising/lowering of +/// masks), then this component might become redundant. +/// +[RegisterComponent, Friend(typeof(FoodSystem), typeof(DrinkSystem))] +public class IngestionBlockerComponent : Component +{ + public override string Name => "IngestionBlocker"; + + /// + /// Is this component currently blocking consumption. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("enabled")] + public bool Enabled { get; set; } = true; +} diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index cc5af10676..2eb49ef1a1 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -263,13 +263,8 @@ 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)); + if (_foodSystem.IsMouthBlocked(userUid, userUid)) return true; - } var transferAmount = FixedPoint2.Min(drink.TransferAmount, drinkSolution.DrainAvailable); var drain = _solutionContainerSystem.Drain(uid, drinkSolution, transferAmount); @@ -336,13 +331,8 @@ 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)); + if (_foodSystem.IsMouthBlocked(targetUid, userUid)) return true; - } EntityManager.TryGetComponent(userUid, out MetaDataComponent? meta); var userName = meta?.EntityName ?? string.Empty; @@ -425,28 +415,4 @@ namespace Content.Server.Nutrition.EntitySystems args.Drink.InUse = false; } } - - public sealed class ForceDrinkEvent : EntityEventArgs - { - public readonly EntityUid User; - public readonly DrinkComponent Drink; - public readonly Solution DrinkSolution; - - public ForceDrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution) - { - User = user; - Drink = drink; - DrinkSolution = drinkSolution; - } - } - - public sealed class ForceDrinkCancelledEvent : EntityEventArgs - { - public readonly DrinkComponent Drink; - - public ForceDrinkCancelledEvent( DrinkComponent drink) - { - Drink = drink; - } - } } diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index e85f1df558..9876afd2e1 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -27,8 +27,6 @@ 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 { @@ -55,6 +53,7 @@ namespace Content.Server.Nutrition.EntitySystems SubscribeLocalEvent(AddEatVerb); SubscribeLocalEvent(OnForceFeed); SubscribeLocalEvent(OnForceFeedCancelled); + SubscribeLocalEvent(OnInventoryIngestAttempt); } /// @@ -139,13 +138,8 @@ 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)); + if (IsMouthBlocked(userUid, userUid)) return true; - } var usedUtensils = new List(); @@ -264,13 +258,8 @@ 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)); + if (IsMouthBlocked(targetUid, userUid)) return true; - } if (!TryGetRequiredUtensils(userUid, food, out var utensils)) return true; @@ -364,7 +353,7 @@ namespace Content.Server.Nutrition.EntitySystems if (!Resolve(uid, ref food) || !Resolve(target, ref body, false)) return; - if (IsMouthBlocked(target, out _)) + if (IsMouthBlocked(target)) return; if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var foodSolution)) @@ -448,62 +437,53 @@ namespace Content.Server.Nutrition.EntitySystems } /// - /// 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? + /// Block ingestion attempts based on the equipped mask or head-wear /// - public bool IsMouthBlocked(EntityUid uid, [NotNullWhen(true)] out EntityUid? blockingEntity, - InventoryComponent? inventory = null) + private void OnInventoryIngestAttempt(EntityUid uid, InventoryComponent component, IngestionAttemptEvent args) { - blockingEntity = null; + if (args.Cancelled) + return; - if (!Resolve(uid, ref inventory, false)) - return false; + IngestionBlockerComponent blocker; - // check masks - if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.MASK, out ItemComponent? mask)) + if (component.TryGetSlotItem(EquipmentSlotDefines.Slots.MASK, out ItemComponent? mask) && + EntityManager.TryGetComponent(mask.OwnerUid, out blocker) && + blocker.Enabled) { - // 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; + args.Blocker = mask.OwnerUid; + args.Cancel(); + return; } - // 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")) + if (component.TryGetSlotItem(EquipmentSlotDefines.Slots.HEAD, out ItemComponent? head) && + EntityManager.TryGetComponent(head.OwnerUid, out blocker) && + blocker.Enabled) { - blockingEntity = head.OwnerUid; - return true; + args.Blocker = head.OwnerUid; + args.Cancel(); + } + } + + + /// + /// Check whether the target's mouth is blocked by equipment (masks or head-wear). + /// + /// The target whose equipment is checked + /// Optional entity that will receive an informative pop-up identifying the blocking + /// piece of equipment. + /// + public bool IsMouthBlocked(EntityUid uid, EntityUid? popupUid = null) + { + var attempt = new IngestionAttemptEvent(); + RaiseLocalEvent(uid, attempt, false); + if (attempt.Cancelled && attempt.Blocker != null && popupUid != null) + { + var name = EntityManager.GetComponent(attempt.Blocker.Value).EntityName; + _popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)), + uid, Filter.Entities(popupUid.Value)); } - return false; - } - } - - public sealed class ForceFeedEvent : EntityEventArgs - { - public readonly EntityUid User; - public readonly FoodComponent Food; - public readonly Solution FoodSolution; - public readonly List Utensils; - - public ForceFeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, List utensils) - { - User = user; - Food = food; - FoodSolution = foodSolution; - Utensils = utensils; - } - } - - public sealed class ForceFeedCancelledEvent : EntityEventArgs - { - public readonly FoodComponent Food; - - public ForceFeedCancelledEvent(FoodComponent food) - { - Food = food; + return attempt.Cancelled; } } } diff --git a/Content.Server/Nutrition/IngestionEvents.cs b/Content.Server/Nutrition/IngestionEvents.cs new file mode 100644 index 0000000000..1db57ba3b3 --- /dev/null +++ b/Content.Server/Nutrition/IngestionEvents.cs @@ -0,0 +1,79 @@ +using Content.Server.Nutrition.Components; +using Content.Shared.Chemistry.Components; +using Robust.Shared.GameObjects; +using System.Collections.Generic; + +namespace Content.Server.Nutrition; + +/// +/// Raised directed at the consumer when attempting to ingest something. +/// +public sealed class IngestionAttemptEvent : CancellableEntityEventArgs +{ + /// + /// The equipment that is blocking consumption. Should only be non-null if the event was canceled. + /// + public EntityUid? Blocker = null; +} + +/// +/// Raised directed at the food after a successful force-feed do-after. +/// +public sealed class ForceFeedEvent : EntityEventArgs +{ + public readonly EntityUid User; + public readonly FoodComponent Food; + public readonly Solution FoodSolution; + public readonly List Utensils; + + public ForceFeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, List utensils) + { + User = user; + Food = food; + FoodSolution = foodSolution; + Utensils = utensils; + } +} + +/// +/// Raised directed at the food after a failed force-feed do-after. +/// +public sealed class ForceFeedCancelledEvent : EntityEventArgs +{ + public readonly FoodComponent Food; + + public ForceFeedCancelledEvent(FoodComponent food) + { + Food = food; + } +} + +/// +/// Raised directed at the drink after a successful force-drink do-after. +/// +public sealed class ForceDrinkEvent : EntityEventArgs +{ + public readonly EntityUid User; + public readonly DrinkComponent Drink; + public readonly Solution DrinkSolution; + + public ForceDrinkEvent(EntityUid user, DrinkComponent drink, Solution drinkSolution) + { + User = user; + Drink = drink; + DrinkSolution = drinkSolution; + } +} + +/// +/// Raised directed at the food after a failed force-dink do-after. +/// +public sealed class ForceDrinkCancelledEvent : EntityEventArgs +{ + public readonly DrinkComponent Drink; + + public ForceDrinkCancelledEvent(DrinkComponent drink) + { + Drink = drink; + } +} diff --git a/Resources/Prototypes/Entities/Clothing/Head/base.yml b/Resources/Prototypes/Entities/Clothing/Head/base.yml index 1fd8f34843..d3c636aee7 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base.yml @@ -41,9 +41,7 @@ Piercing: 0.95 Heat: 0.90 Radiation: 0.25 - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index 80f6d6c27d..0dfcd01d24 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -8,9 +8,7 @@ sprite: Clothing/Head/Helmets/bombsuit.rsi - type: Clothing sprite: Clothing/Head/Helmets/bombsuit.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -22,9 +20,7 @@ sprite: Clothing/Head/Helmets/cosmonaut.rsi - type: Clothing sprite: Clothing/Head/Helmets/cosmonaut.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -36,9 +32,7 @@ sprite: Clothing/Head/Helmets/cult.rsi - type: Clothing sprite: Clothing/Head/Helmets/cult.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -79,9 +73,7 @@ sprite: Clothing/Head/Helmets/light_riot.rsi - type: Clothing sprite: Clothing/Head/Helmets/light_riot.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -93,9 +85,7 @@ sprite: Clothing/Head/Helmets/scaf.rsi - type: Clothing sprite: Clothing/Head/Helmets/scaf.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -107,9 +97,7 @@ sprite: Clothing/Head/Helmets/spaceninja.rsi - type: Clothing sprite: Clothing/Head/Helmets/spaceninja.rsi - - type: Tag - tags: - - ConcealsFace # well only partially? + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -121,9 +109,7 @@ sprite: Clothing/Head/Helmets/syndicate.rsi - type: Clothing sprite: Clothing/Head/Helmets/syndicate.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -135,9 +121,7 @@ sprite: Clothing/Head/Helmets/templar.rsi - type: Clothing sprite: Clothing/Head/Helmets/templar.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -160,6 +144,4 @@ sprite: Clothing/Head/Helmets/wizardhelm.rsi - type: Clothing sprite: Clothing/Head/Helmets/wizardhelm.rsi - - type: Tag - tags: - - ConcealsFace \ No newline at end of file + - type: IngestionBlocker \ 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 0a4d15f11b..4f3ef5fd51 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml @@ -30,9 +30,7 @@ sprite: Clothing/Head/Misc/chickenhead.rsi - type: Clothing sprite: Clothing/Head/Misc/chickenhead.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -55,9 +53,7 @@ sprite: Clothing/Head/Misc/pumpkin.rsi - type: Clothing sprite: Clothing/Head/Misc/pumpkin.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -80,9 +76,7 @@ sprite: Clothing/Head/Misc/richard.rsi - type: Clothing sprite: Clothing/Head/Misc/richard.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -94,9 +88,7 @@ sprite: Clothing/Head/Misc/skubhead.rsi - type: Clothing sprite: Clothing/Head/Misc/skubhead.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -108,9 +100,7 @@ sprite: Clothing/Head/Misc/xenom.rsi - type: Clothing sprite: Clothing/Head/Misc/xenom.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase @@ -122,9 +112,7 @@ sprite: Clothing/Head/Misc/xenos.rsi - type: Clothing sprite: Clothing/Head/Misc/xenos.rsi - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Clothing/Head/welding.yml b/Resources/Prototypes/Entities/Clothing/Head/welding.yml index 375f3ecebd..ba2ab93044 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/welding.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/welding.yml @@ -4,9 +4,7 @@ name: welding mask abstract: true components: - - type: Tag - tags: - - ConcealsFace + - type: IngestionBlocker - type: entity parent: WeldingMaskBase diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index cb9afb25a7..9112452d8e 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -9,6 +9,7 @@ - type: Clothing sprite: Clothing/Mask/gas.rsi - type: BreathMask + - type: IngestionBlocker - type: entity parent: ClothingMaskBase @@ -21,6 +22,7 @@ - type: Clothing sprite: Clothing/Mask/breath.rsi - type: BreathMask + - type: IngestionBlocker - type: entity parent: ClothingMaskBase @@ -69,3 +71,4 @@ - type: Clothing sprite: Clothing/Mask/sterile.rsi - type: BreathMask + - type: IngestionBlocker diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index b9f6328282..ea594aba85 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -242,7 +242,3 @@ - type: Tag id: Write - -# for head wear & masks that cover the face. -- type: Tag - id: ConcealsFace