Add IngestionBlocker component (#5692)

This commit is contained in:
Leon Friedrich
2021-12-05 07:56:27 +13:00
committed by GitHub
parent e9e9a6871d
commit bfff21ea73
11 changed files with 169 additions and 152 deletions

View File

@@ -8,6 +8,7 @@ namespace Content.Client.Entry
"Anchorable",
"AmmoBox",
"Pickaxe",
"IngestionBlocker",
"Interactable",
"CloningPod",
"Destructible",

View File

@@ -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;
/// <summary>
/// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
[RegisterComponent, Friend(typeof(FoodSystem), typeof(DrinkSystem))]
public class IngestionBlockerComponent : Component
{
public override string Name => "IngestionBlocker";
/// <summary>
/// Is this component currently blocking consumption.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled { get; set; } = true;
}

View File

@@ -263,13 +263,8 @@ namespace Content.Server.Nutrition.EntitySystems
return true;
}
if (_foodSystem.IsMouthBlocked(userUid, out var blocker))
{
var name = EntityManager.GetComponent<MetaDataComponent>(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<MetaDataComponent>(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;
}
}
}

View File

@@ -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<FoodComponent, GetInteractionVerbsEvent>(AddEatVerb);
SubscribeLocalEvent<SharedBodyComponent, ForceFeedEvent>(OnForceFeed);
SubscribeLocalEvent<ForceFeedCancelledEvent>(OnForceFeedCancelled);
SubscribeLocalEvent<InventoryComponent, IngestionAttemptEvent>(OnInventoryIngestAttempt);
}
/// <summary>
@@ -139,13 +138,8 @@ namespace Content.Server.Nutrition.EntitySystems
!_bodySystem.TryGetComponentsOnMechanisms<StomachComponent>(userUid, out var stomachs, body))
return false;
if (IsMouthBlocked(userUid, out var blocker))
{
var name = EntityManager.GetComponent<MetaDataComponent>(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<UtensilComponent>();
@@ -264,13 +258,8 @@ namespace Content.Server.Nutrition.EntitySystems
return true;
}
if (IsMouthBlocked(targetUid, out var blocker))
{
var name = EntityManager.GetComponent<MetaDataComponent>(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
}
/// <summary>
/// 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
/// </summary>
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;
}
return false;
args.Blocker = head.OwnerUid;
args.Cancel();
}
}
public sealed class ForceFeedEvent : EntityEventArgs
{
public readonly EntityUid User;
public readonly FoodComponent Food;
public readonly Solution FoodSolution;
public readonly List<UtensilComponent> Utensils;
public ForceFeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, List<UtensilComponent> utensils)
/// <summary>
/// Check whether the target's mouth is blocked by equipment (masks or head-wear).
/// </summary>
/// <param name="uid">The target whose equipment is checked</param>
/// <param name="popupUid">Optional entity that will receive an informative pop-up identifying the blocking
/// piece of equipment.</param>
/// <returns></returns>
public bool IsMouthBlocked(EntityUid uid, EntityUid? popupUid = null)
{
User = user;
Food = food;
FoodSolution = foodSolution;
Utensils = utensils;
}
var attempt = new IngestionAttemptEvent();
RaiseLocalEvent(uid, attempt, false);
if (attempt.Cancelled && attempt.Blocker != null && popupUid != null)
{
var name = EntityManager.GetComponent<MetaDataComponent>(attempt.Blocker.Value).EntityName;
_popupSystem.PopupEntity(Loc.GetString("food-system-remove-mask", ("entity", name)),
uid, Filter.Entities(popupUid.Value));
}
public sealed class ForceFeedCancelledEvent : EntityEventArgs
{
public readonly FoodComponent Food;
public ForceFeedCancelledEvent(FoodComponent food)
{
Food = food;
return attempt.Cancelled;
}
}
}

View File

@@ -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;
/// <summary>
/// Raised directed at the consumer when attempting to ingest something.
/// </summary>
public sealed class IngestionAttemptEvent : CancellableEntityEventArgs
{
/// <summary>
/// The equipment that is blocking consumption. Should only be non-null if the event was canceled.
/// </summary>
public EntityUid? Blocker = null;
}
/// <summary>
/// Raised directed at the food after a successful force-feed do-after.
/// </summary>
public sealed class ForceFeedEvent : EntityEventArgs
{
public readonly EntityUid User;
public readonly FoodComponent Food;
public readonly Solution FoodSolution;
public readonly List<UtensilComponent> Utensils;
public ForceFeedEvent(EntityUid user, FoodComponent food, Solution foodSolution, List<UtensilComponent> utensils)
{
User = user;
Food = food;
FoodSolution = foodSolution;
Utensils = utensils;
}
}
/// <summary>
/// Raised directed at the food after a failed force-feed do-after.
/// </summary>
public sealed class ForceFeedCancelledEvent : EntityEventArgs
{
public readonly FoodComponent Food;
public ForceFeedCancelledEvent(FoodComponent food)
{
Food = food;
}
}
/// <summary>
/// Raised directed at the drink after a successful force-drink do-after.
/// </summary>
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;
}
}
/// <summary>
/// Raised directed at the food after a failed force-dink do-after.
/// </summary>
public sealed class ForceDrinkCancelledEvent : EntityEventArgs
{
public readonly DrinkComponent Drink;
public ForceDrinkCancelledEvent(DrinkComponent drink)
{
Drink = drink;
}
}

View File

@@ -41,9 +41,7 @@
Piercing: 0.95
Heat: 0.90
Radiation: 0.25
- type: Tag
tags:
- ConcealsFace
- type: IngestionBlocker
- type: entity
abstract: true

View File

@@ -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
- type: IngestionBlocker

View File

@@ -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

View File

@@ -4,9 +4,7 @@
name: welding mask
abstract: true
components:
- type: Tag
tags:
- ConcealsFace
- type: IngestionBlocker
- type: entity
parent: WeldingMaskBase

View File

@@ -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

View File

@@ -242,7 +242,3 @@
- type: Tag
id: Write
# for head wear & masks that cover the face.
- type: Tag
id: ConcealsFace