From 1c7285825ca3ca8e50fe68df17017f8801f4bb8f Mon Sep 17 00:00:00 2001 From: mirrorcult Date: Tue, 8 Jun 2021 19:10:29 -0700 Subject: [PATCH] Refactor ExtinguisherCabinet->ItemCabinet and actually maps them in, adds EntityWhitelist (#4154) * i probably shouldnt have done this in one commit * map nonsense * fix example code * unnecessary * test * reviews * little fix for open datafield * add soul --- .../ExtinguisherCabinetVisualizer.cs | 40 ---- .../Components/ItemCabinetVisualizer.cs | 51 +++++ Content.Client/IgnoredComponents.cs | 3 +- .../Tests/Utility/EntityWhitelistTest.cs | 121 +++++++++++ .../ExtinguisherCabinetComponent.cs | 121 ----------- .../ExtinguisherCabinetFilledComponent.cs | 17 -- .../Components/ItemCabinetComponent.cs | 97 +++++++++ .../Items/Storage/StorageCounterComponent.cs | 1 + .../GameObjects/EntitySystems/BuckleSystem.cs | 4 +- .../EntitySystems/ItemCabinetSystem.cs | 195 ++++++++++++++++++ .../Components/ExtinguisherCabinet.cs | 4 +- .../Components/Interaction/IAfterInteract.cs | 2 +- .../Components/Interaction/IInteractHand.cs | 4 +- Content.Shared/Utility/EntityWhitelist.cs | 85 ++++++++ .../components/item-cabinet-component.ftl | 13 ++ Resources/Maps/saltern.yml | 192 +++++++++++++++++ .../Walls/extinguisher_cabinet.yml | 32 ++- .../Objects/Misc/fire_extinguisher.yml | 4 + 18 files changed, 795 insertions(+), 191 deletions(-) delete mode 100644 Content.Client/GameObjects/Components/ExtinguisherCabinetVisualizer.cs create mode 100644 Content.Client/GameObjects/Components/ItemCabinetVisualizer.cs create mode 100644 Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs delete mode 100644 Content.Server/GameObjects/Components/ExtinguisherCabinetComponent.cs delete mode 100644 Content.Server/GameObjects/Components/ExtinguisherCabinetFilledComponent.cs create mode 100644 Content.Server/GameObjects/Components/ItemCabinetComponent.cs create mode 100644 Content.Server/GameObjects/EntitySystems/ItemCabinetSystem.cs create mode 100644 Content.Shared/Utility/EntityWhitelist.cs create mode 100644 Resources/Locale/en-US/components/item-cabinet-component.ftl diff --git a/Content.Client/GameObjects/Components/ExtinguisherCabinetVisualizer.cs b/Content.Client/GameObjects/Components/ExtinguisherCabinetVisualizer.cs deleted file mode 100644 index a805fa0d60..0000000000 --- a/Content.Client/GameObjects/Components/ExtinguisherCabinetVisualizer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Shared.GameObjects.Components; -using JetBrains.Annotations; -using Robust.Client.GameObjects; - -namespace Content.Client.GameObjects.Components -{ - [UsedImplicitly] - public class ExtinguisherCabinetVisualizer : AppearanceVisualizer - { - public override void OnChangeData(AppearanceComponent component) - { - base.OnChangeData(component); - - var sprite = component.Owner.GetComponent(); - - if (component.TryGetData(ExtinguisherCabinetVisuals.IsOpen, out bool isOpen)) - { - if (isOpen) - { - if (component.TryGetData(ExtinguisherCabinetVisuals.ContainsExtinguisher, out bool contains)) - { - if (contains) - { - sprite.LayerSetState(0, "extinguisher_full"); - } - else - { - sprite.LayerSetState(0, "extinguisher_empty"); - } - - } - } - else - { - sprite.LayerSetState(0, "extinguisher_closed"); - } - } - } - } -} diff --git a/Content.Client/GameObjects/Components/ItemCabinetVisualizer.cs b/Content.Client/GameObjects/Components/ItemCabinetVisualizer.cs new file mode 100644 index 0000000000..3044e5e908 --- /dev/null +++ b/Content.Client/GameObjects/Components/ItemCabinetVisualizer.cs @@ -0,0 +1,51 @@ +using System.ComponentModel.DataAnnotations; +using Content.Shared.GameObjects.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Client.GameObjects.Components +{ + [UsedImplicitly] + public class ItemCabinetVisualizer : AppearanceVisualizer + { + // TODO proper layering + [DataField("fullState", required: true)] + private string _fullState = default!; + + [DataField("emptyState", required: true)] + private string _emptyState = default!; + + [DataField("closedState", required: true)] + private string _closedState = default!; + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.Owner.TryGetComponent(out var sprite) + && component.TryGetData(ItemCabinetVisuals.IsOpen, out bool isOpen)) + { + if (isOpen) + { + if (component.TryGetData(ItemCabinetVisuals.ContainsItem, out bool contains)) + { + if (contains) + { + sprite.LayerSetState(0, _fullState); + } + else + { + sprite.LayerSetState(0, _emptyState); + } + + } + } + else + { + sprite.LayerSetState(0, _closedState); + } + } + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index a8063e47fb..51e76489c9 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -158,8 +158,7 @@ namespace Content.Client "SignalTransmitter", "SignalButton", "SignalLinker", - "ExtinguisherCabinet", - "ExtinguisherCabinetFilled", + "ItemCabinet", "FireExtinguisher", "Firelock", "AtmosPlaque", diff --git a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs new file mode 100644 index 0000000000..755890e6e5 --- /dev/null +++ b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs @@ -0,0 +1,121 @@ +using System.IO; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Shared.GameObjects.Components.Tag; +using Content.Shared.Prototypes; +using Content.Shared.Utility; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; + +namespace Content.IntegrationTests.Tests.Utility +{ + [TestFixture] + [TestOf(typeof(EntityWhitelist))] + public class EntityWhitelistTest : ContentIntegrationTest + { + private const string InvalidComponent = "Sprite"; + private const string ValidComponent = "Physics"; + + private static readonly string Prototypes = $@" +- type: Tag + id: ValidTag +- type: Tag + id: InvalidTag + +- type: entity + id: WhitelistDummy + components: + - type: ItemCabinet + whitelist: + prototypes: + - ValidPrototypeDummy + components: + - {ValidComponent} + tags: + - ValidTag + +- type: entity + id: InvalidComponentDummy + components: + - type: {InvalidComponent} +- type: entity + id: InvalidTagDummy + components: + - type: Tag + tags: + - InvalidTag + +- type: entity + id: ValidComponentDummy + components: + - type: {ValidComponent} +- type: entity + id: ValidTagDummy + components: + - type: Tag + tags: + - ValidTag"; + + [Test] + public async Task Test() + { + var serverOptions = new ServerContentIntegrationOption {ExtraPrototypes = Prototypes}; + var server = StartServer(serverOptions); + + await server.WaitIdleAsync(); + var mapManager = server.ResolveDependency(); + var entityManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var mapId = new MapId(1); + var mapCoordinates = new MapCoordinates(0, 0, mapId); + + var validComponent = entityManager.SpawnEntity("ValidComponentDummy", mapCoordinates); + var validTag = entityManager.SpawnEntity("ValidTagDummy", mapCoordinates); + + var invalidComponent = entityManager.SpawnEntity("InvalidComponentDummy", mapCoordinates); + var invalidTag = entityManager.SpawnEntity("InvalidTagDummy", mapCoordinates); + + // Test instantiated on its own + var whitelistInst = new EntityWhitelist + { + Components = new[] {$"{ValidComponent}"}, + Tags = new[] {"ValidTag"} + }; + whitelistInst.UpdateRegistrations(); + Assert.That(whitelistInst, Is.Not.Null); + + Assert.That(whitelistInst.Components, Is.Not.Null); + Assert.That(whitelistInst.Tags, Is.Not.Null); + + Assert.That(whitelistInst.IsValid(validComponent), Is.True); + Assert.That(whitelistInst.IsValid(validTag), Is.True); + + Assert.That(whitelistInst.IsValid(invalidComponent), Is.False); + Assert.That(whitelistInst.IsValid(invalidTag), Is.False); + + // Test from serialized + var dummy = entityManager.SpawnEntity("WhitelistDummy", mapCoordinates); + var whitelistSer = dummy.GetComponent().Whitelist; + Assert.That(whitelistSer, Is.Not.Null); + + Assert.That(whitelistSer.Components, Is.Not.Null); + Assert.That(whitelistSer.Tags, Is.Not.Null); + + Assert.That(whitelistSer.IsValid(validComponent), Is.True); + Assert.That(whitelistSer.IsValid(validTag), Is.True); + + Assert.That(whitelistSer.IsValid(invalidComponent), Is.False); + Assert.That(whitelistSer.IsValid(invalidTag), Is.False); + }); + } + } +} diff --git a/Content.Server/GameObjects/Components/ExtinguisherCabinetComponent.cs b/Content.Server/GameObjects/Components/ExtinguisherCabinetComponent.cs deleted file mode 100644 index 7a3e77f4d6..0000000000 --- a/Content.Server/GameObjects/Components/ExtinguisherCabinetComponent.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Threading.Tasks; -using Content.Server.GameObjects.Components.GUI; -using Content.Server.GameObjects.Components.Items; -using Content.Server.GameObjects.Components.Items.Storage; -using Content.Server.Interfaces.GameObjects.Components.Items; -using Content.Shared.Audio; -using Content.Shared.GameObjects.Components; -using Content.Shared.Interfaces; -using Content.Shared.Interfaces.GameObjects.Components; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.Localization; -using Robust.Shared.Player; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components -{ - [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class ExtinguisherCabinetComponent : Component, IInteractUsing, IInteractHand, IActivate - { - public override string Name => "ExtinguisherCabinet"; - - private bool _opened = false; - [DataField("doorSound")] - private string _doorSound = "/Audio/Machines/machine_switch.ogg"; - - [ViewVariables] protected ContainerSlot ItemContainer = default!; - [ViewVariables] public string DoorSound => _doorSound; - - public override void Initialize() - { - base.Initialize(); - - ItemContainer = - ContainerHelpers.EnsureContainer(Owner, "extinguisher_cabinet", out _); - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - if (!_opened) - { - _opened = true; - ClickLatchSound(); - } - else - { - if (ItemContainer.ContainedEntity != null || !eventArgs.Using.HasComponent()) - { - return false; - } - var handsComponent = eventArgs.User.GetComponent(); - - if (!handsComponent.Drop(eventArgs.Using, ItemContainer)) - { - return false; - } - } - - UpdateVisuals(); - - return true; - } - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - if (_opened) - { - if (ItemContainer.ContainedEntity == null) - { - _opened = false; - ClickLatchSound(); - } - else if (eventArgs.User.TryGetComponent(out HandsComponent? hands)) - { - Owner.PopupMessage(eventArgs.User, - Loc.GetString("You take {0:extinguisherName} from the {1:cabinetName}", ItemContainer.ContainedEntity.Name, Owner.Name)); - hands.PutInHandOrDrop(ItemContainer.ContainedEntity.GetComponent()); - } - else if (ItemContainer.Remove(ItemContainer.ContainedEntity)) - { - ItemContainer.ContainedEntity.Transform.Coordinates = Owner.Transform.Coordinates; - } - } - else - { - _opened = true; - ClickLatchSound(); - } - - UpdateVisuals(); - - return true; - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - _opened = !_opened; - ClickLatchSound(); - UpdateVisuals(); - } - - private void UpdateVisuals() - { - if (Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - appearance.SetData(ExtinguisherCabinetVisuals.IsOpen, _opened); - appearance.SetData(ExtinguisherCabinetVisuals.ContainsExtinguisher, ItemContainer.ContainedEntity != null); - } - } - - private void ClickLatchSound() - { - // Don't have original click, this sounds close - SoundSystem.Play(Filter.Pvs(Owner), DoorSound, Owner, AudioHelpers.WithVariation(0.15f)); - } - } -} diff --git a/Content.Server/GameObjects/Components/ExtinguisherCabinetFilledComponent.cs b/Content.Server/GameObjects/Components/ExtinguisherCabinetFilledComponent.cs deleted file mode 100644 index fcedb47bbc..0000000000 --- a/Content.Server/GameObjects/Components/ExtinguisherCabinetFilledComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Robust.Shared.GameObjects; - -namespace Content.Server.GameObjects.Components -{ - [RegisterComponent] - public class ExtinguisherCabinetFilledComponent : ExtinguisherCabinetComponent - { - public override string Name => "ExtinguisherCabinetFilled"; - - public override void Initialize() - { - base.Initialize(); - - ItemContainer.Insert(Owner.EntityManager.SpawnEntity("FireExtinguisher", Owner.Transform.Coordinates)); - } - } -} diff --git a/Content.Server/GameObjects/Components/ItemCabinetComponent.cs b/Content.Server/GameObjects/Components/ItemCabinetComponent.cs new file mode 100644 index 0000000000..5b74560b6b --- /dev/null +++ b/Content.Server/GameObjects/Components/ItemCabinetComponent.cs @@ -0,0 +1,97 @@ +using System.Collections; +using System.Collections.Generic; +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.EntitySystems.ActionBlocker; +using Content.Shared.GameObjects.Verbs; +using Content.Shared.Utility; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components +{ + /// + /// Used for entities that can hold one item that fits the whitelist, which can be extracted by interacting with + /// the entity, and can have an item fitting the whitelist placed back inside + /// + [RegisterComponent] + public class ItemCabinetComponent : Component + { + public override string Name => "ItemCabinet"; + + /// + /// Sound to be played when the cabinet door is opened. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("doorSound")] + public string? DoorSound { get; set; } + + /// + /// The prototype that should be spawned inside the cabinet when it is map initialized. + /// + [ViewVariables] + [DataField("spawnPrototype")] + public string? SpawnPrototype { get; set; } + + /// + /// A whitelist defining which entities are allowed into the cabinet. + /// + [ViewVariables] + [DataField("whitelist")] + public EntityWhitelist? Whitelist = null; + + [ViewVariables] + public ContainerSlot ItemContainer = default!; + + /// + /// Whether the cabinet is currently open or not. + /// + [ViewVariables] + [DataField("opened")] + public bool Opened { get; set; } = false; + + [Verb] + public sealed class EjectItemFromCabinetVerb : Verb + { + protected override void GetData(IEntity user, ItemCabinetComponent component, VerbData data) + { + if (component.ItemContainer.ContainedEntity == null || !component.Opened || !ActionBlockerSystem.CanInteract(user)) + data.Visibility = VerbVisibility.Invisible; + else + { + data.Text = Loc.GetString("comp-item-cabinet-eject-verb-text"); + data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; + data.Visibility = VerbVisibility.Visible; + } + } + + protected override void Activate(IEntity user, ItemCabinetComponent component) + { + component.Owner.EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new TryEjectItemCabinetEvent(user), false); + } + } + + [Verb] + public sealed class ToggleItemCabinetVerb : Verb + { + protected override void GetData(IEntity user, ItemCabinetComponent component, VerbData data) + { + if (!ActionBlockerSystem.CanInteract(user)) + data.Visibility = VerbVisibility.Invisible; + else + { + data.Text = Loc.GetString(component.Opened ? "comp-item-cabinet-close-verb-text" : "comp-item-cabinet-open-verb-text"); + data.IconTexture = component.Opened ? "/Textures/Interface/VerbIcons/close.svg.192dpi.png" : "/Textures/Interface/VerbIcons/open.svg.192dpi.png"; + data.Visibility = VerbVisibility.Visible; + } + } + + protected override void Activate(IEntity user, ItemCabinetComponent component) + { + component.Owner.EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new ToggleItemCabinetEvent(), false); + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs index 1120352fba..554c0e9275 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/StorageCounterComponent.cs @@ -25,6 +25,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage [RegisterComponent] public class StorageCounterComponent : Component, ISerializationHooks { + // TODO Convert to EntityWhitelist [DataField("countTag")] private string? _countTag; diff --git a/Content.Server/GameObjects/EntitySystems/BuckleSystem.cs b/Content.Server/GameObjects/EntitySystems/BuckleSystem.cs index 77a5f9b4bd..811c3b7e74 100644 --- a/Content.Server/GameObjects/EntitySystems/BuckleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/BuckleSystem.cs @@ -31,10 +31,10 @@ namespace Content.Server.GameObjects.EntitySystems SubscribeLocalEvent(ContainerModifiedBuckle); SubscribeLocalEvent(ContainerModifiedStrap); - SubscribeLocalEvent(HandleAttackHand); + SubscribeLocalEvent(HandleInteractHand); } - private void HandleAttackHand(EntityUid uid, BuckleComponent component, InteractHandEvent args) + private void HandleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args) { args.Handled = component.TryUnbuckle(args.User); } diff --git a/Content.Server/GameObjects/EntitySystems/ItemCabinetSystem.cs b/Content.Server/GameObjects/EntitySystems/ItemCabinetSystem.cs new file mode 100644 index 0000000000..bc120e0ef0 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/ItemCabinetSystem.cs @@ -0,0 +1,195 @@ +using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Shared.Audio; +using Content.Shared.GameObjects.Components; +using Content.Shared.GameObjects.EntitySystems.ActionBlocker; +using Content.Shared.GameObjects.Verbs; +using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; +using Microsoft.EntityFrameworkCore; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.Player; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class ItemCabinetSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInitialize); + + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnActivateInWorld); + + SubscribeLocalEvent(OnTryEjectItemCabinet); + SubscribeLocalEvent(OnTryInsertItemCabinet); + SubscribeLocalEvent(OnToggleItemCabinet); + } + + private void OnMapInitialize(EntityUid uid, ItemCabinetComponent comp, MapInitEvent args) + { + var owner = EntityManager.GetEntity(uid); + comp.ItemContainer = + owner.EnsureContainer("item_cabinet", out _); + + if(comp.SpawnPrototype != null) + comp.ItemContainer.Insert(EntityManager.SpawnEntity(comp.SpawnPrototype, owner.Transform.Coordinates)); + + UpdateVisuals(comp); + } + + private void OnInteractUsing(EntityUid uid, ItemCabinetComponent comp, InteractUsingEvent args) + { + args.Handled = true; + if (!comp.Opened) + { + RaiseLocalEvent(uid, new ToggleItemCabinetEvent(), false); + } + else + { + RaiseLocalEvent(uid, new TryInsertItemCabinetEvent(args.User, args.Used), false); + } + + args.Handled = true; + } + + private void OnInteractHand(EntityUid uid, ItemCabinetComponent comp, InteractHandEvent args) + { + args.Handled = true; + if (comp.Opened) + { + if (comp.ItemContainer.ContainedEntity == null) + { + RaiseLocalEvent(uid, new ToggleItemCabinetEvent(), false); + return; + } + RaiseLocalEvent(uid, new TryEjectItemCabinetEvent(args.User), false); + } + else + { + RaiseLocalEvent(uid, new ToggleItemCabinetEvent(), false); + } + } + + private void OnActivateInWorld(EntityUid uid, ItemCabinetComponent comp, ActivateInWorldEvent args) + { + args.Handled = true; + RaiseLocalEvent(uid, new ToggleItemCabinetEvent(), false); + } + + /// + /// Toggles the ItemCabinet's state. + /// + private void OnToggleItemCabinet(EntityUid uid, ItemCabinetComponent comp, ToggleItemCabinetEvent args) + { + comp.Opened = !comp.Opened; + ClickLatchSound(comp); + UpdateVisuals(comp); + } + + /// + /// Tries to insert an entity into the ItemCabinet's slot from the user's hands. + /// + private static void OnTryInsertItemCabinet(EntityUid uid, ItemCabinetComponent comp, TryInsertItemCabinetEvent args) + { + if (comp.ItemContainer.ContainedEntity != null || args.Cancelled || (comp.Whitelist != null && !comp.Whitelist.IsValid(args.Item))) + { + return; + } + + if (!args.User.TryGetComponent(out var hands) || !hands.Drop(args.Item, comp.ItemContainer)) + { + return; + } + + UpdateVisuals(comp); + } + + /// + /// Tries to eject the ItemCabinet's item, either into the user's hands or onto the floor. + /// + private static void OnTryEjectItemCabinet(EntityUid uid, ItemCabinetComponent comp, TryEjectItemCabinetEvent args) + { + if (comp.ItemContainer.ContainedEntity == null || args.Cancelled) + return; + if (args.User.TryGetComponent(out HandsComponent? hands)) + { + + if (comp.ItemContainer.ContainedEntity.TryGetComponent(out var item)) + { + comp.Owner.PopupMessage(args.User, + Loc.GetString("comp-item-cabinet-successfully-taken", + ("item", comp.ItemContainer.ContainedEntity), + ("cabinet", comp.Owner))); + hands.PutInHandOrDrop(item); + } + } + else if (comp.ItemContainer.Remove(comp.ItemContainer.ContainedEntity)) + { + comp.ItemContainer.ContainedEntity.Transform.Coordinates = args.User.Transform.Coordinates; + } + UpdateVisuals(comp); + } + + private static void UpdateVisuals(ItemCabinetComponent comp) + { + if (comp.Owner.TryGetComponent(out SharedAppearanceComponent? appearance)) + { + appearance.SetData(ItemCabinetVisuals.IsOpen, comp.Opened); + appearance.SetData(ItemCabinetVisuals.ContainsItem, comp.ItemContainer.ContainedEntity != null); + } + } + + private static void ClickLatchSound(ItemCabinetComponent comp) + { + if (comp.DoorSound == null) return; + SoundSystem.Play(Filter.Pvs(comp.Owner), comp.DoorSound, comp.Owner, AudioHelpers.WithVariation(0.15f)); + } + } + + public class ToggleItemCabinetEvent : EntityEventArgs + { + } + + public class TryEjectItemCabinetEvent : CancellableEntityEventArgs + { + /// + /// The user who tried to eject the item. + /// + public IEntity User; + + public TryEjectItemCabinetEvent(IEntity user) + { + User = user; + } + } + + public class TryInsertItemCabinetEvent : CancellableEntityEventArgs + { + /// + /// The user who tried to eject the item. + /// + public IEntity User; + + /// + /// The item to be inserted. + /// + public IEntity Item; + + public TryInsertItemCabinetEvent(IEntity user, IEntity item) + { + User = user; + Item = item; + } + } +} diff --git a/Content.Shared/GameObjects/Components/ExtinguisherCabinet.cs b/Content.Shared/GameObjects/Components/ExtinguisherCabinet.cs index f7615bbb86..918d7dc5ed 100644 --- a/Content.Shared/GameObjects/Components/ExtinguisherCabinet.cs +++ b/Content.Shared/GameObjects/Components/ExtinguisherCabinet.cs @@ -5,9 +5,9 @@ using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components { [Serializable, NetSerializable] - public enum ExtinguisherCabinetVisuals + public enum ItemCabinetVisuals : byte { IsOpen, - ContainsExtinguisher + ContainsItem } } diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs index 6dff0d7193..ae851c2365 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IAfterInteract.cs @@ -48,7 +48,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components } /// - /// Raised when clicking on another object and no attack event was handled. + /// Raised directed on the used object when clicking on another object and no attack event was handled. /// [PublicAPI] public class AfterInteractEvent : HandledEntityEventArgs diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractHand.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractHand.cs index a3d00e60d4..92c8e2b74d 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractHand.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractHand.cs @@ -15,7 +15,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components /// /// Called when a player directly interacts with an empty hand when user is in range of the target entity. /// - [Obsolete("Use AttackHandMessage instead")] + [Obsolete("Use InteractHandEvent instead")] bool InteractHand(InteractHandEventArgs eventArgs); } @@ -32,7 +32,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components } /// - /// Raised when a target entity is interacted with by a user with an empty hand. + /// Raised directed on a target entity when it is interacted with by a user with an empty hand. /// [PublicAPI] public class InteractHandEvent : HandledEntityEventArgs diff --git a/Content.Shared/Utility/EntityWhitelist.cs b/Content.Shared/Utility/EntityWhitelist.cs new file mode 100644 index 0000000000..80697d5a2b --- /dev/null +++ b/Content.Shared/Utility/EntityWhitelist.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Tag; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Utility +{ + /// + /// Used to determine whether an entity fits a certain whitelist. + /// Does not whitelist by prototypes, since that is undesirable; you're better off just adding a tag to all + /// entity prototypes that need to be whitelisted, and checking for that. + /// + /// + /// whitelist: + /// tags: + /// - Cigarette + /// - FirelockElectronics + /// components: + /// - Buckle + /// - AsteroidRock + /// + [DataDefinition] + public class EntityWhitelist : ISerializationHooks + { + /// + /// Component names that are allowed in the whitelist. + /// + [DataField("components")] public string[]? Components = null; + + private List? _registrations = null; + + /// + /// Tags that are allowed in the whitelist. + /// + [DataField("tags")] public string[]? Tags = null; + + void ISerializationHooks.AfterDeserialization() + { + UpdateRegistrations(); + } + + public void UpdateRegistrations() + { + if (Components == null) return; + + var compfact = IoCManager.Resolve(); + _registrations = new List(); + foreach (var name in Components) + { + if (!compfact.TryGetRegistration(name, out var registration)) + { + Logger.Warning($"Invalid component name {name} passed to EntityWhitelist!"); + continue; + } + + _registrations.Add(registration); + } + } + + /// + /// Returns whether a given entity fits the whitelist. + /// + public bool IsValid(IEntity entity) + { + if (Tags != null) + { + if (entity.HasAnyTag(Tags)) + return true; + } + + if (_registrations != null) + { + foreach (var reg in _registrations) + { + if (entity.TryGetComponent(reg.Type, out _)) + return true; + } + } + return false; + } + } +} diff --git a/Resources/Locale/en-US/components/item-cabinet-component.ftl b/Resources/Locale/en-US/components/item-cabinet-component.ftl new file mode 100644 index 0000000000..9862050223 --- /dev/null +++ b/Resources/Locale/en-US/components/item-cabinet-component.ftl @@ -0,0 +1,13 @@ +### Used for item cabinet (fire extinguisher cabinets) + +## Displayed when the item is successfully taken out of the cabinet. + +comp-item-cabinet-successfully-taken = You take { THE($item) } from { THE($cabinet) }. + +## Displayed in the context menu for the item cabinet. + +comp-item-cabinet-eject-verb-text = Eject item + +comp-item-cabinet-open-verb-text = Open +comp-item-cabinet-close-verb-text = Close + diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 6321f1964e..49e78ffbdb 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -48006,4 +48006,196 @@ entities: type: Transform - canCollide: False type: Physics +- uid: 4921 + type: ExtinguisherCabinetFilled + components: + - pos: -26.5,-1.5 + parent: 853 + type: Transform +- uid: 4922 + type: ExtinguisherCabinetFilled + components: + - pos: -26.5,-4.5 + parent: 853 + type: Transform +- uid: 4923 + type: ExtinguisherCabinetFilled + components: + - pos: -15.5,-0.5 + parent: 853 + type: Transform +- uid: 4924 + type: ExtinguisherCabinetFilled + components: + - pos: -18.5,8.5 + parent: 853 + type: Transform +- uid: 4925 + type: ExtinguisherCabinetFilled + components: + - pos: -30.5,10.5 + parent: 853 + type: Transform +- uid: 4926 + type: ExtinguisherCabinetFilled + components: + - pos: -34.5,-2.5 + parent: 853 + type: Transform +- uid: 4927 + type: ExtinguisherCabinetFilled + components: + - pos: -16.5,-8.5 + parent: 853 + type: Transform +- uid: 4928 + type: ExtinguisherCabinetFilled + components: + - pos: -6.5,-16.5 + parent: 853 + type: Transform +- uid: 4929 + type: ExtinguisherCabinetFilled + components: + - pos: -6.5,-18.5 + parent: 853 + type: Transform +- uid: 4930 + type: ExtinguisherCabinetFilled + components: + - pos: 8.5,-17.5 + parent: 853 + type: Transform +- uid: 4931 + type: ExtinguisherCabinetFilled + components: + - pos: 16.5,-16.5 + parent: 853 + type: Transform +- uid: 4932 + type: ExtinguisherCabinetFilled + components: + - pos: 24.5,-16.5 + parent: 853 + type: Transform +- uid: 4933 + type: ExtinguisherCabinetFilled + components: + - pos: 25.5,-7.5 + parent: 853 + type: Transform +- uid: 4934 + type: ExtinguisherCabinetFilled + components: + - pos: 32.5,-5.5 + parent: 853 + type: Transform +- uid: 4935 + type: ExtinguisherCabinetFilled + components: + - pos: 41.5,-1.5 + parent: 853 + type: Transform +- uid: 4936 + type: ExtinguisherCabinetFilled + components: + - pos: 46.5,-11.5 + parent: 853 + type: Transform +- uid: 4937 + type: ExtinguisherCabinetFilled + components: + - pos: 52.5,-11.5 + parent: 853 + type: Transform +- uid: 4938 + type: ExtinguisherCabinetFilled + components: + - pos: 45.5,6.5 + parent: 853 + type: Transform +- uid: 4939 + type: ExtinguisherCabinetFilled + components: + - pos: 17.5,9.5 + parent: 853 + type: Transform +- uid: 4940 + type: ExtinguisherCabinetFilled + components: + - pos: 9.5,12.5 + parent: 853 + type: Transform +- uid: 4941 + type: ExtinguisherCabinetFilled + components: + - pos: -10.5,13.5 + parent: 853 + type: Transform +- uid: 4942 + type: ExtinguisherCabinetFilled + components: + - pos: -6.5,18.5 + parent: 853 + type: Transform +- uid: 4943 + type: ExtinguisherCabinetFilled + components: + - pos: 0.5,16.5 + parent: 853 + type: Transform +- uid: 4944 + type: ExtinguisherCabinetFilled + components: + - pos: 11.5,18.5 + parent: 853 + type: Transform +- uid: 4945 + type: ExtinguisherCabinetFilled + components: + - pos: 5.5,24.5 + parent: 853 + type: Transform +- uid: 4946 + type: ExtinguisherCabinetFilled + components: + - pos: 1.5,26.5 + parent: 853 + type: Transform +- uid: 4947 + type: ExtinguisherCabinetFilled + components: + - pos: -3.5,-25.5 + parent: 853 + type: Transform +- uid: 4948 + type: ExtinguisherCabinetFilled + components: + - pos: -13.5,-23.5 + parent: 853 + type: Transform +- uid: 4949 + type: ExtinguisherCabinetFilled + components: + - pos: 11.5,-28.5 + parent: 853 + type: Transform +- uid: 4950 + type: ExtinguisherCabinetFilled + components: + - pos: 20.5,-20.5 + parent: 853 + type: Transform +- uid: 4951 + type: ExtinguisherCabinetFilled + components: + - pos: 29.5,11.5 + parent: 853 + type: Transform +- uid: 4952 + type: ExtinguisherCabinetFilled + components: + - pos: 44.5,11.5 + parent: 853 + type: Transform ... diff --git a/Resources/Prototypes/Entities/Constructible/Walls/extinguisher_cabinet.yml b/Resources/Prototypes/Entities/Constructible/Walls/extinguisher_cabinet.yml index cf823b50a3..3750e879c2 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/extinguisher_cabinet.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/extinguisher_cabinet.yml @@ -1,7 +1,6 @@ - type: entity id: ExtinguisherCabinet name: extinguisher cabinet - abstract: true description: A small wall mounted cabinet designed to hold a fire extinguisher. components: - type: Clickable @@ -9,15 +8,40 @@ - type: Sprite sprite: Constructible/Misc/extinguisher_cabinet.rsi state: extinguisher_closed - - type: ExtinguisherCabinet + - type: ItemCabinet + doorSound: /Audio/Machines/machine_switch.ogg + whitelist: + components: + - FireExtinguisher - type: Appearance visuals: - - type: ExtinguisherCabinetVisualizer + - type: ItemCabinetVisualizer + emptyState: extinguisher_empty + fullState: extinguisher_full + closedState: extinguisher_closed placement: mode: SnapgridCenter +- type: entity + id: ExtinguisherCabinetOpen + parent: ExtinguisherCabinet + suffix: Open + components: + - type: ItemCabinet + opened: true + - type: entity id: ExtinguisherCabinetFilled parent: ExtinguisherCabinet + suffix: Filled components: - - type: ExtinguisherCabinetFilled + - type: ItemCabinet + spawnPrototype: FireExtinguisher + +- type: entity + id: ExtinguisherCabinetFilledOpen + parent: ExtinguisherCabinetFilled + suffix: Filled, Open + components: + - type: ItemCabinet + opened: true diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index 644971d9d3..dcbe3d4fde 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -31,6 +31,10 @@ transferAmount: 5 impulse: 50.0 - type: FireExtinguisher + - type: MeleeWeapon + damage: 10 + damageType: Blunt + hitSound: /Audio/Weapons/smash.ogg - type: Appearance visuals: - type: SprayVisualizer