From 3b29ffdfa0209e4e884a63e41549d3f02ad6aad1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 24 Nov 2021 20:03:07 +1300 Subject: [PATCH] Make chemistry machines and IdCardConsole use item slots (#5428) * chemistry item slots * item slots id card console --- Content.Client/Access/IdCardConsoleSystem.cs | 13 ++ .../EntitySystems/ChemMasterSystem.cs | 12 ++ .../EntitySystems/ReagentDispenserSystem.cs | 11 ++ .../Components/IdCardConsoleComponent.cs | 144 +++--------------- Content.Server/Access/IdCardConsoleSystem.cs | 80 ---------- .../Access/Systems/IdCardConsoleSystem.cs | 22 +++ .../Components/ChemMasterComponent.cs | 104 +------------ .../Components/ReagentDispenserComponent.cs | 105 ++----------- .../EntitySystems/ChemMasterSystem.cs | 67 +------- .../EntitySystems/ReagentDispenserSystem.cs | 69 +-------- .../EmptyOnMachineDeconstructSystem.cs | 14 +- .../Access/SharedIdCardConsoleComponent.cs | 8 + .../Access/SharedIdCardConsoleSystem.cs | 33 ++++ Content.Shared/Acts/ActSystem.cs | 24 +-- .../Components/SharedChemMasterComponent.cs | 7 +- .../SharedReagentDispenserComponent.cs | 7 +- .../EntitySystems/SharedChemMasterSystem.cs | 32 ++++ .../SharedReagentDispenserSystem.cs | 32 ++++ .../Containers/ItemSlot/ItemSlotsComponent.cs | 37 +++++ .../Containers/ItemSlot/ItemSlotsSystem.cs | 59 ++++++- .../components/id-card-console-component.ftl | 3 - .../components/chem-master-component.ftl | 6 +- .../reagent-dispenser-component.ftl | 7 +- .../Entities/Structures/Dispensers/chem.yml | 9 +- .../Machines/Computers/computers.yml | 18 +++ .../Structures/Machines/chem_master.yml | 8 +- 26 files changed, 371 insertions(+), 560 deletions(-) create mode 100644 Content.Client/Access/IdCardConsoleSystem.cs create mode 100644 Content.Client/Chemistry/EntitySystems/ChemMasterSystem.cs create mode 100644 Content.Client/Chemistry/EntitySystems/ReagentDispenserSystem.cs delete mode 100644 Content.Server/Access/IdCardConsoleSystem.cs create mode 100644 Content.Server/Access/Systems/IdCardConsoleSystem.cs create mode 100644 Content.Shared/Access/SharedIdCardConsoleSystem.cs create mode 100644 Content.Shared/Chemistry/EntitySystems/SharedChemMasterSystem.cs create mode 100644 Content.Shared/Chemistry/EntitySystems/SharedReagentDispenserSystem.cs diff --git a/Content.Client/Access/IdCardConsoleSystem.cs b/Content.Client/Access/IdCardConsoleSystem.cs new file mode 100644 index 0000000000..27577d66e2 --- /dev/null +++ b/Content.Client/Access/IdCardConsoleSystem.cs @@ -0,0 +1,13 @@ +using Content.Shared.Access; +using JetBrains.Annotations; + +namespace Content.Client.Access +{ + [UsedImplicitly] + public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem + { + // one day, maybe bound user interfaces can be shared too. + // then this doesn't have to be like this. + // I hate this. + } +} diff --git a/Content.Client/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemMasterSystem.cs new file mode 100644 index 0000000000..a35e5c04aa --- /dev/null +++ b/Content.Client/Chemistry/EntitySystems/ChemMasterSystem.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; +using Content.Shared.Chemistry.EntitySystems; + +namespace Content.Client.Chemistry.EntitySystems +{ + [UsedImplicitly] + public sealed class ChemMasterSystem : SharedChemMasterSystem + { + // gotta love empty client side systems that exist purely because theres one specific thing that can only be + // done server-side which prevents the whole system from being in shared. + } +} diff --git a/Content.Client/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Client/Chemistry/EntitySystems/ReagentDispenserSystem.cs new file mode 100644 index 0000000000..25c09b4603 --- /dev/null +++ b/Content.Client/Chemistry/EntitySystems/ReagentDispenserSystem.cs @@ -0,0 +1,11 @@ +using JetBrains.Annotations; +using Content.Shared.Chemistry.EntitySystems; + +namespace Content.Client.Chemistry.EntitySystems +{ + [UsedImplicitly] + public sealed class ReagentDispenserSystem : SharedReagentDispenserSystem + { + // hello there. + } +} diff --git a/Content.Server/Access/Components/IdCardConsoleComponent.cs b/Content.Server/Access/Components/IdCardConsoleComponent.cs index 61eefd2922..0683649d12 100644 --- a/Content.Server/Access/Components/IdCardConsoleComponent.cs +++ b/Content.Server/Access/Components/IdCardConsoleComponent.cs @@ -1,19 +1,14 @@ using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Content.Server.Access.Systems; using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.Access; -using Content.Shared.Acts; -using Content.Shared.Hands.Components; +using Content.Shared.Containers.ItemSlots; using Content.Shared.Interaction; -using Content.Shared.Popups; using Robust.Server.GameObjects; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Prototypes; using Robust.Shared.ViewVariables; @@ -21,26 +16,18 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Access.Components { [RegisterComponent] - public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IInteractUsing, IBreakAct + [ComponentReference(typeof(SharedIdCardConsoleComponent))] + public sealed class IdCardConsoleComponent : SharedIdCardConsoleComponent { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - public ContainerSlot PrivilegedIdContainer = default!; - public ContainerSlot TargetIdContainer = default!; - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(IdCardConsoleUiKey.Key); [ViewVariables] private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; - public bool PrivilegedIDEmpty => PrivilegedIdContainer.ContainedEntities.Count < 1; - public bool TargetIDEmpty => TargetIdContainer.ContainedEntities.Count < 1; - protected override void Initialize() { base.Initialize(); - PrivilegedIdContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-privilegedId"); - TargetIdContainer = ContainerHelpers.EnsureContainer(Owner, $"{Name}-targetId"); - Owner.EnsureComponentWarn(); Owner.EnsureComponentWarn(); @@ -48,8 +35,6 @@ namespace Content.Server.Access.Components { UserInterface.OnReceiveMessage += OnUiReceiveMessage; } - - UpdateUserInterface(); } private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) @@ -65,23 +50,22 @@ namespace Content.Server.Access.Components switch (msg.Button) { case UiButton.PrivilegedId: - HandleId(obj.Session.AttachedEntity, PrivilegedIdContainer); + HandleIdButton(obj.Session.AttachedEntity, PrivilegedIdSlot); break; case UiButton.TargetId: - HandleId(obj.Session.AttachedEntity, TargetIdContainer); + HandleIdButton(obj.Session.AttachedEntity, TargetIdSlot); break; } break; case WriteToTargetIdMessage msg: TryWriteToTargetId(msg.FullName, msg.JobTitle, msg.AccessList); + UpdateUserInterface(); break; } - - UpdateUserInterface(); } /// - /// Returns true if there is an ID in and said ID satisfies the requirements of . + /// Returns true if there is an ID in and said ID satisfies the requirements of . /// private bool PrivilegedIdIsAuthorized() { @@ -90,23 +74,20 @@ namespace Content.Server.Access.Components return true; } - var privilegedIdEntity = PrivilegedIdContainer.ContainedEntity; + var privilegedIdEntity = PrivilegedIdSlot.Item; var accessSystem = EntitySystem.Get(); return privilegedIdEntity != null && accessSystem.IsAllowed(reader, privilegedIdEntity.Uid); } /// /// Called when the "Submit" button in the UI gets pressed. - /// Writes data passed from the UI into the ID stored in , if present. + /// Writes data passed from the UI into the ID stored in , if present. /// private void TryWriteToTargetId(string newFullName, string newJobTitle, List newAccessList) { - if (!PrivilegedIdIsAuthorized() || TargetIdContainer.ContainedEntity == null) - { + var targetIdEntity = TargetIdSlot.Item; + if (targetIdEntity == null || !PrivilegedIdIsAuthorized()) return; - } - - var targetIdEntity = TargetIdContainer.ContainedEntity; var cardSystem = EntitySystem.Get(); cardSystem.TryChangeFullName(targetIdEntity.Uid, newFullName); @@ -125,125 +106,46 @@ namespace Content.Server.Access.Components /// /// Called when one of the insert/remove ID buttons gets pressed. /// - private void HandleId(IEntity user, ContainerSlot container) + private void HandleIdButton(IEntity user, ItemSlot slot) { - if (!user.TryGetComponent(out SharedHandsComponent? hands)) - { - Owner.PopupMessage(user, Loc.GetString("access-id-card-console-component-no-hands-error")); - return; - } - - if (container.ContainedEntity == null) - { - InsertIdFromHand(user, container, hands); - } + if (slot.HasItem) + EntitySystem.Get().TryEjectToHands(OwnerUid, slot, user.Uid); else - { - PutIdInHand(container, hands); - } + EntitySystem.Get().TryInsertFromHand(OwnerUid, slot, user.Uid); } - public void InsertIdFromHand(IEntity user, ContainerSlot container, SharedHandsComponent hands) + public void UpdateUserInterface() { - if (!hands.TryGetActiveHeldEntity(out var heldEntity)) - return; - - if (!heldEntity.HasComponent()) - return; - - if (!hands.TryPutHandIntoContainer(hands.ActiveHand!, container)) - { - Owner.PopupMessage(user, Loc.GetString("access-id-card-console-component-cannot-let-go-error")); - return; - } - UpdateUserInterface(); - } - - public void PutIdInHand(ContainerSlot container, SharedHandsComponent hands) - { - var idEntity = container.ContainedEntity; - if (idEntity == null || !container.Remove(idEntity)) - { - return; - } - UpdateUserInterface(); - - hands.TryPutInActiveHandOrAny(idEntity); - } - - private void UpdateUserInterface() - { - var isPrivilegedIdPresent = PrivilegedIdContainer.ContainedEntity != null; - var targetIdEntity = TargetIdContainer.ContainedEntity; + var targetIdEntity = TargetIdSlot.Item; IdCardConsoleBoundUserInterfaceState newState; // this could be prettier if (targetIdEntity == null) { newState = new IdCardConsoleBoundUserInterfaceState( - isPrivilegedIdPresent, + PrivilegedIdSlot.HasItem, PrivilegedIdIsAuthorized(), false, null, null, null, - PrivilegedIdContainer.ContainedEntity?.Name ?? string.Empty, - TargetIdContainer.ContainedEntity?.Name ?? string.Empty); + PrivilegedIdSlot.Item?.Name ?? string.Empty, + string.Empty); } else { var targetIdComponent = targetIdEntity.GetComponent(); var targetAccessComponent = targetIdEntity.GetComponent(); newState = new IdCardConsoleBoundUserInterfaceState( - isPrivilegedIdPresent, + PrivilegedIdSlot.HasItem, PrivilegedIdIsAuthorized(), true, targetIdComponent.FullName, targetIdComponent.JobTitle, targetAccessComponent.Tags.ToArray(), - PrivilegedIdContainer.ContainedEntity?.Name ?? string.Empty, - TargetIdContainer.ContainedEntity?.Name ?? string.Empty); + PrivilegedIdSlot.Item?.Name ?? string.Empty, + targetIdEntity.Name); } UserInterface?.SetState(newState); } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - var item = eventArgs.Using; - var user = eventArgs.User; - - if (!PrivilegedIDEmpty && !TargetIDEmpty) - { - return false; - } - - if (!item.HasComponent() || !user.TryGetComponent(out SharedHandsComponent? hand)) - { - return false; - } - - if (PrivilegedIDEmpty) - { - InsertIdFromHand(user, PrivilegedIdContainer, hand); - } - - else if (TargetIDEmpty) - { - InsertIdFromHand(user, TargetIdContainer, hand); - } - - UpdateUserInterface(); - return true; - } - - public void OnBreak(BreakageEventArgs eventArgs) - { - var privileged = PrivilegedIdContainer.ContainedEntity; - if (privileged != null) - PrivilegedIdContainer.Remove(privileged); - - var target = TargetIdContainer.ContainedEntity; - if (target != null) - TargetIdContainer.Remove(target); - } } } diff --git a/Content.Server/Access/IdCardConsoleSystem.cs b/Content.Server/Access/IdCardConsoleSystem.cs deleted file mode 100644 index aa81d33f42..0000000000 --- a/Content.Server/Access/IdCardConsoleSystem.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Content.Server.Access.Components; -using Content.Shared.ActionBlocker; -using Content.Shared.Verbs; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; - -namespace Content.Server.Access -{ - public class IdCardConsoleSystem : EntitySystem - { - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(AddInsertVerbs); - SubscribeLocalEvent(AddEjectVerbs); - } - - private void AddInsertVerbs(EntityUid uid, IdCardConsoleComponent component, GetInteractionVerbsEvent args) - { - if (args.Using == null || - !args.CanAccess || - !args.CanInteract || - !args.Using.HasComponent() || - !_actionBlockerSystem.CanDrop(args.User.Uid)) - return; - - // Can we insert a privileged ID? - if (component.PrivilegedIDEmpty) - { - Verb verb = new(); - verb.Act = () => component.InsertIdFromHand(args.User, component.PrivilegedIdContainer, args.Hands!); - verb.Category = VerbCategory.Insert; - verb.Text = Loc.GetString("id-card-console-privileged-id"); - args.Verbs.Add(verb); - } - - // Can we insert a target ID? - if (component.TargetIDEmpty) - { - Verb verb = new(); - verb.Act = () => component.InsertIdFromHand(args.User, component.TargetIdContainer, args.Hands!); - verb.Category = VerbCategory.Insert; - verb.Text = Loc.GetString("id-card-console-target-id"); - args.Verbs.Add(verb); - } - } - - private void AddEjectVerbs(EntityUid uid, IdCardConsoleComponent component, GetAlternativeVerbsEvent args) - { - if (args.Hands == null || - !args.CanAccess || - !args.CanInteract || - !_actionBlockerSystem.CanPickup(args.User.Uid)) - return; - - // Can we eject a privileged ID? - if (!component.PrivilegedIDEmpty) - { - Verb verb = new(); - verb.Act = () => component.PutIdInHand(component.PrivilegedIdContainer, args.Hands); - verb.Category = VerbCategory.Eject; - verb.Text = Loc.GetString("id-card-console-privileged-id"); - args.Verbs.Add(verb); - } - - // Can we eject a target ID? - if (!component.TargetIDEmpty) - { - Verb verb = new(); - verb.Act = () => component.PutIdInHand(component.TargetIdContainer, args.Hands); - verb.Category = VerbCategory.Eject; - verb.Text = Loc.GetString("id-card-console-target-id"); - args.Verbs.Add(verb); - } - } - } -} diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs new file mode 100644 index 0000000000..3e3740510b --- /dev/null +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -0,0 +1,22 @@ +using Content.Server.Access.Components; +using Content.Shared.Access; +using JetBrains.Annotations; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; + +namespace Content.Server.Access.Systems +{ + [UsedImplicitly] + public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem + { + public override void Initialize() + { + base.Initialize(); + + // one day, maybe bound user interfaces can be shared too. + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + } + } +} diff --git a/Content.Server/Chemistry/Components/ChemMasterComponent.cs b/Content.Server/Chemistry/Components/ChemMasterComponent.cs index 9148b8c316..b4811d0728 100644 --- a/Content.Server/Chemistry/Components/ChemMasterComponent.cs +++ b/Content.Server/Chemistry/Components/ChemMasterComponent.cs @@ -9,6 +9,7 @@ using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.ActionBlocker; using Content.Shared.Chemistry.Components; +using Content.Shared.Containers.ItemSlots; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Popups; @@ -33,15 +34,9 @@ namespace Content.Server.Chemistry.Components /// [RegisterComponent] [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(IInteractUsing))] - public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing + [ComponentReference(typeof(SharedChemMasterComponent))] + public class ChemMasterComponent : SharedChemMasterComponent, IActivate { - [ViewVariables] - public ContainerSlot BeakerContainer = default!; - - [ViewVariables] - public bool HasBeaker => BeakerContainer.ContainedEntity != null; - [ViewVariables] private bool _bufferModeTransfer = true; @@ -73,13 +68,7 @@ namespace Content.Server.Chemistry.Components UserInterface.OnReceiveMessage += OnUiReceiveMessage; } - // Name relied upon by construction graph machine.yml to ensure beaker doesn't get deleted - BeakerContainer = - ContainerHelpers.EnsureContainer(Owner, $"{Name}-reagentContainerContainer"); - _bufferSolution = EntitySystem.Get().EnsureSolution(Owner.Uid, SolutionName); - - UpdateUserInterface(); } [Obsolete("Component Messages are deprecated, use Entity Events instead.")] @@ -126,7 +115,7 @@ namespace Content.Server.Chemistry.Components switch (msg.action) { case UiAction.Eject: - TryEject(obj.Session.AttachedEntity); + EntitySystem.Get().TryEjectToHands(OwnerUid, BeakerSlot, obj.Session.AttachedEntityUid); break; case UiAction.ChemButton: TransferReagent(msg.id, msg.amount, msg.isBuffer); @@ -180,7 +169,7 @@ namespace Content.Server.Chemistry.Components /// Returns a private ChemMasterBoundUserInterfaceState GetUserInterfaceState() { - var beaker = BeakerContainer.ContainedEntity; + var beaker = BeakerSlot.Item; if (beaker is null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get().TryGetSolution(beaker.Uid, fits.Solution, out var beakerSolution)) { @@ -201,34 +190,10 @@ namespace Content.Server.Chemistry.Components UserInterface?.SetState(state); } - /// - /// If this component contains an entity with a , eject it. - /// Tries to eject into user's hands first, then ejects onto chem master if both hands are full. - /// - public void TryEject(IEntity user) - { - if (!HasBeaker) - return; - - var beaker = BeakerContainer.ContainedEntity; - - if (beaker is null) - return; - - BeakerContainer.Remove(beaker); - UpdateUserInterface(); - - if (!user.TryGetComponent(out var hands) || - !beaker.TryGetComponent(out var item)) - return; - if (hands.CanPutInHand(item)) - hands.PutInHand(item); - } - private void TransferReagent(string id, FixedPoint2 amount, bool isBuffer) { - if (!HasBeaker && _bufferModeTransfer) return; - var beaker = BeakerContainer.ContainedEntity; + if (!BeakerSlot.HasItem && _bufferModeTransfer) return; + var beaker = BeakerSlot.Item; if (beaker is null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get().TryGetSolution(beaker.Uid, fits.Solution, out var beakerSolution)) @@ -390,61 +355,6 @@ namespace Content.Server.Chemistry.Components } } - /// - /// Called when you click the owner entity with something in your active hand. If the entity in your hand - /// contains a , if you have hands, and if the chem master doesn't already - /// hold a container, it will be added to the chem master. - /// - /// Data relevant to the event such as the actor which triggered it. - /// - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) - { - if (!args.User.TryGetComponent(out HandsComponent? hands)) - { - Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-interact-using-no-hands")); - return true; - } - - if (hands.GetActiveHand == null) - { - Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-interact-using-nothing-in-hands")); - return false; - } - - var activeHandEntity = hands.GetActiveHand.Owner; - if (activeHandEntity.HasComponent()) - { - if (HasBeaker) - { - Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-has-beaker-already-message")); - } - else if (!activeHandEntity.HasComponent()) - { - //If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit. - Owner.PopupMessage(args.User, - Loc.GetString("chem-master-component-container-too-large-message", - ("container", activeHandEntity))); - } - else - { - BeakerContainer.Insert(activeHandEntity); - UpdateUserInterface(); - } - } - else - { - Owner.PopupMessage(args.User, - Loc.GetString("chem-master-component-cannot-put-entity-message", ("entity", activeHandEntity))); - // TBD: This is very definitely hax so that Construction & Wires get a chance to handle things. - // When this is ECS'd, drop this in favour of proper prioritization. - // Since this is a catch-all handler, that means do this last! - // Also note ReagentDispenserComponent did something similar before I got here. - return false; - } - - return true; - } - private void ClickSound() { SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); diff --git a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs index 2464f6071b..92168f5e13 100644 --- a/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs +++ b/Content.Server/Chemistry/Components/ReagentDispenserComponent.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; using Content.Server.Hands.Components; -using Content.Server.Items; using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.ActionBlocker; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Dispenser; +using Content.Shared.Containers.ItemSlots; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Popups; @@ -18,7 +17,6 @@ using Content.Shared.Sound; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Audio; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; @@ -38,21 +36,19 @@ namespace Content.Server.Chemistry.Components /// [RegisterComponent] [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(IInteractUsing))] - public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing + [ComponentReference(typeof(SharedReagentDispenserComponent))] + public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate { private static ReagentInventoryComparer _comparer = new(); public static string SolutionName = "reagent"; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [ViewVariables] public ContainerSlot BeakerContainer = default!; [ViewVariables] [DataField("pack")] private string _packPrototypeId = ""; [DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - [ViewVariables] public bool HasBeaker => BeakerContainer.ContainedEntity != null; [ViewVariables] private FixedPoint2 _dispenseAmount = FixedPoint2.New(10); [UsedImplicitly] @@ -84,12 +80,7 @@ namespace Content.Server.Chemistry.Components UserInterface.OnReceiveMessage += OnUiReceiveMessage; } - // Name relied upon by construction graph machine.yml to ensure beaker doesn't get deleted - BeakerContainer = - ContainerHelpers.EnsureContainer(Owner, $"{Name}-reagentContainerContainer"); - InitializeFromPrototype(); - UpdateUserInterface(); } [Obsolete("Component Messages are deprecated, use Entity Events instead.")] @@ -157,7 +148,7 @@ namespace Content.Server.Chemistry.Components switch (msg.Button) { case UiButton.Eject: - TryEject(obj.Session.AttachedEntity); + EntitySystem.Get().TryEjectToHands(OwnerUid, BeakerSlot, obj.Session.AttachedEntityUid); break; case UiButton.Clear: TryClear(); @@ -190,7 +181,7 @@ namespace Content.Server.Chemistry.Components _dispenseAmount = FixedPoint2.New(100); break; case UiButton.Dispense: - if (HasBeaker) + if (BeakerSlot.HasItem) { TryDispense(msg.DispenseIndex); } @@ -233,7 +224,7 @@ namespace Content.Server.Chemistry.Components /// Returns a private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState() { - var beaker = BeakerContainer.ContainedEntity; + var beaker = BeakerSlot.Item; if (beaker == null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get().TryGetSolution(beaker.Uid, fits.Solution, out var solution)) { @@ -253,40 +244,19 @@ namespace Content.Server.Chemistry.Components UserInterface?.SetState(state); } - /// - /// If this component contains an entity with a , eject it. - /// Tries to eject into user's hands first, then ejects onto dispenser if both hands are full. - /// - public void TryEject(IEntity user) - { - if (!HasBeaker) - return; - - var beaker = BeakerContainer.ContainedEntity; - if (beaker is null) - return; - - BeakerContainer.Remove(beaker); - UpdateUserInterface(); - - if (!user.TryGetComponent(out var hands) || - !beaker.TryGetComponent(out var item)) - return; - if (hands.CanPutInHand(item)) - hands.PutInHand(item); - } - /// /// If this component contains an entity with a , remove all of it's reagents / solutions. /// private void TryClear() { - if (!HasBeaker || !BeakerContainer.ContainedEntity!.TryGetComponent(out FitsInDispenserComponent? fits) || + var beaker = BeakerSlot.Item; + + if (beaker == null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get() - .TryGetSolution(BeakerContainer.ContainedEntity.Uid, fits.Solution, out var solution)) + .TryGetSolution(beaker.Uid, fits.Solution, out var solution)) return; - EntitySystem.Get().RemoveAllSolution(BeakerContainer.ContainedEntity!.Uid, solution); + EntitySystem.Get().RemoveAllSolution(beaker.Uid, solution); UpdateUserInterface(); } @@ -297,14 +267,14 @@ namespace Content.Server.Chemistry.Components /// The index of the reagent in Inventory. private void TryDispense(int dispenseIndex) { - if (!HasBeaker) return; + var beaker = BeakerSlot.Item; - if (BeakerContainer.ContainedEntity is not {} contained || !contained.TryGetComponent(out FitsInDispenserComponent? fits) + if (beaker is null || !beaker.TryGetComponent(out FitsInDispenserComponent? fits) || !EntitySystem.Get() - .TryGetSolution(BeakerContainer.ContainedEntity.Uid, fits.Solution, out var solution)) return; + .TryGetSolution(beaker.Uid, fits.Solution, out var solution)) return; EntitySystem.Get() - .TryAddReagent(BeakerContainer.ContainedEntity.Uid, solution, Inventory[dispenseIndex].ID, _dispenseAmount, out _); + .TryAddReagent(beaker.Uid, solution, Inventory[dispenseIndex].ID, _dispenseAmount, out _); UpdateUserInterface(); } @@ -333,51 +303,6 @@ namespace Content.Server.Chemistry.Components } } - /// - /// Called when you click the owner entity with something in your active hand. If the entity in your hand - /// contains a , if you have hands, and if the dispenser doesn't already - /// hold a container, it will be added to the dispenser. - /// - /// Data relevant to the event such as the actor which triggered it. - /// - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) - { - if (!args.User.TryGetComponent(out HandsComponent? hands)) - { - Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-interact-using-no-hands")); - return true; - } - - if (hands.GetActiveHand == null) - { - Owner.PopupMessage(args.User, - Loc.GetString("reagent-dispenser-component-interact-using-nothing-in-hands")); - return false; - } - - var activeHandEntity = hands.GetActiveHand.Owner; - if (activeHandEntity.HasComponent()) - { - if (HasBeaker) - { - Owner.PopupMessage(args.User, - Loc.GetString("reagent-dispenser-component-has-container-already-message")); - return false; - } - - BeakerContainer.Insert(activeHandEntity); - UpdateUserInterface(); - - return true; - } - - Owner.PopupMessage(args.User, - Loc.GetString("reagent-dispenser-component-cannot-put-entity-message", - ("entity", activeHandEntity))); - - return false; - } - private void ClickSound() { SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); diff --git a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs index 993ff518bf..55a7bf6591 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs @@ -1,76 +1,21 @@ -using Content.Shared.Verbs; using Content.Server.Chemistry.Components; -using Content.Server.Chemistry.Components.SolutionManager; -using Content.Server.Construction.Components; +using Content.Shared.Chemistry.EntitySystems; using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Content.Shared.ActionBlocker; namespace Content.Server.Chemistry.EntitySystems { [UsedImplicitly] - public class ChemMasterSystem : EntitySystem + public sealed class ChemMasterSystem : SharedChemMasterSystem { - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - public override void Initialize() { base.Initialize(); - - SubscribeLocalEvent(OnSolutionChange); - SubscribeLocalEvent(AddInsertVerb); - SubscribeLocalEvent(AddEjectVerb); - } - - // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? Maybe using something like the - // system mentioned in #4538? The code here is basically identical to the stuff in ChemDispenserSystem - private void AddEjectVerb(EntityUid uid, ChemMasterComponent component, GetAlternativeVerbsEvent args) - { - if (args.Hands == null || - !args.CanAccess || - !args.CanInteract || - !component.HasBeaker || - !_actionBlockerSystem.CanPickup(args.User.Uid)) - return; - - Verb verb = new(); - verb.Act = () => - { - component.TryEject(args.User); - component.UpdateUserInterface(); - }; - verb.Category = VerbCategory.Eject; - verb.Text = component.BeakerContainer.ContainedEntity!.Name; - args.Verbs.Add(verb); - } - - private void AddInsertVerb(EntityUid uid, ChemMasterComponent component, GetInteractionVerbsEvent args) - { - if (args.Using == null || - !args.CanAccess || - !args.CanInteract || - component.HasBeaker || - !args.Using.HasComponent() || - !_actionBlockerSystem.CanDrop(args.User.Uid)) - return; - - Verb verb = new(); - verb.Act = () => - { - component.BeakerContainer.Insert(args.Using); - component.UpdateUserInterface(); - }; - verb.Category = VerbCategory.Insert; - verb.Text = args.Using.Name; - args.Verbs.Add(verb); - } - - private void OnSolutionChange(EntityUid uid, ChemMasterComponent component, - SolutionChangedEvent solutionChanged) - { - component.UpdateUserInterface(); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); } } } diff --git a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs index 7a224efcaf..f2fba97541 100644 --- a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs @@ -1,78 +1,21 @@ -using Content.Shared.Verbs; using Content.Server.Chemistry.Components; -using Content.Server.Chemistry.Components.SolutionManager; -using Content.Server.Construction.Components; +using Content.Shared.Chemistry.EntitySystems; using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Content.Shared.ActionBlocker; namespace Content.Server.Chemistry.EntitySystems { [UsedImplicitly] - public class ReagentDispenserSystem : EntitySystem + public sealed class ReagentDispenserSystem : SharedReagentDispenserSystem { - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - public override void Initialize() { base.Initialize(); - - SubscribeLocalEvent(OnSolutionChange); - - SubscribeLocalEvent(AddEjectVerb); - SubscribeLocalEvent(AddInsertVerb); - } - - // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? Maybe using something like the - // system mentioned in #4538? The code here is basically identical to the stuff in ChemDispenserSystem. - private void AddEjectVerb(EntityUid uid, ReagentDispenserComponent component, GetAlternativeVerbsEvent args) - { - if (args.Hands == null || - !args.CanAccess || - !args.CanInteract || - !component.HasBeaker || - !_actionBlockerSystem.CanPickup(args.User.Uid)) - return; - - Verb verb = new(); - verb.Act = () => - { - component.TryEject(args.User); - component.UpdateUserInterface(); - }; - verb.Category = VerbCategory.Eject; - verb.Text = component.BeakerContainer.ContainedEntity!.Name; - - args.Verbs.Add(verb); - } - - private void AddInsertVerb(EntityUid uid, ReagentDispenserComponent component, GetInteractionVerbsEvent args) - { - if (args.Using == null || - !args.CanAccess || - !args.CanInteract || - component.HasBeaker || - !args.Using.HasComponent() || - !_actionBlockerSystem.CanDrop(args.User.Uid)) - return; - - Verb verb = new(); - verb.Act = () => - { - component.BeakerContainer.Insert(args.Using); - component.UpdateUserInterface(); - }; - verb.Category = VerbCategory.Insert; - verb.Text = args.Using.Name; - args.Verbs.Add(verb); - } - - - private void OnSolutionChange(EntityUid uid, ReagentDispenserComponent component, SolutionChangedEvent args) - { - component.UpdateUserInterface(); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); + SubscribeLocalEvent((_, comp, _) => comp.UpdateUserInterface()); } } } diff --git a/Content.Server/Containers/EmptyOnMachineDeconstructSystem.cs b/Content.Server/Containers/EmptyOnMachineDeconstructSystem.cs index 19e358fc4d..14e495edee 100644 --- a/Content.Server/Containers/EmptyOnMachineDeconstructSystem.cs +++ b/Content.Server/Containers/EmptyOnMachineDeconstructSystem.cs @@ -1,9 +1,8 @@ -using Content.Shared.Verbs; using Content.Server.Construction.Components; using JetBrains.Annotations; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; +using Content.Shared.Containers.ItemSlots; namespace Content.Server.Containers { @@ -18,6 +17,17 @@ namespace Content.Server.Containers base.Initialize(); SubscribeLocalEvent(OnDeconstruct); + SubscribeLocalEvent(OnSlotsDeconstruct); + } + + // really this should be handled by ItemSlotsSystem, but for whatever reason MachineDeconstructedEvent is server-side? So eh. + private void OnSlotsDeconstruct(EntityUid uid, ItemSlotsComponent component, MachineDeconstructedEvent args) + { + foreach (var slot in component.Slots.Values) + { + if (slot.EjectOnDeconstruct && slot.Item != null) + slot.ContainerSlot.Remove(slot.Item); + } } private void OnDeconstruct(EntityUid uid, EmptyOnMachineDeconstructComponent component, MachineDeconstructedEvent ev) diff --git a/Content.Shared/Access/SharedIdCardConsoleComponent.cs b/Content.Shared/Access/SharedIdCardConsoleComponent.cs index 61f7ffdf4b..5b394a1554 100644 --- a/Content.Shared/Access/SharedIdCardConsoleComponent.cs +++ b/Content.Shared/Access/SharedIdCardConsoleComponent.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using Content.Shared.Containers.ItemSlots; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Shared.Access { @@ -12,6 +14,12 @@ namespace Content.Shared.Access public const int MaxFullNameLength = 256; public const int MaxJobTitleLength = 256; + [DataField("privilegedIdSlot")] + public ItemSlot PrivilegedIdSlot = new(); + + [DataField("targetIdSlot")] + public ItemSlot TargetIdSlot = new(); + public enum UiButton { PrivilegedId, diff --git a/Content.Shared/Access/SharedIdCardConsoleSystem.cs b/Content.Shared/Access/SharedIdCardConsoleSystem.cs new file mode 100644 index 0000000000..ba9decb18f --- /dev/null +++ b/Content.Shared/Access/SharedIdCardConsoleSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Containers.ItemSlots; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Shared.Access +{ + [UsedImplicitly] + public abstract class SharedIdCardConsoleSystem : EntitySystem + { + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentRemove); + } + + private void OnComponentInit(EntityUid uid, SharedIdCardConsoleComponent component, ComponentInit args) + { + _itemSlotsSystem.AddItemSlot(uid, $"{component.Name}-privilegedId", component.PrivilegedIdSlot); + _itemSlotsSystem.AddItemSlot(uid, $"{component.Name}-targetId", component.TargetIdSlot); + } + + private void OnComponentRemove(EntityUid uid, SharedIdCardConsoleComponent component, ComponentRemove args) + { + _itemSlotsSystem.RemoveItemSlot(uid, component.PrivilegedIdSlot); + _itemSlotsSystem.RemoveItemSlot(uid, component.TargetIdSlot); + } + } +} diff --git a/Content.Shared/Acts/ActSystem.cs b/Content.Shared/Acts/ActSystem.cs index 82168335c2..272a3b45f0 100644 --- a/Content.Shared/Acts/ActSystem.cs +++ b/Content.Shared/Acts/ActSystem.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -17,15 +17,9 @@ namespace Content.Shared.Acts void OnDestroy(DestructionEventArgs eventArgs); } - public class DestructionEventArgs : EntityEventArgs - { - public EntityUid Owner { get; init; } = default!; - } + public class DestructionEventArgs : EntityEventArgs { } - public class BreakageEventArgs : EventArgs - { - public EntityUid Owner { get; init; } = default!; - } + public class BreakageEventArgs : EntityEventArgs { } public interface IBreakAct { @@ -55,11 +49,9 @@ namespace Content.Shared.Acts { public void HandleDestruction(EntityUid owner) { - var eventArgs = new DestructionEventArgs - { - Owner = owner - }; + var eventArgs = new DestructionEventArgs(); + RaiseLocalEvent(owner, eventArgs, false); var destroyActs = EntityManager.GetComponents(owner).ToList(); foreach (var destroyAct in destroyActs) @@ -88,10 +80,8 @@ namespace Content.Shared.Acts public void HandleBreakage(EntityUid owner) { - var eventArgs = new BreakageEventArgs - { - Owner = owner, - }; + var eventArgs = new BreakageEventArgs(); + RaiseLocalEvent(owner, eventArgs, false); var breakActs = EntityManager.GetComponents(owner).ToList(); foreach (var breakAct in breakActs) { diff --git a/Content.Shared/Chemistry/Components/SharedChemMasterComponent.cs b/Content.Shared/Chemistry/Components/SharedChemMasterComponent.cs index 2e01bc8080..eb6f9732a0 100644 --- a/Content.Shared/Chemistry/Components/SharedChemMasterComponent.cs +++ b/Content.Shared/Chemistry/Components/SharedChemMasterComponent.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using Content.Shared.Chemistry.Reagent; using Content.Shared.Cloning; +using Content.Shared.Containers.ItemSlots; using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Shared.Chemistry.Components { @@ -15,6 +15,9 @@ namespace Content.Shared.Chemistry.Components /// public class SharedChemMasterComponent : Component { + [DataField("beakerSlot")] + public ItemSlot BeakerSlot = new(); + public override string Name => "ChemMaster"; public const string SolutionName = "buffer"; diff --git a/Content.Shared/Chemistry/Dispenser/SharedReagentDispenserComponent.cs b/Content.Shared/Chemistry/Dispenser/SharedReagentDispenserComponent.cs index 407c18b8c2..7f967ea95f 100644 --- a/Content.Shared/Chemistry/Dispenser/SharedReagentDispenserComponent.cs +++ b/Content.Shared/Chemistry/Dispenser/SharedReagentDispenserComponent.cs @@ -1,9 +1,11 @@ -using System; +using System; using System.Collections.Generic; using Content.Shared.Chemistry.Reagent; +using Content.Shared.Containers.ItemSlots; using Content.Shared.FixedPoint; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Shared.Chemistry.Dispenser { @@ -17,6 +19,9 @@ namespace Content.Shared.Chemistry.Dispenser { public override string Name => "ReagentDispenser"; + [DataField("beakerSlot")] + public ItemSlot BeakerSlot = new(); + /// /// A list of reagents which this may dispense. Defined in yaml prototype, see . /// diff --git a/Content.Shared/Chemistry/EntitySystems/SharedChemMasterSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedChemMasterSystem.cs new file mode 100644 index 0000000000..e7046f6eba --- /dev/null +++ b/Content.Shared/Chemistry/EntitySystems/SharedChemMasterSystem.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Chemistry.Components; + +namespace Content.Shared.Chemistry.EntitySystems +{ + [UsedImplicitly] + public abstract class SharedChemMasterSystem : EntitySystem + { + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentRemove); + } + + private void OnComponentInit(EntityUid uid, SharedChemMasterComponent component, ComponentInit args) + { + _itemSlotsSystem.AddItemSlot(uid, $"{component.Name}-beaker", component.BeakerSlot); + } + + private void OnComponentRemove(EntityUid uid, SharedChemMasterComponent component, ComponentRemove args) + { + _itemSlotsSystem.RemoveItemSlot(uid, component.BeakerSlot); + } + } +} diff --git a/Content.Shared/Chemistry/EntitySystems/SharedReagentDispenserSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedReagentDispenserSystem.cs new file mode 100644 index 0000000000..9fb60efec6 --- /dev/null +++ b/Content.Shared/Chemistry/EntitySystems/SharedReagentDispenserSystem.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Chemistry.Dispenser; + +namespace Content.Shared.Chemistry.EntitySystems +{ + [UsedImplicitly] + public abstract class SharedReagentDispenserSystem : EntitySystem + { + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentRemove); + } + + private void OnComponentInit(EntityUid uid, SharedReagentDispenserComponent component, ComponentInit args) + { + _itemSlotsSystem.AddItemSlot(uid, $"{component.Name}-beaker", component.BeakerSlot); + } + + private void OnComponentRemove(EntityUid uid, SharedReagentDispenserComponent component, ComponentRemove args) + { + _itemSlotsSystem.RemoveItemSlot(uid, component.BeakerSlot); + } + } +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs index 25b4b2ba8f..de7792b312 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs @@ -133,10 +133,47 @@ namespace Content.Shared.Containers.ItemSlots [ViewVariables] public ContainerSlot ContainerSlot = default!; + /// + /// If this slot belongs to some de-constructible component, should the item inside the slot be ejected upon + /// deconstruction? + /// + /// + /// The actual deconstruction logic is handled by the server-side EmptyOnMachineDeconstructSystem. + /// + [DataField("ejectOnDeconstruct")] + public bool EjectOnDeconstruct = true; + + /// + /// If this slot belongs to some breakable or destructible entity, should the item inside the slot be + /// ejected when it is broken or destroyed? + /// + [DataField("ejectOnBreak")] + public bool EjectOnBreak = false; + + /// + /// If this is not an empty string, this will generate a popup when someone attempts to insert a bad item + /// into this slot. This string will be passed through localization. + /// + [DataField("whitelistFailPopup")] + public string WhitelistFailPopup = string.Empty; + + /// + /// If the user interacts with an entity with an already-filled item slot, should they attempt to swap out the item? + /// + /// + /// Useful for things like chem dispensers, but undesirable for things like the ID card console, where you + /// want to insert more than one item that matches the same whitelist. + /// + [DataField("swap")] + public bool Swap = true; + public string ID => ContainerSlot.ID; // Convenience properties public bool HasItem => ContainerSlot.ContainedEntity != null; public IEntity? Item => ContainerSlot.ContainedEntity; + + // and to make it easier for whenever IEntity is removed + public EntityUid? ItemUid => ContainerSlot.ContainedEntity?.Uid; } } diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index e6095ad0d1..62cf743045 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -1,6 +1,8 @@ using Content.Shared.ActionBlocker; +using Content.Shared.Acts; using Content.Shared.Hands.Components; using Content.Shared.Interaction; +using Content.Shared.Popups; using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Containers; @@ -22,6 +24,7 @@ namespace Content.Shared.Containers.ItemSlots public class ItemSlotsSystem : EntitySystem { [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; public override void Initialize() { @@ -36,6 +39,9 @@ namespace Content.Shared.Containers.ItemSlots SubscribeLocalEvent(AddEjectVerbs); SubscribeLocalEvent(AddInteractionVerbsVerbs); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent(GetItemSlotsState); SubscribeLocalEvent(HandleItemSlotsState); } @@ -139,7 +145,7 @@ namespace Content.Shared.Containers.ItemSlots foreach (var slot in itemSlots.Slots.Values) { - if (!CanInsert(args.UsedUid, slot, swap: true)) + if (!CanInsert(uid, args.UsedUid, slot, swap: slot.Swap, popup: args.UserUid)) continue; // Drop the held item onto the floor. Return if the user cannot drop. @@ -174,7 +180,11 @@ namespace Content.Shared.Containers.ItemSlots /// Check whether a given item can be inserted into a slot. Unless otherwise specified, this will return /// false if the slot is already filled. /// - public bool CanInsert(EntityUid uid, ItemSlot slot, bool swap = false) + /// + /// If a popup entity is given, and if the item slot is set to generate a popup message when it fails to + /// pass the whitelist, then this will generate a popup. + /// + public bool CanInsert(EntityUid uid, EntityUid usedUid, ItemSlot slot, bool swap = false, EntityUid? popup = null) { if (slot.Locked) return false; @@ -182,8 +192,12 @@ namespace Content.Shared.Containers.ItemSlots if (!swap && slot.HasItem) return false; - if (slot.Whitelist != null && !slot.Whitelist.IsValid(uid)) + if (slot.Whitelist != null && !slot.Whitelist.IsValid(usedUid)) + { + if (popup.HasValue && !string.IsNullOrWhiteSpace(slot.WhitelistFailPopup)) + _popupSystem.PopupEntity(Loc.GetString(slot.WhitelistFailPopup), uid, Filter.Entities(popup.Value)); return false; + } // We should also check ContainerSlot.CanInsert, but that prevents swapping interactions. Given that // ContainerSlot.CanInsert gets called when the item is actually inserted anyways, we can just get away with @@ -212,7 +226,30 @@ namespace Content.Shared.Containers.ItemSlots /// False if failed to insert item public bool TryInsert(EntityUid uid, ItemSlot slot, IEntity item) { - if (!CanInsert(item.Uid, slot)) + if (!CanInsert(uid, item.Uid, slot)) + return false; + + Insert(uid, slot, item); + return true; + } + + /// + /// Tries to insert item into a specific slot from an entity's hand. + /// + /// False if failed to insert item + public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, SharedHandsComponent? hands = null) + { + if (!Resolve(user, ref hands, false)) + return false; + + if (!hands.TryGetActiveHeldEntity(out var item)) + return false; + + if (!CanInsert(uid, item.Uid, slot)) + return false; + + // hands.Drop(item) checks CanDrop action blocker + if (!_actionBlockerSystem.CanInteract(user) && hands.Drop(item)) return false; Insert(uid, slot, item); @@ -364,7 +401,7 @@ namespace Content.Shared.Containers.ItemSlots foreach (var slot in itemSlots.Slots.Values) { - if (!CanInsert(args.Using.Uid, slot)) + if (!CanInsert(uid, args.Using.Uid, slot)) continue; var verbSubject = slot.Name != string.Empty @@ -397,6 +434,18 @@ namespace Content.Shared.Containers.ItemSlots } #endregion + /// + /// Eject items from (some) slots when the entity is destroyed. + /// + private void OnBreak(EntityUid uid, ItemSlotsComponent component, EntityEventArgs args) + { + foreach (var slot in component.Slots.Values) + { + if (slot.EjectOnBreak && slot.HasItem) + TryEject(uid, slot, out var _); + } + } + /// /// Get the contents of some item slot. /// diff --git a/Resources/Locale/en-US/access/components/id-card-console-component.ftl b/Resources/Locale/en-US/access/components/id-card-console-component.ftl index a8c16d0713..1ac8e65047 100644 --- a/Resources/Locale/en-US/access/components/id-card-console-component.ftl +++ b/Resources/Locale/en-US/access/components/id-card-console-component.ftl @@ -7,8 +7,5 @@ id-card-console-window-eject-button = Eject id-card-console-window-insert-button = Insert access-id-card-console-component-no-hands-error = You have no hands. -access-id-card-console-component-cannot-let-go-error = You can't let go of the ID card! - -## For Verbs id-card-console-privileged-id = Privileged ID id-card-console-target-id = Target ID \ No newline at end of file diff --git a/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl b/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl index 5300a3f37c..57c7d3fd66 100644 --- a/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/chem-master-component.ftl @@ -1,11 +1,7 @@ ## Entity chem-master-component-activate-no-hands = You have no hands. -chem-master-component-interact-using-no-hands = You have no hands. -chem-master-component-interact-using-nothing-in-hands = You have nothing in your hand! -chem-master-component-has-beaker-already-message = This ChemMaster already has a container in it. -chem-master-component-container-too-large-message = The {$container} is too large for the ChemMaster! -chem-master-component-cannot-put-entity-message = You can't put {$entity} in the ChemMaster! +chem-master-component-cannot-put-entity-message = You can't put this in the ChemMaster! ## Bound UI chem-master-bound-user-interface-title = ChemMaster 4000 diff --git a/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl b/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl index c9dc5fcd6a..48ec8f5213 100644 --- a/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl @@ -1,12 +1,7 @@ ## Entity reagent-dispenser-component-activate-no-hands = You have no hands. -reagent-dispenser-component-interact-using-no-hands = You have no hands. -reagent-dispenser-component-interact-using-nothing-in-hands = You have nothing in your hand! - -reagent-dispenser-component-has-container-already-message = This dispenser already has a container in it. -reagent-dispenser-component-container-too-large-message = The {$container} is too large for the ChemMaster! -reagent-dispenser-component-cannot-put-entity-message = You can't put { THE($entity) } in the dispenser! +reagent-dispenser-component-cannot-put-entity-message = You can't put this in the dispenser! ## Bound UI diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml index a2822c9e9f..3a7bfb9c7e 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml @@ -9,6 +9,11 @@ state: industrial-working - type: ReagentDispenser pack: ChemDispenserStandardInventory + beakerSlot: + whitelistFailPopup: reagent-dispenser-component-cannot-put-entity-message + whitelist: + components: + - FitsInDispenser - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: Construction @@ -30,7 +35,3 @@ - type: Wires BoardName: "chem_dispenser" LayoutId: chem_dispenser - - type: EmptyOnMachineDeconstruct - containers: - - ReagentDispenser-reagentContainerContainer - diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 1dbf7f9e4c..b45b9be8e3 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -126,6 +126,24 @@ - type: AccessReader access: [["HeadOfPersonnel"]] - type: IdCardConsole + privilegedIdSlot: + name: id-card-console-privileged-id + ejectSound: /Audio/Machines/id_swipe.ogg + insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg + ejectOnBreak: true + swap: false + whitelist: + components: + - IdCard + targetIdSlot: + name: id-card-console-target-id + ejectSound: /Audio/Machines/id_swipe.ogg + insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg + ejectOnBreak: true + swap: false + whitelist: + components: + - IdCard - type: ActivatableUI key: enum.IdCardConsoleUiKey.Key - type: ActivatableUIRequiresPower diff --git a/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml b/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml index d6ec34b1c9..8b3bc7a67b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml @@ -16,6 +16,11 @@ sprite: Structures/Machines/mixer.rsi state: mixer_loaded - type: ChemMaster + beakerSlot: + whitelistFailPopup: chem-master-component-cannot-put-entity-message + whitelist: + components: + - FitsInDispenser - type: Physics bodyType: Static fixtures: @@ -52,7 +57,4 @@ - type: Construction graph: machine node: machine - - type: EmptyOnMachineDeconstruct - containers: - - ChemMaster-reagentContainerContainer