Secret stash refractor (#29396)
* First commit * Will do this in another PR! * maybe? * Moved stuff to ToolOpenableSystem because its smarter and cooler
This commit is contained in:
@@ -4,6 +4,7 @@ using Content.Server.Popups;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
using Content.Shared.Tools.EntitySystems;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
@@ -25,7 +26,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem) });
|
SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using Content.Shared.Storage.Components;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
|
|
||||||
namespace Content.Shared.Plants
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction wrapper for <see cref="SecretStashComponent"/>.
|
|
||||||
/// Gently rustle after each interaction with plant.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
[Access(typeof(PottedPlantHideSystem))]
|
|
||||||
public sealed partial class PottedPlantHideComponent : Component
|
|
||||||
{
|
|
||||||
[DataField("rustleSound")]
|
|
||||||
public SoundSpecifier RustleSound = new SoundPathSpecifier("/Audio/Effects/plant_rustle.ogg");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using Content.Shared.Popups;
|
|
||||||
using Content.Shared.Storage.Components;
|
|
||||||
using Content.Shared.Storage.EntitySystems;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
|
|
||||||
namespace Content.Shared.Plants
|
|
||||||
{
|
|
||||||
public sealed class PottedPlantHideSystem : EntitySystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
|
||||||
[Dependency] private readonly SecretStashSystem _stashSystem = default!;
|
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<PottedPlantHideComponent, ComponentInit>(OnInit);
|
|
||||||
SubscribeLocalEvent<PottedPlantHideComponent, InteractUsingEvent>(OnInteractUsing);
|
|
||||||
SubscribeLocalEvent<PottedPlantHideComponent, InteractHandEvent>(OnInteractHand);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInit(EntityUid uid, PottedPlantHideComponent component, ComponentInit args)
|
|
||||||
{
|
|
||||||
EntityManager.EnsureComponent<SecretStashComponent>(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInteractUsing(EntityUid uid, PottedPlantHideComponent component, InteractUsingEvent args)
|
|
||||||
{
|
|
||||||
if (args.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Rustle(uid, component, args.User);
|
|
||||||
args.Handled = _stashSystem.TryHideItem(uid, args.User, args.Used);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInteractHand(EntityUid uid, PottedPlantHideComponent component, InteractHandEvent args)
|
|
||||||
{
|
|
||||||
if (args.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Rustle(uid, component, args.User);
|
|
||||||
|
|
||||||
var gotItem = _stashSystem.TryGetItem(uid, args.User);
|
|
||||||
if (!gotItem)
|
|
||||||
{
|
|
||||||
var msg = Loc.GetString("potted-plant-hide-component-interact-hand-got-no-item-message");
|
|
||||||
_popupSystem.PopupClient(msg, uid, args.User);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Handled = gotItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Rustle(EntityUid uid, PottedPlantHideComponent? component = null, EntityUid? user = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref component))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_audio.PlayPredicted(component.RustleSound, uid, user, AudioParams.Default.WithVariation(0.25f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,84 +7,56 @@ using Content.Shared.Tools;
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
namespace Content.Shared.Storage.Components
|
namespace Content.Shared.Storage.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logic for a secret slot stash, like plant pot or toilet cistern.
|
/// Logic for a secret slot stash, like plant pot or toilet cistern.
|
||||||
/// Unlike <see cref="ItemSlotsComponent"/> it doesn't have interaction logic or verbs.
|
/// Unlike <see cref="ItemSlotsComponent"/> it has logic for opening and closing
|
||||||
/// Other classes like <see cref="ToiletComponent"/> should implement it.
|
/// the stash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
[Access(typeof(SecretStashSystem))]
|
[Access(typeof(SecretStashSystem))]
|
||||||
public sealed partial class SecretStashComponent : Component
|
public sealed partial class SecretStashComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Max item size that can be fitted into secret stash.
|
/// Max item size that can be inserted into secret stash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("maxItemSize")]
|
[DataField("maxItemSize")]
|
||||||
public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
|
public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If stash has way to open then this will switch between open and closed.
|
/// This sound will be played when you try to insert an item in the stash.
|
||||||
/// </summary>
|
/// The sound will be played whether or not the item is actually inserted.
|
||||||
[DataField, AutoNetworkedField]
|
|
||||||
public bool ToggleOpen;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prying the door.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float PryDoorTime = 1f;
|
public SoundSpecifier? TryInsertItemSound;
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public ProtoId<ToolQualityPrototype> PryingQuality = "Prying";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is stash openable?.
|
/// This sound will be played when you try to remove an item in the stash.
|
||||||
/// </summary>
|
/// The sound will be played whether or not the item is actually removed.
|
||||||
[DataField, AutoNetworkedField]
|
|
||||||
public bool OpenableStash = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// IC secret stash name. For example "the toilet cistern".
|
|
||||||
/// If empty string, will replace it with entity name in init.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public string SecretPartName { get; set; } = "";
|
public SoundSpecifier? TryRemoveItemSound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, verbs will appear to help interact with the stash.
|
||||||
|
/// </summary>
|
||||||
[DataField, AutoNetworkedField]
|
[DataField, AutoNetworkedField]
|
||||||
public string ExamineStash = "comp-secret-stash-on-examine-found-hidden-item";
|
public bool HasVerbs = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the secret stash. For example "the toilet cistern".
|
||||||
|
/// If null, the name of the entity will be used instead.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public string? SecretStashName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Container used to keep secret stash item.
|
/// Container used to keep secret stash item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public ContainerSlot ItemContainer = default!;
|
public ContainerSlot ItemContainer = default!;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simple pry event for prying open a stash door.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public sealed partial class StashPryDoAfterEvent : SimpleDoAfterEvent
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Visualizers for handling stash open closed state if stash has door.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum StashVisuals : byte
|
|
||||||
{
|
|
||||||
DoorVisualState,
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum DoorVisualState : byte
|
|
||||||
{
|
|
||||||
DoorOpen,
|
|
||||||
DoorClosed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,215 +8,206 @@ using Robust.Shared.Containers;
|
|||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Content.Shared.Tools.Systems;
|
using Content.Shared.Tools.Systems;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Tools.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Shared.Storage.EntitySystems
|
namespace Content.Shared.Storage.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Secret Stash allows an item to be hidden within.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SecretStashSystem : EntitySystem
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Secret Stash allows an item to be hidden within.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class SecretStashSystem : EntitySystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||||
[Dependency] private readonly SharedToolSystem _tool = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly ToolOpenableSystem _toolOpenableSystem = default!;
|
||||||
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
|
SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
|
||||||
SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
|
SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
|
||||||
SubscribeLocalEvent<SecretStashComponent, StashPryDoAfterEvent>(OnSecretStashPried);
|
SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem) });
|
||||||
SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing);
|
|
||||||
SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
|
SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
|
||||||
SubscribeLocalEvent<SecretStashComponent, ExaminedEvent>(OnExamine);
|
SubscribeLocalEvent<SecretStashComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInit(EntityUid uid, SecretStashComponent component, ComponentInit args)
|
private void OnInit(Entity<SecretStashComponent> entity, ref ComponentInit args)
|
||||||
{
|
{
|
||||||
component.ItemContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, "stash", out _);
|
entity.Comp.ItemContainer = _containerSystem.EnsureContainer<ContainerSlot>(entity, "stash", out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroyed(EntityUid uid, SecretStashComponent component, DestructionEventArgs args)
|
private void OnDestroyed(Entity<SecretStashComponent> entity, ref DestructionEventArgs args)
|
||||||
{
|
{
|
||||||
_containerSystem.EmptyContainer(component.ItemContainer);
|
var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer);
|
||||||
|
if (storedInside != null && storedInside.Count >= 1)
|
||||||
|
{
|
||||||
|
var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity)));
|
||||||
|
_popupSystem.PopupEntity(popup, storedInside[0], PopupType.MediumCaution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractUsing(Entity<SecretStashComponent> entity, ref InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || !IsStashOpen(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = TryStashItem(entity, args.User, args.Used);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractHand(Entity<SecretStashComponent> entity, ref InteractHandEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled || !IsStashOpen(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Handled = TryGetItem(entity, args.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is there something inside secret stash item container?
|
/// Tries to hide the given item into the stash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasItemInside(EntityUid uid, SecretStashComponent? component = null)
|
/// <returns>True if item was hidden inside stash and false otherwise.</returns>
|
||||||
|
private bool TryStashItem(Entity<SecretStashComponent> entity, EntityUid userUid, EntityUid itemToHideUid)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!TryComp<ItemComponent>(itemToHideUid, out var itemComp))
|
||||||
return false;
|
return false;
|
||||||
return component.ItemContainer.ContainedEntity != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInteractUsing(EntityUid uid, SecretStashComponent component, InteractUsingEvent args)
|
_audio.PlayPredicted(entity.Comp.TryInsertItemSound, entity, userUid, AudioParams.Default.WithVariation(0.25f));
|
||||||
{
|
|
||||||
if (args.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!component.OpenableStash)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// is player trying place or lift off cistern lid?
|
|
||||||
if (_tool.UseTool(args.Used, args.User, uid, component.PryDoorTime, component.PryingQuality, new StashPryDoAfterEvent()))
|
|
||||||
args.Handled = true;
|
|
||||||
// maybe player is trying to hide something inside cistern?
|
|
||||||
else if (component.ToggleOpen)
|
|
||||||
{
|
|
||||||
TryHideItem(uid, args.User, args.Used);
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInteractHand(EntityUid uid, SecretStashComponent component, InteractHandEvent args)
|
|
||||||
{
|
|
||||||
if (args.Handled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!component.OpenableStash)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// trying to get something from stash?
|
|
||||||
if (component.ToggleOpen)
|
|
||||||
{
|
|
||||||
var gotItem = TryGetItem(uid, args.User);
|
|
||||||
if (gotItem)
|
|
||||||
{
|
|
||||||
args.Handled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSecretStashPried(EntityUid uid, SecretStashComponent component, StashPryDoAfterEvent args)
|
|
||||||
{
|
|
||||||
if (args.Cancelled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ToggleOpen(uid, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleOpen(EntityUid uid, SecretStashComponent? component = null, MetaDataComponent? meta = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref component))
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.ToggleOpen = !component.ToggleOpen;
|
|
||||||
|
|
||||||
UpdateAppearance(uid, component);
|
|
||||||
Dirty(uid, component, meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateAppearance(EntityUid uid, SecretStashComponent? component = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref component))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_appearance.SetData(uid, StashVisuals.DoorVisualState, component.ToggleOpen ? DoorVisualState.DoorOpen : DoorVisualState.DoorClosed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to hide item inside secret stash from hands of user.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if item was hidden inside stash</returns>
|
|
||||||
public bool TryHideItem(EntityUid uid, EntityUid userUid, EntityUid itemToHideUid,
|
|
||||||
SecretStashComponent? component = null, ItemComponent? item = null,
|
|
||||||
HandsComponent? hands = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref component))
|
|
||||||
return false;
|
|
||||||
if (!Resolve(itemToHideUid, ref item))
|
|
||||||
return false;
|
|
||||||
if (!Resolve(userUid, ref hands))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// check if secret stash is already occupied
|
// check if secret stash is already occupied
|
||||||
var container = component.ItemContainer;
|
var container = entity.Comp.ItemContainer;
|
||||||
if (container.ContainedEntity != null)
|
if (HasItemInside(entity))
|
||||||
{
|
{
|
||||||
var msg = Loc.GetString("comp-secret-stash-action-hide-container-not-empty");
|
var popup = Loc.GetString("comp-secret-stash-action-hide-container-not-empty");
|
||||||
_popupSystem.PopupClient(msg, uid, userUid);
|
_popupSystem.PopupClient(popup, entity, userUid);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if item is too big to fit into secret stash
|
// check if item is too big to fit into secret stash
|
||||||
if (_item.GetSizePrototype(item.Size) > _item.GetSizePrototype(component.MaxItemSize))
|
if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize))
|
||||||
{
|
{
|
||||||
var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
|
var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
|
||||||
("item", itemToHideUid), ("stash", GetSecretPartName(uid, component)));
|
("item", itemToHideUid), ("stashname", GetStashName(entity)));
|
||||||
_popupSystem.PopupClient(msg, uid, userUid);
|
_popupSystem.PopupClient(msg, entity, userUid);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to move item from hands to stash container
|
// try to move item from hands to stash container
|
||||||
if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container))
|
if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// all done, show success message
|
// all done, show success message
|
||||||
var successMsg = Loc.GetString("comp-secret-stash-action-hide-success",
|
var successMsg = Loc.GetString("comp-secret-stash-action-hide-success",
|
||||||
("item", itemToHideUid), ("this", GetSecretPartName(uid, component)));
|
("item", itemToHideUid), ("stashname", GetStashName(entity)));
|
||||||
_popupSystem.PopupClient(successMsg, uid, userUid);
|
_popupSystem.PopupClient(successMsg, entity, userUid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try get item and place it in users hand.
|
/// Try the given item in the stash and place it in users hand.
|
||||||
/// If user can't take it by hands, will drop item from container.
|
/// If user can't take hold the item in their hands, the item will be dropped onto the ground.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if user received item</returns>
|
/// <returns>True if user received item.</returns>
|
||||||
public bool TryGetItem(EntityUid uid, EntityUid userUid, SecretStashComponent? component = null,
|
private bool TryGetItem(Entity<SecretStashComponent> entity, EntityUid userUid)
|
||||||
HandsComponent? hands = null)
|
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!TryComp<HandsComponent>(userUid, out var handsComp))
|
||||||
return false;
|
|
||||||
if (!Resolve(userUid, ref hands))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
_audio.PlayPredicted(entity.Comp.TryRemoveItemSound, entity, userUid, AudioParams.Default.WithVariation(0.25f));
|
||||||
|
|
||||||
// check if secret stash has something inside
|
// check if secret stash has something inside
|
||||||
var container = component.ItemContainer;
|
var itemInStash = entity.Comp.ItemContainer.ContainedEntity;
|
||||||
if (container.ContainedEntity == null)
|
if (itemInStash == null)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
_handsSystem.PickupOrDrop(userUid, container.ContainedEntity.Value, handsComp: hands);
|
_handsSystem.PickupOrDrop(userUid, itemInStash.Value, handsComp: handsComp);
|
||||||
|
|
||||||
// show success message
|
// show success message
|
||||||
var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
|
var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
|
||||||
("stash", GetSecretPartName(uid, component)));
|
("stashname", GetStashName(entity)));
|
||||||
_popupSystem.PopupClient(successMsg, uid, userUid);
|
_popupSystem.PopupClient(successMsg, entity, userUid);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnExamine(EntityUid uid, SecretStashComponent component, ExaminedEvent args)
|
private void OnGetVerb(Entity<SecretStashComponent> entity, ref GetVerbsEvent<InteractionVerb> args)
|
||||||
{
|
{
|
||||||
if (args.IsInDetailsRange && component.ToggleOpen)
|
if (!args.CanInteract || !args.CanAccess || !entity.Comp.HasVerbs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = args.User;
|
||||||
|
var item = args.Using;
|
||||||
|
var stashName = GetStashName(entity);
|
||||||
|
|
||||||
|
var itemVerb = new InteractionVerb();
|
||||||
|
|
||||||
|
// This will add the verb relating to inserting / grabbing items.
|
||||||
|
if (IsStashOpen(entity))
|
||||||
{
|
{
|
||||||
if (HasItemInside(uid))
|
if (item != null)
|
||||||
{
|
{
|
||||||
var msg = Loc.GetString(component.ExamineStash);
|
itemVerb.Text = Loc.GetString("comp-secret-stash-verb-insert-into-stash");
|
||||||
args.PushMarkup(msg);
|
if (HasItemInside(entity))
|
||||||
|
{
|
||||||
|
itemVerb.Disabled = true;
|
||||||
|
itemVerb.Message = Loc.GetString("comp-secret-stash-verb-insert-message-item-already-inside", ("stashname", stashName));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemVerb.Message = Loc.GetString("comp-secret-stash-verb-insert-message-no-item", ("item", item), ("stashname", stashName));
|
||||||
|
}
|
||||||
|
|
||||||
|
itemVerb.Act = () => TryStashItem(entity, user, item.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemVerb.Text = Loc.GetString("comp-secret-stash-verb-take-out-item");
|
||||||
|
itemVerb.Message = Loc.GetString("comp-secret-stash-verb-take-out-message-something", ("stashname", stashName));
|
||||||
|
if (!HasItemInside(entity))
|
||||||
|
{
|
||||||
|
itemVerb.Disabled = true;
|
||||||
|
itemVerb.Message = Loc.GetString("comp-secret-stash-verb-take-out-message-nothing", ("stashname", stashName));
|
||||||
|
}
|
||||||
|
|
||||||
|
itemVerb.Act = () => TryGetItem(entity, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Verbs.Add(itemVerb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSecretPartName(EntityUid uid, SecretStashComponent stash)
|
#region Helper functions
|
||||||
|
|
||||||
|
/// <returns>
|
||||||
|
/// The stash name if it exists, or the entity name if it doesn't.
|
||||||
|
/// </returns>
|
||||||
|
private string GetStashName(Entity<SecretStashComponent> entity)
|
||||||
{
|
{
|
||||||
if (stash.SecretPartName != "")
|
if (entity.Comp.SecretStashName == null)
|
||||||
return Loc.GetString(stash.SecretPartName);
|
return Identity.Name(entity, EntityManager);
|
||||||
|
return Loc.GetString(entity.Comp.SecretStashName);
|
||||||
var entityName = Loc.GetString("comp-secret-stash-secret-part-name", ("this", uid));
|
|
||||||
|
|
||||||
return entityName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <returns>
|
||||||
|
/// True if the stash is open OR the there is no toolOpenableComponent attacheded to the entity
|
||||||
|
/// and false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
private bool IsStashOpen(Entity<SecretStashComponent> stash)
|
||||||
|
{
|
||||||
|
return _toolOpenableSystem.IsOpen(stash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasItemInside(Entity<SecretStashComponent> entity)
|
||||||
|
{
|
||||||
|
return entity.Comp.ItemContainer.ContainedEntity != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
84
Content.Shared/Tools/Components/ToolOpenableComponent.cs
Normal file
84
Content.Shared/Tools/Components/ToolOpenableComponent.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Content.Shared.DoAfter;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Tools.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logic for using tools (Or verbs) to open / close something on an entity.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class ToolOpenableComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Is the openable part open or closed?
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool IsOpen = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a tool is needed to open the entity, this time will be used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float OpenTime = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a tool is needed to close the entity, this time will be used.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float CloseTime = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What type of tool quality is needed to open this?
|
||||||
|
/// If null, the it will only be openable by a verb.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<ToolQualityPrototype>? OpenToolQualityNeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What type of tool quality is needed to close this.
|
||||||
|
/// If null, this will only be closable by a verb.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<ToolQualityPrototype>? CloseToolQualityNeeded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, verbs will appear to help interact with opening / closing.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool HasVerbs = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of what is being open and closed.
|
||||||
|
/// E.g toilet lid, pannel, compartment.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public string? Name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simple do after event for opening or closing.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed partial class ToolOpenableDoAfterEventToggleOpen : SimpleDoAfterEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visualizers for handling stash open closed state if stash has door.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum ToolOpenableVisuals : byte
|
||||||
|
{
|
||||||
|
ToolOpenableVisualState,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum ToolOpenableVisualState : byte
|
||||||
|
{
|
||||||
|
Open,
|
||||||
|
Closed
|
||||||
|
}
|
||||||
|
}
|
||||||
171
Content.Shared/Tools/Systems/ToolOpenableSystem.cs
Normal file
171
Content.Shared/Tools/Systems/ToolOpenableSystem.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using Content.Shared.IdentityManagement;
|
||||||
|
using Content.Shared.Tools.Components;
|
||||||
|
using Content.Shared.Tools.Systems;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Examine;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
|
||||||
|
namespace Content.Shared.Tools.EntitySystems;
|
||||||
|
|
||||||
|
public sealed class ToolOpenableSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedToolSystem _tool = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<ToolOpenableComponent, ComponentInit>(OnInit);
|
||||||
|
SubscribeLocalEvent<ToolOpenableComponent, ToolOpenableDoAfterEventToggleOpen>(OnOpenableStateToggled);
|
||||||
|
SubscribeLocalEvent<ToolOpenableComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
|
SubscribeLocalEvent<ToolOpenableComponent, ExaminedEvent>(OnExamine);
|
||||||
|
SubscribeLocalEvent<ToolOpenableComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInit(Entity<ToolOpenableComponent> entity, ref ComponentInit args)
|
||||||
|
{
|
||||||
|
UpdateAppearance(entity);
|
||||||
|
Dirty(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractUsing(Entity<ToolOpenableComponent> entity, ref InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (args.Handled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TryOpenClose(entity, args.Used, args.User))
|
||||||
|
args.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to open or close what is openable.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> Returns false if you can't interact with the openable thing with the given item. </returns>
|
||||||
|
private bool TryOpenClose(Entity<ToolOpenableComponent> entity, EntityUid? toolToToggle, EntityUid user)
|
||||||
|
{
|
||||||
|
var neededToolQuantity = entity.Comp.IsOpen ? entity.Comp.CloseToolQualityNeeded : entity.Comp.OpenToolQualityNeeded;
|
||||||
|
var time = entity.Comp.IsOpen ? entity.Comp.CloseTime : entity.Comp.OpenTime;
|
||||||
|
var evt = new ToolOpenableDoAfterEventToggleOpen();
|
||||||
|
|
||||||
|
// If neededToolQuantity is null it can only be open be opened with the verbs.
|
||||||
|
if (toolToToggle == null || neededToolQuantity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _tool.UseTool(toolToToggle.Value, user, entity, time, neededToolQuantity, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOpenableStateToggled(Entity<ToolOpenableComponent> entity, ref ToolOpenableDoAfterEventToggleOpen args)
|
||||||
|
{
|
||||||
|
if (args.Cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ToggleState(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggle the state and update appearance.
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleState(Entity<ToolOpenableComponent> entity)
|
||||||
|
{
|
||||||
|
entity.Comp.IsOpen = !entity.Comp.IsOpen;
|
||||||
|
UpdateAppearance(entity);
|
||||||
|
Dirty(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Helper functions
|
||||||
|
|
||||||
|
private string GetName(Entity<ToolOpenableComponent> entity)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Name == null)
|
||||||
|
return Identity.Name(entity, EntityManager);
|
||||||
|
return Loc.GetString(entity.Comp.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOpen(EntityUid uid, ToolOpenableComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component, false))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return component.IsOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance(Entity<ToolOpenableComponent> entity)
|
||||||
|
{
|
||||||
|
_appearance.SetData(entity, ToolOpenableVisuals.ToolOpenableVisualState, entity.Comp.IsOpen ? ToolOpenableVisualState.Open : ToolOpenableVisualState.Closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region User interface functions
|
||||||
|
|
||||||
|
private void OnExamine(Entity<ToolOpenableComponent> entity, ref ExaminedEvent args)
|
||||||
|
{
|
||||||
|
if (!args.IsInDetailsRange)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string msg;
|
||||||
|
var name = GetName(entity);
|
||||||
|
if (entity.Comp.IsOpen)
|
||||||
|
msg = Loc.GetString("tool-openable-component-examine-opened", ("name", name));
|
||||||
|
else
|
||||||
|
msg = Loc.GetString("tool-openable-component-examine-closed", ("name", name));
|
||||||
|
|
||||||
|
args.PushMarkup(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetVerb(Entity<ToolOpenableComponent> entity, ref GetVerbsEvent<InteractionVerb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanInteract || !args.CanAccess || !entity.Comp.HasVerbs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var user = args.User;
|
||||||
|
var item = args.Using;
|
||||||
|
var name = GetName(entity);
|
||||||
|
|
||||||
|
var toggleVerb = new InteractionVerb
|
||||||
|
{
|
||||||
|
IconEntity = GetNetEntity(item)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entity.Comp.IsOpen)
|
||||||
|
{
|
||||||
|
toggleVerb.Text = toggleVerb.Message = Loc.GetString("tool-openable-component-verb-close");
|
||||||
|
var neededQual = entity.Comp.CloseToolQualityNeeded;
|
||||||
|
|
||||||
|
// If neededQual is null you don't need a tool to open / close.
|
||||||
|
if (neededQual != null &&
|
||||||
|
(item == null || !_tool.HasQuality(item.Value, neededQual)))
|
||||||
|
{
|
||||||
|
toggleVerb.Disabled = true;
|
||||||
|
toggleVerb.Message = Loc.GetString("tool-openable-component-verb-cant-close", ("name", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neededQual == null)
|
||||||
|
toggleVerb.Act = () => ToggleState(entity);
|
||||||
|
else
|
||||||
|
toggleVerb.Act = () => TryOpenClose(entity, item, user);
|
||||||
|
|
||||||
|
args.Verbs.Add(toggleVerb);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The open verb should only appear when holding the correct tool or if no tool is needed.
|
||||||
|
|
||||||
|
toggleVerb.Text = toggleVerb.Message = Loc.GetString("tool-openable-component-verb-open");
|
||||||
|
var neededQual = entity.Comp.OpenToolQualityNeeded;
|
||||||
|
|
||||||
|
if (neededQual == null)
|
||||||
|
{
|
||||||
|
toggleVerb.Act = () => ToggleState(entity);
|
||||||
|
args.Verbs.Add(toggleVerb);
|
||||||
|
}
|
||||||
|
else if (item != null && _tool.HasQuality(item.Value, neededQual))
|
||||||
|
{
|
||||||
|
toggleVerb.Act = () => TryOpenClose(entity, item, user);
|
||||||
|
args.Verbs.Add(toggleVerb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
### Secret stash component. Stuff like potted plants, comfy chair cushions, etc...
|
### Secret stash component. Stuff like potted plants, comfy chair cushions, etc...
|
||||||
|
|
||||||
comp-secret-stash-secret-part-name = { THE($item) }
|
comp-secret-stash-action-hide-success = You hide { THE($item) } in the {$stashname}.
|
||||||
comp-secret-stash-action-hide-success = You hide { THE($item) } in { $this }
|
comp-secret-stash-action-hide-container-not-empty = There's already something in here!?
|
||||||
comp-secret-stash-action-hide-container-not-empty = There's already something in here?!
|
comp-secret-stash-action-hide-item-too-big = { THE($item) } is too big to fit in the {$stashname}.
|
||||||
comp-secret-stash-action-hide-item-too-big = { THE($item) } is too big to fit in {$stash}!
|
comp-secret-stash-action-get-item-found-something = There was something inside the {$stashname}!
|
||||||
comp-secret-stash-action-get-item-found-something = There was something inside {$stash}!
|
comp-secret-stash-on-examine-found-hidden-item = There is something hidden inside the {$stashname}!
|
||||||
comp-secret-stash-on-examine-found-hidden-item = There is something hidden inside.
|
comp-secret-stash-on-destroyed-popup = Something falls out of the the {$stashname}!
|
||||||
|
|
||||||
secret-stash-part-plant = the plant
|
### Verbs
|
||||||
secret-stash-part-toilet = the toilet cistern
|
comp-secret-stash-verb-insert-into-stash = Stash item
|
||||||
|
comp-secret-stash-verb-insert-message-item-already-inside = There is already an item inside the {$stashname}.
|
||||||
|
comp-secret-stash-verb-insert-message-no-item = Hide { THE($item) } in the {$stashname}.
|
||||||
|
comp-secret-stash-verb-take-out-item = Grab item
|
||||||
|
comp-secret-stash-verb-take-out-message-something = Take the contents of the {$stashname} out.
|
||||||
|
comp-secret-stash-verb-take-out-message-nothing = There is nothing inside the {$stashname}.
|
||||||
|
|
||||||
|
comp-secret-stash-verb-close = Close
|
||||||
|
comp-secret-stash-verb-cant-close = You can't close the {$stashname} with that.
|
||||||
|
comp-secret-stash-verb-open = Open
|
||||||
|
|
||||||
|
### Stash names
|
||||||
|
secret-stash-plant = plant
|
||||||
|
secret-stash-toilet = toilet cistern
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
tool-openable-component-examine-closed = The {$name} is closed.
|
||||||
|
tool-openable-component-examine-opened = The {$name} is open.
|
||||||
|
|
||||||
|
tool-openable-component-verb-close = Close
|
||||||
|
tool-openable-component-verb-open = Open
|
||||||
|
tool-openable-component-verb-cant-close = You can't close the {$name} with that.
|
||||||
@@ -24,9 +24,11 @@
|
|||||||
offset: "0.0,0.3"
|
offset: "0.0,0.3"
|
||||||
sprite: Structures/Furniture/potted_plants.rsi
|
sprite: Structures/Furniture/potted_plants.rsi
|
||||||
noRot: true
|
noRot: true
|
||||||
- type: PottedPlantHide
|
|
||||||
- type: SecretStash
|
- type: SecretStash
|
||||||
secretPartName: secret-stash-part-plant
|
tryInsertItemSound: /Audio/Effects/plant_rustle.ogg
|
||||||
|
tryRemoveItemSound: /Audio/Effects/plant_rustle.ogg
|
||||||
|
hasVerbs: false
|
||||||
|
secretStashName: secret-stash-plant
|
||||||
- type: ContainerContainer
|
- type: ContainerContainer
|
||||||
containers:
|
containers:
|
||||||
stash: !type:ContainerSlot {}
|
stash: !type:ContainerSlot {}
|
||||||
|
|||||||
@@ -68,9 +68,11 @@
|
|||||||
- type: PlungerUse
|
- type: PlungerUse
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
- type: SecretStash
|
- type: SecretStash
|
||||||
secretPartName: secret-stash-part-toilet
|
secretStashName: secret-stash-toilet
|
||||||
examineStash: toilet-component-on-examine-found-hidden-item
|
- type: ToolOpenable
|
||||||
openableStash: true
|
openToolQualityNeeded: Prying
|
||||||
|
closeToolQualityNeeded: Prying
|
||||||
|
name: secret-stash-toilet
|
||||||
- type: Drain
|
- type: Drain
|
||||||
autoDrain: false
|
autoDrain: false
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
@@ -103,10 +105,10 @@
|
|||||||
SeatVisualState.SeatUp:
|
SeatVisualState.SeatUp:
|
||||||
SeatUp: { state: disposal-up }
|
SeatUp: { state: disposal-up }
|
||||||
SeatDown: { state: disposal-down }
|
SeatDown: { state: disposal-down }
|
||||||
enum.StashVisuals.DoorVisualState:
|
enum.ToolOpenableVisuals.ToolOpenableVisualState:
|
||||||
DoorVisualState.DoorOpen:
|
ToolOpenableVisualState.StashOpen:
|
||||||
DoorOpen: { state: disposal-open }
|
Open: { state: disposal-open }
|
||||||
DoorClosed: { state: disposal-closed }
|
Closed: { state: disposal-closed }
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: ToiletDirtyWater
|
id: ToiletDirtyWater
|
||||||
|
|||||||
Reference in New Issue
Block a user