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:
beck-thompson
2024-08-08 16:51:58 -07:00
committed by GitHub
parent e4ff5780d5
commit ad18c6e9a5
11 changed files with 513 additions and 351 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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> [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
/// Secret Stash allows an item to be hidden within. [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
/// </summary> [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public sealed class SecretStashSystem : EntitySystem [Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ToolOpenableSystem _toolOpenableSystem = default!;
public override void Initialize()
{ {
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; base.Initialize();
[Dependency] private readonly SharedHandsSystem _handsSystem = default!; SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit);
[Dependency] private readonly SharedContainerSystem _containerSystem = default!; SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
[Dependency] private readonly SharedItemSystem _item = default!; SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem) });
[Dependency] private readonly SharedToolSystem _tool = default!; SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; SubscribeLocalEvent<SecretStashComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
}
public override void Initialize() private void OnInit(Entity<SecretStashComponent> entity, ref ComponentInit args)
{
entity.Comp.ItemContainer = _containerSystem.EnsureContainer<ContainerSlot>(entity, "stash", out _);
}
private void OnDestroyed(Entity<SecretStashComponent> entity, ref DestructionEventArgs args)
{
var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer);
if (storedInside != null && storedInside.Count >= 1)
{ {
base.Initialize(); var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity)));
SubscribeLocalEvent<SecretStashComponent, ComponentInit>(OnInit); _popupSystem.PopupEntity(popup, storedInside[0], PopupType.MediumCaution);
SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
SubscribeLocalEvent<SecretStashComponent, StashPryDoAfterEvent>(OnSecretStashPried);
SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SecretStashComponent, ExaminedEvent>(OnExamine);
}
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);
}
/// <summary>
/// Is there something inside secret stash item container?
/// </summary>
public bool HasItemInside(EntityUid uid, SecretStashComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
return component.ItemContainer.ContainedEntity != null;
}
private void OnInteractUsing(EntityUid uid, SecretStashComponent component, InteractUsingEvent args)
{
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
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;
}
// 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;
}
// try to move item from hands to stash container
if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container))
{
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;
}
/// <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;
}
_handsSystem.PickupOrDrop(userUid, container.ContainedEntity.Value, handsComp: hands);
// show success message
var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
("stash", GetSecretPartName(uid, component)));
_popupSystem.PopupClient(successMsg, uid, userUid);
return true;
}
private void OnExamine(EntityUid uid, SecretStashComponent component, ExaminedEvent args)
{
if (args.IsInDetailsRange && component.ToggleOpen)
{
if (HasItemInside(uid))
{
var msg = Loc.GetString(component.ExamineStash);
args.PushMarkup(msg);
}
}
}
private string GetSecretPartName(EntityUid uid, SecretStashComponent stash)
{
if (stash.SecretPartName != "")
return Loc.GetString(stash.SecretPartName);
var entityName = Loc.GetString("comp-secret-stash-secret-part-name", ("this", uid));
return entityName;
} }
} }
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>
/// 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;
_audio.PlayPredicted(entity.Comp.TryInsertItemSound, entity, userUid, AudioParams.Default.WithVariation(0.25f));
// check if secret stash is already occupied
var container = entity.Comp.ItemContainer;
if (HasItemInside(entity))
{
var popup = Loc.GetString("comp-secret-stash-action-hide-container-not-empty");
_popupSystem.PopupClient(popup, entity, userUid);
return false;
}
// check if item is too big to fit into secret stash
if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize))
{
var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big",
("item", itemToHideUid), ("stashname", GetStashName(entity)));
_popupSystem.PopupClient(msg, entity, userUid);
return false;
}
// try to move item from hands to stash container
if (!_handsSystem.TryDropIntoContainer(userUid, itemToHideUid, container))
return false;
// 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>
/// 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;
_audio.PlayPredicted(entity.Comp.TryRemoveItemSound, entity, userUid, AudioParams.Default.WithVariation(0.25f));
// check if secret stash has something inside
var itemInStash = entity.Comp.ItemContainer.ContainedEntity;
if (itemInStash == null)
return false;
_handsSystem.PickupOrDrop(userUid, itemInStash.Value, handsComp: handsComp);
// show success message
var successMsg = Loc.GetString("comp-secret-stash-action-get-item-found-something",
("stashname", GetStashName(entity)));
_popupSystem.PopupClient(successMsg, entity, userUid);
return true;
}
private void OnGetVerb(Entity<SecretStashComponent> entity, ref GetVerbsEvent<InteractionVerb> args)
{
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 (item != null)
{
itemVerb.Text = Loc.GetString("comp-secret-stash-verb-insert-into-stash");
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);
}
}
#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 (entity.Comp.SecretStashName == null)
return Identity.Name(entity, EntityManager);
return Loc.GetString(entity.Comp.SecretStashName);
}
/// <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
} }

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

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

View File

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

View File

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

View File

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

View File

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