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
This commit is contained in:
mirrorcult
2021-06-08 19:10:29 -07:00
committed by GitHub
parent 07494e4059
commit 1c7285825c
18 changed files with 795 additions and 191 deletions

View File

@@ -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<ISpriteComponent>();
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");
}
}
}
}
}

View File

@@ -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<SpriteComponent>(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);
}
}
}
}
}

View File

@@ -158,8 +158,7 @@ namespace Content.Client
"SignalTransmitter",
"SignalButton",
"SignalLinker",
"ExtinguisherCabinet",
"ExtinguisherCabinetFilled",
"ItemCabinet",
"FireExtinguisher",
"Firelock",
"AtmosPlaque",

View File

@@ -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<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
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<ItemCabinetComponent>().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);
});
}
}
}

View File

@@ -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<ContainerSlot>(Owner, "extinguisher_cabinet", out _);
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!_opened)
{
_opened = true;
ClickLatchSound();
}
else
{
if (ItemContainer.ContainedEntity != null || !eventArgs.Using.HasComponent<FireExtinguisherComponent>())
{
return false;
}
var handsComponent = eventArgs.User.GetComponent<IHandsComponent>();
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<ItemComponent>());
}
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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
[RegisterComponent]
public class ItemCabinetComponent : Component
{
public override string Name => "ItemCabinet";
/// <summary>
/// Sound to be played when the cabinet door is opened.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("doorSound")]
public string? DoorSound { get; set; }
/// <summary>
/// The prototype that should be spawned inside the cabinet when it is map initialized.
/// </summary>
[ViewVariables]
[DataField("spawnPrototype")]
public string? SpawnPrototype { get; set; }
/// <summary>
/// A whitelist defining which entities are allowed into the cabinet.
/// </summary>
[ViewVariables]
[DataField("whitelist")]
public EntityWhitelist? Whitelist = null;
[ViewVariables]
public ContainerSlot ItemContainer = default!;
/// <summary>
/// Whether the cabinet is currently open or not.
/// </summary>
[ViewVariables]
[DataField("opened")]
public bool Opened { get; set; } = false;
[Verb]
public sealed class EjectItemFromCabinetVerb : Verb<ItemCabinetComponent>
{
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<ItemCabinetComponent>
{
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);
}
}
}
}

View File

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

View File

@@ -31,10 +31,10 @@ namespace Content.Server.GameObjects.EntitySystems
SubscribeLocalEvent<BuckleComponent, EntRemovedFromContainerMessage>(ContainerModifiedBuckle);
SubscribeLocalEvent<StrapComponent, EntRemovedFromContainerMessage>(ContainerModifiedStrap);
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(HandleAttackHand);
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(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);
}

View File

@@ -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<ItemCabinetComponent, MapInitEvent>(OnMapInitialize);
SubscribeLocalEvent<ItemCabinetComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<ItemCabinetComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<ItemCabinetComponent, ActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<ItemCabinetComponent, TryEjectItemCabinetEvent>(OnTryEjectItemCabinet);
SubscribeLocalEvent<ItemCabinetComponent, TryInsertItemCabinetEvent>(OnTryInsertItemCabinet);
SubscribeLocalEvent<ItemCabinetComponent, ToggleItemCabinetEvent>(OnToggleItemCabinet);
}
private void OnMapInitialize(EntityUid uid, ItemCabinetComponent comp, MapInitEvent args)
{
var owner = EntityManager.GetEntity(uid);
comp.ItemContainer =
owner.EnsureContainer<ContainerSlot>("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);
}
/// <summary>
/// Toggles the ItemCabinet's state.
/// </summary>
private void OnToggleItemCabinet(EntityUid uid, ItemCabinetComponent comp, ToggleItemCabinetEvent args)
{
comp.Opened = !comp.Opened;
ClickLatchSound(comp);
UpdateVisuals(comp);
}
/// <summary>
/// Tries to insert an entity into the ItemCabinet's slot from the user's hands.
/// </summary>
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<HandsComponent>(out var hands) || !hands.Drop(args.Item, comp.ItemContainer))
{
return;
}
UpdateVisuals(comp);
}
/// <summary>
/// Tries to eject the ItemCabinet's item, either into the user's hands or onto the floor.
/// </summary>
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<ItemComponent>(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
{
/// <summary>
/// The user who tried to eject the item.
/// </summary>
public IEntity User;
public TryEjectItemCabinetEvent(IEntity user)
{
User = user;
}
}
public class TryInsertItemCabinetEvent : CancellableEntityEventArgs
{
/// <summary>
/// The user who tried to eject the item.
/// </summary>
public IEntity User;
/// <summary>
/// The item to be inserted.
/// </summary>
public IEntity Item;
public TryInsertItemCabinetEvent(IEntity user, IEntity item)
{
User = user;
Item = item;
}
}
}

View File

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

View File

@@ -48,7 +48,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components
}
/// <summary>
/// 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.
/// </summary>
[PublicAPI]
public class AfterInteractEvent : HandledEntityEventArgs

View File

@@ -15,7 +15,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components
/// <summary>
/// Called when a player directly interacts with an empty hand when user is in range of the target entity.
/// </summary>
[Obsolete("Use AttackHandMessage instead")]
[Obsolete("Use InteractHandEvent instead")]
bool InteractHand(InteractHandEventArgs eventArgs);
}
@@ -32,7 +32,7 @@ namespace Content.Shared.Interfaces.GameObjects.Components
}
/// <summary>
/// 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.
/// </summary>
[PublicAPI]
public class InteractHandEvent : HandledEntityEventArgs

View File

@@ -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
{
/// <summary>
/// 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.
/// </summary>
/// <code>
/// whitelist:
/// tags:
/// - Cigarette
/// - FirelockElectronics
/// components:
/// - Buckle
/// - AsteroidRock
/// </code>
[DataDefinition]
public class EntityWhitelist : ISerializationHooks
{
/// <summary>
/// Component names that are allowed in the whitelist.
/// </summary>
[DataField("components")] public string[]? Components = null;
private List<IComponentRegistration>? _registrations = null;
/// <summary>
/// Tags that are allowed in the whitelist.
/// </summary>
[DataField("tags")] public string[]? Tags = null;
void ISerializationHooks.AfterDeserialization()
{
UpdateRegistrations();
}
public void UpdateRegistrations()
{
if (Components == null) return;
var compfact = IoCManager.Resolve<IComponentFactory>();
_registrations = new List<IComponentRegistration>();
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);
}
}
/// <summary>
/// Returns whether a given entity fits the whitelist.
/// </summary>
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;
}
}
}

View File

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

View File

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

View File

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

View File

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