using Content.Shared.Interaction;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Tools.EntitySystems;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
{
base.Initialize();
- SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem) });
+ SubscribeLocalEvent<UtensilComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(ItemSlotsSystem), typeof(ToolOpenableSystem) });
}
/// <summary>
+++ /dev/null
-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");
- }
-}
+++ /dev/null
-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));
- }
- }
-}
using Robust.Shared.GameStates;
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
+using Robust.Shared.Audio;
namespace Content.Shared.Storage.Components
{
/// <summary>
/// Logic for a secret slot stash, like plant pot or toilet cistern.
- /// Unlike <see cref="ItemSlotsComponent"/> it doesn't have interaction logic or verbs.
- /// Other classes like <see cref="ToiletComponent"/> should implement it.
+ /// Unlike <see cref="ItemSlotsComponent"/> it has logic for opening and closing
+ /// the stash.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SecretStashSystem))]
public sealed partial class SecretStashComponent : Component
{
/// <summary>
- /// Max item size that can be fitted into secret stash.
+ /// Max item size that can be inserted into secret stash.
/// </summary>
[DataField("maxItemSize")]
public ProtoId<ItemSizePrototype> MaxItemSize = "Small";
/// <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.
+ /// The sound will be played whether or not the item is actually inserted.
/// </summary>
- [DataField, AutoNetworkedField]
- public bool ToggleOpen;
+ [DataField]
+ public SoundSpecifier? TryInsertItemSound;
/// <summary>
- /// Prying the door.
+ /// This sound will be played when you try to remove an item in the stash.
+ /// The sound will be played whether or not the item is actually removed.
/// </summary>
[DataField]
- public float PryDoorTime = 1f;
-
- [DataField]
- public ProtoId<ToolQualityPrototype> PryingQuality = "Prying";
+ public SoundSpecifier? TryRemoveItemSound;
/// <summary>
- /// Is stash openable?.
+ /// If true, verbs will appear to help interact with the stash.
/// </summary>
[DataField, AutoNetworkedField]
- public bool OpenableStash = false;
+ public bool HasVerbs = true;
/// <summary>
- /// IC secret stash name. For example "the toilet cistern".
- /// If empty string, will replace it with entity name in init.
+ /// 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 SecretPartName { get; set; } = "";
-
- [DataField, AutoNetworkedField]
- public string ExamineStash = "comp-secret-stash-on-examine-found-hidden-item";
+ public string? SecretStashName;
/// <summary>
/// Container used to keep secret stash item.
/// </summary>
[ViewVariables]
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
}
}
using Content.Shared.Interaction;
using Content.Shared.Tools.Systems;
using Content.Shared.Examine;
-
-namespace Content.Shared.Storage.EntitySystems
+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;
+
+/// <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 SharedHandsSystem _handsSystem = default!;
- [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
- [Dependency] private readonly SharedItemSystem _item = default!;
- [Dependency] private readonly SharedToolSystem _tool = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
- SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
- SubscribeLocalEvent<SecretStashComponent, StashPryDoAfterEvent>(OnSecretStashPried);
- SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing);
- SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
- SubscribeLocalEvent<SecretStashComponent, ExaminedEvent>(OnExamine);
- }
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
+ [Dependency] private readonly SharedItemSystem _item = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly ToolOpenableSystem _toolOpenableSystem = default!;
- private void OnInit(EntityUid uid, SecretStashComponent component, ComponentInit args)
- {
- component.ItemContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, "stash", out _);
- }
- private void OnDestroyed(EntityUid uid, SecretStashComponent component, DestructionEventArgs args)
- {
- _containerSystem.EmptyContainer(component.ItemContainer);
- }
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
+ SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem) });
+ SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
+ SubscribeLocalEvent<SecretStashComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
+ }
+
+ private void OnInit(Entity<SecretStashComponent> entity, ref ComponentInit args)
+ {
+ entity.Comp.ItemContainer = _containerSystem.EnsureContainer<ContainerSlot>(entity, "stash", out _);
+ }
- /// <summary>
- /// Is there something inside secret stash item container?
- /// </summary>
- public bool HasItemInside(EntityUid uid, SecretStashComponent? component = null)
+ private void OnDestroyed(Entity<SecretStashComponent> entity, ref DestructionEventArgs args)
+ {
+ var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer);
+ if (storedInside != null && storedInside.Count >= 1)
{
- if (!Resolve(uid, ref component))
- return false;
- return component.ItemContainer.ContainedEntity != null;
+ var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity)));
+ _popupSystem.PopupEntity(popup, storedInside[0], PopupType.MediumCaution);
}
+ }
- private void OnInteractUsing(EntityUid uid, SecretStashComponent component, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
+ private void OnInteractUsing(Entity<SecretStashComponent> entity, ref InteractUsingEvent args)
+ {
+ if (args.Handled || !IsStashOpen(entity))
+ return;
- if (!component.OpenableStash)
- return;
+ args.Handled = TryStashItem(entity, args.User, args.Used);
+ }
- // 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(Entity<SecretStashComponent> entity, ref InteractHandEvent args)
+ {
+ if (args.Handled || !IsStashOpen(entity))
+ return;
- private void OnInteractHand(EntityUid uid, SecretStashComponent component, InteractHandEvent args)
- {
- if (args.Handled)
- return;
+ args.Handled = TryGetItem(entity, args.User);
+ }
- if (!component.OpenableStash)
- return;
+ /// <summary>
+ /// Tries to hide the given item into the stash.
+ /// </summary>
+ /// <returns>True if item was hidden inside stash and false otherwise.</returns>
+ private bool TryStashItem(Entity<SecretStashComponent> entity, EntityUid userUid, EntityUid itemToHideUid)
+ {
+ if (!TryComp<ItemComponent>(itemToHideUid, out var itemComp))
+ return false;
- // trying to get something from stash?
- if (component.ToggleOpen)
- {
- var gotItem = TryGetItem(uid, args.User);
- if (gotItem)
- {
- args.Handled = true;
- return;
- }
- }
- args.Handled = true;
- }
+ _audio.PlayPredicted(entity.Comp.TryInsertItemSound, entity, userUid, AudioParams.Default.WithVariation(0.25f));
- private void OnSecretStashPried(EntityUid uid, SecretStashComponent component, StashPryDoAfterEvent args)
+ // check if secret stash is already occupied
+ var container = entity.Comp.ItemContainer;
+ if (HasItemInside(entity))
{
- if (args.Cancelled)
- return;
-
- ToggleOpen(uid, component);
+ var popup = Loc.GetString("comp-secret-stash-action-hide-container-not-empty");
+ _popupSystem.PopupClient(popup, entity, userUid);
+ return false;
}
- public void ToggleOpen(EntityUid uid, SecretStashComponent? component = null, MetaDataComponent? meta = null)
+ // check if item is too big to fit into secret stash
+ if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize))
{
- if (!Resolve(uid, ref component))
- return;
-
- component.ToggleOpen = !component.ToggleOpen;
-
- UpdateAppearance(uid, component);
- Dirty(uid, component, meta);
+ var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
+ ("item", itemToHideUid), ("stashname", GetStashName(entity)));
+ _popupSystem.PopupClient(msg, entity, userUid);
+ return false;
}
- private void UpdateAppearance(EntityUid uid, SecretStashComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
+ // try to move item from hands to stash container
+ if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container))
+ return false;
- _appearance.SetData(uid, StashVisuals.DoorVisualState, component.ToggleOpen ? DoorVisualState.DoorOpen : DoorVisualState.DoorClosed);
- }
+ // all done, show success message
+ var successMsg = Loc.GetString("comp-secret-stash-action-hide-success",
+ ("item", itemToHideUid), ("stashname", GetStashName(entity)));
+ _popupSystem.PopupClient(successMsg, entity, userUid);
+ return true;
+ }
- /// <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
- var container = component.ItemContainer;
- if (container.ContainedEntity != null)
- {
- var msg = Loc.GetString("comp-secret-stash-action-hide-container-not-empty");
- _popupSystem.PopupClient(msg, uid, userUid);
- return false;
- }
+ /// <summary>
+ /// Try the given item in the stash and place it in users hand.
+ /// If user can't take hold the item in their hands, the item will be dropped onto the ground.
+ /// </summary>
+ /// <returns>True if user received item.</returns>
+ private bool TryGetItem(Entity<SecretStashComponent> entity, EntityUid userUid)
+ {
+ if (!TryComp<HandsComponent>(userUid, out var handsComp))
+ return false;
- // check if item is too big to fit into secret stash
- if (_item.GetSizePrototype(item.Size) > _item.GetSizePrototype(component.MaxItemSize))
- {
- var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
- ("item", itemToHideUid), ("stash", GetSecretPartName(uid, component)));
- _popupSystem.PopupClient(msg, uid, userUid);
- return false;
- }
+ _audio.PlayPredicted(entity.Comp.TryRemoveItemSound, entity, userUid, AudioParams.Default.WithVariation(0.25f));
- // try to move item from hands to stash container
- if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container))
- {
- return false;
- }
+ // check if secret stash has something inside
+ var itemInStash = entity.Comp.ItemContainer.ContainedEntity;
+ if (itemInStash == null)
+ return false;
- // all done, show success message
- var successMsg = Loc.GetString("comp-secret-stash-action-hide-success",
- ("item", itemToHideUid), ("this", GetSecretPartName(uid, component)));
- _popupSystem.PopupClient(successMsg, uid, userUid);
- return true;
- }
+ _handsSystem.PickupOrDrop(userUid, itemInStash.Value, handsComp: handsComp);
- /// <summary>
- /// Try get item and place it in users hand.
- /// If user can't take it by hands, will drop item from container.
- /// </summary>
- /// <returns>True if user received item</returns>
- public bool TryGetItem(EntityUid uid, EntityUid userUid, SecretStashComponent? component = null,
- HandsComponent? hands = null)
- {
- if (!Resolve(uid, ref component))
- return false;
- if (!Resolve(userUid, ref hands))
- return false;
-
- // check if secret stash has something inside
- var container = component.ItemContainer;
- if (container.ContainedEntity == null)
- {
- return false;
- }
+ // show success message
+ var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
+ ("stashname", GetStashName(entity)));
+ _popupSystem.PopupClient(successMsg, entity, userUid);
- _handsSystem.PickupOrDrop(userUid, container.ContainedEntity.Value, handsComp: hands);
+ return true;
+ }
+
+ private void OnGetVerb(Entity<SecretStashComponent> entity, ref GetVerbsEvent<InteractionVerb> args)
+ {
+ if (!args.CanInteract || !args.CanAccess || !entity.Comp.HasVerbs)
+ return;
- // show success message
- var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
- ("stash", GetSecretPartName(uid, component)));
- _popupSystem.PopupClient(successMsg, uid, userUid);
+ var user = args.User;
+ var item = args.Using;
+ var stashName = GetStashName(entity);
- return true;
- }
+ var itemVerb = new InteractionVerb();
- private void OnExamine(EntityUid uid, SecretStashComponent component, ExaminedEvent args)
+ // This will add the verb relating to inserting / grabbing items.
+ if (IsStashOpen(entity))
{
- if (args.IsInDetailsRange && component.ToggleOpen)
+ if (item != null)
{
- if (HasItemInside(uid))
+ itemVerb.Text = Loc.GetString("comp-secret-stash-verb-insert-into-stash");
+ if (HasItemInside(entity))
{
- var msg = Loc.GetString(component.ExamineStash);
- args.PushMarkup(msg);
+ 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)
- {
- if (stash.SecretPartName != "")
- return Loc.GetString(stash.SecretPartName);
+ #region Helper functions
- var entityName = Loc.GetString("comp-secret-stash-secret-part-name", ("this", uid));
+ /// <returns>
+ /// The stash name if it exists, or the entity name if it doesn't.
+ /// </returns>
+ private string GetStashName(Entity<SecretStashComponent> entity)
+ {
+ if (entity.Comp.SecretStashName == null)
+ return Identity.Name(entity, EntityManager);
+ return Loc.GetString(entity.Comp.SecretStashName);
+ }
- 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
}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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
+}
### 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 { $this }
-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 {$stash}!
-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.
-
-secret-stash-part-plant = the plant
-secret-stash-part-toilet = the toilet cistern
+comp-secret-stash-action-hide-success = You hide { THE($item) } in the {$stashname}.
+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-get-item-found-something = There was something inside the {$stashname}!
+comp-secret-stash-on-examine-found-hidden-item = There is something hidden inside the {$stashname}!
+comp-secret-stash-on-destroyed-popup = Something falls out of the the {$stashname}!
+
+### Verbs
+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
--- /dev/null
+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.
offset: "0.0,0.3"
sprite: Structures/Furniture/potted_plants.rsi
noRot: true
- - type: PottedPlantHide
- 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
containers:
stash: !type:ContainerSlot {}
- type: PlungerUse
- type: Appearance
- type: SecretStash
- secretPartName: secret-stash-part-toilet
- examineStash: toilet-component-on-examine-found-hidden-item
- openableStash: true
+ secretStashName: secret-stash-toilet
+ - type: ToolOpenable
+ openToolQualityNeeded: Prying
+ closeToolQualityNeeded: Prying
+ name: secret-stash-toilet
- type: Drain
autoDrain: false
- type: StaticPrice
SeatVisualState.SeatUp:
SeatUp: { state: disposal-up }
SeatDown: { state: disposal-down }
- enum.StashVisuals.DoorVisualState:
- DoorVisualState.DoorOpen:
- DoorOpen: { state: disposal-open }
- DoorClosed: { state: disposal-closed }
+ enum.ToolOpenableVisuals.ToolOpenableVisualState:
+ ToolOpenableVisualState.StashOpen:
+ Open: { state: disposal-open }
+ Closed: { state: disposal-closed }
- type: entity
id: ToiletDirtyWater