diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index 301cf14311..954b553227 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -3,12 +3,14 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Shared.Atmos; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Clothing; using Content.Shared.Inventory.Events; namespace Content.Server.Body.Systems; public sealed class LungSystem : EntitySystem { + [Dependency] private readonly AtmosphereSystem _atmos = default!; [Dependency] private readonly InternalsSystem _internals = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; @@ -21,6 +23,7 @@ public sealed class LungSystem : EntitySystem SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnMaskToggled); } private void OnGotUnequipped(EntityUid uid, BreathToolComponent component, GotUnequippedEvent args) @@ -48,6 +51,28 @@ public sealed class LungSystem : EntitySystem component.LungSolution.CanReact = false; // No dexalin lungs } + private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args) + { + // toggle breath tool connection (skip during equip since that is handled in LungSystem) + if (args.IsEquip) + { + if (args.IsToggled) + { + _atmos.DisconnectInternals(ent.Comp); + } + else + { + ent.Comp.IsFunctional = true; + + if (TryComp(args.Wearer, out InternalsComponent? internals)) + { + ent.Comp.ConnectedInternalsEntity = args.Wearer; + _internals.ConnectBreathTool((args.Wearer, internals), ent); + } + } + } + } + public void GasToReagent(EntityUid uid, LungComponent lung) { foreach (var gas in Enum.GetValues()) diff --git a/Content.Server/Clothing/Components/MaskComponent.cs b/Content.Server/Clothing/Components/MaskComponent.cs deleted file mode 100644 index 704ea11ece..0000000000 --- a/Content.Server/Clothing/Components/MaskComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Clothing.Components -{ - [Access(typeof(MaskSystem))] - [RegisterComponent] - public sealed partial class MaskComponent : Component - { - [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string ToggleAction = "ActionToggleMask"; - - /// - /// This mask can be toggled (pulled up/down) - /// - [DataField("toggleActionEntity")] - public EntityUid? ToggleActionEntity; - - public bool IsToggled = false; - } -} diff --git a/Content.Server/Clothing/MaskSystem.cs b/Content.Server/Clothing/MaskSystem.cs deleted file mode 100644 index 776979106f..0000000000 --- a/Content.Server/Clothing/MaskSystem.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Content.Server.Actions; -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Server.Clothing.Components; -using Content.Server.IdentityManagement; -using Content.Server.Nutrition.EntitySystems; -using Content.Server.Popups; -using Content.Server.VoiceMask; -using Content.Shared.Actions; -using Content.Shared.Clothing; -using Content.Shared.Clothing.Components; -using Content.Shared.Clothing.EntitySystems; -using Content.Shared.IdentityManagement.Components; -using Content.Shared.Inventory; -using Content.Shared.Inventory.Events; - -namespace Content.Server.Clothing -{ - public sealed class MaskSystem : EntitySystem - { - [Dependency] private readonly ActionsSystem _actionSystem = default!; - [Dependency] private readonly AtmosphereSystem _atmos = default!; - [Dependency] private readonly InternalsSystem _internals = default!; - [Dependency] private readonly InventorySystem _inventorySystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IdentitySystem _identity = default!; - [Dependency] private readonly ClothingSystem _clothing = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnToggleMask); - SubscribeLocalEvent(OnGetActions); - SubscribeLocalEvent(OnGotUnequipped); - } - - private void OnGetActions(EntityUid uid, MaskComponent component, GetItemActionsEvent args) - { - if (!args.InHands) - args.AddAction(ref component.ToggleActionEntity, component.ToggleAction); - } - - private void OnToggleMask(Entity ent, ref ToggleMaskEvent args) - { - var (uid, mask) = ent; - if (mask.ToggleActionEntity == null) - return; - - if (!_inventorySystem.TryGetSlotEntity(args.Performer, "mask", out var existing) || !uid.Equals(existing)) - return; - - mask.IsToggled ^= true; - _actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled); - - // Pulling mask down can change identity, so we want to update that - _identity.QueueIdentityUpdate(args.Performer); - - if (mask.IsToggled) - _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-down-popup-message", ("mask", uid)), args.Performer, args.Performer); - else - _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-up-popup-message", ("mask", uid)), args.Performer, args.Performer); - - ToggleMaskComponents(uid, mask, args.Performer); - } - - // set to untoggled when unequipped, so it isn't left in a 'pulled down' state - private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args) - { - if (mask.ToggleActionEntity == null) - return; - - mask.IsToggled = false; - _actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled); - - ToggleMaskComponents(uid, mask, args.Equipee, true); - } - - private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, bool isEquip = false) - { - // toggle visuals - if (TryComp(uid, out var clothing)) - { - //TODO: sprites for 'pulled down' state. defaults to invisible due to no sprite with this prefix - _clothing.SetEquippedPrefix(uid, mask.IsToggled ? "toggled" : null, clothing); - } - - // shouldn't this be an event? - - // toggle ingestion blocking - if (TryComp(uid, out var blocker)) - blocker.Enabled = !mask.IsToggled; - - // toggle identity - if (TryComp(uid, out var identity)) - identity.Enabled = !mask.IsToggled; - - // toggle voice masking - if (TryComp(wearer, out var voiceMask)) - voiceMask.Enabled = !mask.IsToggled; - - // toggle breath tool connection (skip during equip since that is handled in LungSystem) - if (isEquip || !TryComp(uid, out var breathTool)) - return; - - if (mask.IsToggled) - { - _atmos.DisconnectInternals(breathTool); - } - else - { - breathTool.IsFunctional = true; - - if (TryComp(wearer, out InternalsComponent? internals)) - { - breathTool.ConnectedInternalsEntity = wearer; - _internals.ConnectBreathTool((wearer, internals), uid); - } - } - } - } -} diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index cdb4dc7c6c..5161911254 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -1,6 +1,7 @@ using Content.Server.Access.Systems; using Content.Server.Administration.Logs; using Content.Server.Humanoid; +using Content.Shared.Clothing; using Content.Shared.Database; using Content.Shared.Hands; using Content.Shared.Humanoid; @@ -33,6 +34,7 @@ public class IdentitySystem : SharedIdentitySystem SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); + SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid)); SubscribeLocalEvent(OnMapInit); } diff --git a/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs b/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs index 68d2b78fba..c3188d07a0 100644 --- a/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs +++ b/Content.Server/Nutrition/Components/IngestionBlockerComponent.cs @@ -1,6 +1,6 @@ -using Content.Server.Clothing; +using Content.Server.Nutrition.EntitySystems; -namespace Content.Server.Nutrition.EntitySystems; +namespace Content.Server.Nutrition.Components; /// /// Component that denotes a piece of clothing that blocks the mouth or otherwise prevents eating & drinking. @@ -9,7 +9,7 @@ namespace Content.Server.Nutrition.EntitySystems; /// 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, Access(typeof(FoodSystem), typeof(DrinkSystem), typeof(MaskSystem))] +[RegisterComponent, Access(typeof(FoodSystem), typeof(DrinkSystem), typeof(IngestionBlockerSystem))] public sealed partial class IngestionBlockerComponent : Component { /// diff --git a/Content.Server/Nutrition/EntitySystems/IngestionBlockerSystem.cs b/Content.Server/Nutrition/EntitySystems/IngestionBlockerSystem.cs new file mode 100644 index 0000000000..5c34df4b38 --- /dev/null +++ b/Content.Server/Nutrition/EntitySystems/IngestionBlockerSystem.cs @@ -0,0 +1,18 @@ +using Content.Shared.Clothing; + +namespace Content.Server.Nutrition.EntitySystems; + +public sealed class IngestionBlockerSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBlockerMaskToggled); + } + + private void OnBlockerMaskToggled(Entity ent, ref ItemMaskToggledEvent args) + { + ent.Comp.Enabled = !args.IsToggled; + } +} diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.cs b/Content.Server/VoiceMask/VoiceMaskSystem.cs index 81ff76fb7a..621ab4e395 100644 --- a/Content.Server/VoiceMask/VoiceMaskSystem.cs +++ b/Content.Server/VoiceMask/VoiceMaskSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Server.Popups; +using Content.Shared.Clothing; using Content.Shared.Database; using Content.Shared.Inventory.Events; using Content.Shared.Popups; @@ -21,6 +22,7 @@ public sealed partial class VoiceMaskSystem : EntitySystem { SubscribeLocalEvent(OnSpeakerNameTransform); SubscribeLocalEvent(OnChangeName); + SubscribeLocalEvent(OnMaskToggled); SubscribeLocalEvent(OnEquip); SubscribeLocalEvent(OnUnequip); SubscribeLocalEvent(OnSetName); @@ -67,6 +69,11 @@ public sealed partial class VoiceMaskSystem : EntitySystem } } + private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args) + { + ent.Comp.Enabled = !args.IsToggled; + } + private void OpenUI(EntityUid player, ActorComponent? actor = null) { if (!Resolve(player, ref actor)) diff --git a/Content.Shared/Clothing/ClothingEvents.cs b/Content.Shared/Clothing/ClothingEvents.cs index 028537f8b2..b4c1224ce9 100644 --- a/Content.Shared/Clothing/ClothingEvents.cs +++ b/Content.Shared/Clothing/ClothingEvents.cs @@ -59,3 +59,15 @@ public sealed class EquipmentVisualsUpdatedEvent : EntityEventArgs } public sealed partial class ToggleMaskEvent : InstantActionEvent { } + +/// +/// Event raised on the mask entity when it is toggled. +/// +[ByRefEvent] +public readonly record struct ItemMaskToggledEvent(EntityUid Wearer, bool IsToggled, bool IsEquip); + +/// +/// Event raised on the entity wearing the mask when it is toggled. +/// +[ByRefEvent] +public readonly record struct WearerMaskToggledEvent(bool Enabled); diff --git a/Content.Shared/Clothing/Components/MaskComponent.cs b/Content.Shared/Clothing/Components/MaskComponent.cs new file mode 100644 index 0000000000..e645524da7 --- /dev/null +++ b/Content.Shared/Clothing/Components/MaskComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Clothing.EntitySystems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Clothing.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(MaskSystem))] +public sealed partial class MaskComponent : Component +{ + [DataField, AutoNetworkedField] + public EntProtoId ToggleAction = "ActionToggleMask"; + + /// + /// This mask can be toggled (pulled up/down) + /// + [DataField, AutoNetworkedField] + public EntityUid? ToggleActionEntity; + + [DataField, AutoNetworkedField] + public bool IsToggled; +} diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index 1c69f1227c..9aa0158088 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -25,6 +25,7 @@ public abstract class ClothingSystem : EntitySystem SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnMaskToggled); } protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args) @@ -52,6 +53,12 @@ public abstract class ClothingSystem : EntitySystem SetEquippedPrefix(uid, state.EquippedPrefix, component); } + private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args) + { + //TODO: sprites for 'pulled down' state. defaults to invisible due to no sprite with this prefix + SetEquippedPrefix(ent, args.IsToggled ? "toggled" : null, ent); + } + #region Public API public void SetEquippedPrefix(EntityUid uid, string? prefix, ClothingComponent? clothing = null) diff --git a/Content.Shared/Clothing/EntitySystems/MaskSystem.cs b/Content.Shared/Clothing/EntitySystems/MaskSystem.cs new file mode 100644 index 0000000000..50cf3afe50 --- /dev/null +++ b/Content.Shared/Clothing/EntitySystems/MaskSystem.cs @@ -0,0 +1,71 @@ +using Content.Shared.Actions; +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory; +using Content.Shared.Inventory.Events; +using Content.Shared.Popups; + +namespace Content.Shared.Clothing.EntitySystems; + +public sealed class MaskSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actionSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnToggleMask); + SubscribeLocalEvent(OnGetActions); + SubscribeLocalEvent(OnGotUnequipped); + } + + private void OnGetActions(EntityUid uid, MaskComponent component, GetItemActionsEvent args) + { + if (!args.InHands) + args.AddAction(ref component.ToggleActionEntity, component.ToggleAction); + } + + private void OnToggleMask(Entity ent, ref ToggleMaskEvent args) + { + var (uid, mask) = ent; + if (mask.ToggleActionEntity == null) + return; + + if (!_inventorySystem.TryGetSlotEntity(args.Performer, "mask", out var existing) || !uid.Equals(existing)) + return; + + mask.IsToggled ^= true; + _actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled); + + if (mask.IsToggled) + _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-down-popup-message", ("mask", uid)), args.Performer, args.Performer); + else + _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-up-popup-message", ("mask", uid)), args.Performer, args.Performer); + + ToggleMaskComponents(uid, mask, args.Performer); + } + + // set to untoggled when unequipped, so it isn't left in a 'pulled down' state + private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args) + { + if (mask.ToggleActionEntity == null) + return; + + mask.IsToggled = false; + Dirty(uid, mask); + _actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled); + + ToggleMaskComponents(uid, mask, args.Equipee, true); + } + + private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, bool isEquip = false) + { + var maskEv = new ItemMaskToggledEvent(wearer, mask.IsToggled, isEquip); + RaiseLocalEvent(uid, ref maskEv); + + var wearerEv = new WearerMaskToggledEvent(mask.IsToggled); + RaiseLocalEvent(uid, ref wearerEv); + } +} diff --git a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs index a671884089..7cb5f41b8a 100644 --- a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs +++ b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Clothing; using Content.Shared.IdentityManagement.Components; using Content.Shared.Inventory; using Robust.Shared.Containers; @@ -16,6 +17,7 @@ public abstract class SharedIdentitySystem : EntitySystem SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnSeeIdentity); SubscribeLocalEvent>((e, c, ev) => OnSeeIdentity(e, c, ev.Args)); + SubscribeLocalEvent(OnMaskToggled); } private void OnSeeIdentity(EntityUid uid, IdentityBlockerComponent component, SeeIdentityAttemptEvent args) @@ -28,4 +30,9 @@ public abstract class SharedIdentitySystem : EntitySystem { component.IdentityEntitySlot = _container.EnsureContainer(uid, SlotName); } + + private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args) + { + ent.Comp.Enabled = !args.IsToggled; + } }