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