diff --git a/Content.Client/GameObjects/Components/Storage/StorageVisualizer2D.cs b/Content.Client/GameObjects/Components/Storage/StorageVisualizer2D.cs index b64b5b974e..e9cfe3e356 100644 --- a/Content.Client/GameObjects/Components/Storage/StorageVisualizer2D.cs +++ b/Content.Client/GameObjects/Components/Storage/StorageVisualizer2D.cs @@ -59,11 +59,32 @@ namespace Content.Client.GameObjects.Components.Storage sprite.LayerSetState(StorageVisualLayers.Door, open ? _stateOpen ?? $"{_stateBase}_open" : _stateClosed ?? $"{_stateBase}_door"); + + if (component.TryGetData(StorageVisuals.CanLock, out bool canLock) && canLock) + { + if (!component.TryGetData(StorageVisuals.Locked, out bool locked)) + { + locked = true; + } + + sprite.LayerSetVisible(StorageVisualLayers.Lock, !open); + if (!open) + { + sprite.LayerSetState(StorageVisualLayers.Lock, locked ? "locked" : "unlocked"); + } + } + + if (component.TryGetData(StorageVisuals.Welded, out bool weldedVal)) + { + sprite.LayerSetVisible(StorageVisualLayers.Welded, weldedVal); + } } } public enum StorageVisualLayers { - Door + Door, + Welded, + Lock } } diff --git a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 355be0b0fb..647380b819 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -1,18 +1,22 @@ using System; using System.Linq; +using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Sound; using Content.Server.GameObjects.EntitySystems; -using Content.Server.Utility; using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.Components.Storage; +using Content.Shared.Interfaces; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -22,18 +26,26 @@ namespace Content.Server.GameObjects.Components [RegisterComponent] [ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IStorageComponent))] - public class EntityStorageComponent : Component, IActivate, IStorageComponent + public class EntityStorageComponent : Component, IActivate, IStorageComponent, IInteractUsing { public override string Name => "EntityStorage"; private const float MaxSize = 1.0f; // maximum width or height of an entity allowed inside the storage. + private static readonly TimeSpan InternalOpenAttemptDelay = TimeSpan.FromSeconds(0.5); + private TimeSpan _lastInternalOpenAttempt; + + [ViewVariables] private int StorageCapacityMax; + [ViewVariables] private bool IsCollidableWhenOpen; + [ViewVariables] private Container Contents; + [ViewVariables] private IEntityQuery entityQuery; private bool _showContents; private bool _open; + private bool _isWeldedShut; /// /// Determines if the container contents should be drawn when the container is closed. @@ -49,6 +61,31 @@ namespace Content.Server.GameObjects.Components } } + [ViewVariables(VVAccess.ReadWrite)] + public bool Open + { + get => _open; + private set => _open = value; + } + + [ViewVariables(VVAccess.ReadWrite)] + public bool IsWeldedShut + { + get => _isWeldedShut; + set + { + _isWeldedShut = value; + + if (Owner.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(StorageVisuals.Welded, value); + } + } + } + + [ViewVariables(VVAccess.ReadWrite)] + public bool CanWeldShut { get; set; } + /// public override void Initialize() { @@ -73,29 +110,30 @@ namespace Content.Server.GameObjects.Components serializer.DataField(ref IsCollidableWhenOpen, "IsCollidableWhenOpen", false); serializer.DataField(ref _showContents, "showContents", false); serializer.DataField(ref _open, "open", false); - } - - [ViewVariables(VVAccess.ReadWrite)] - public bool Open - { - get => _open; - private set => _open = value; + serializer.DataField(this, a => a.IsWeldedShut, "IsWeldedShut", false); + serializer.DataField(this, a => a.CanWeldShut, "CanWeldShut", true); } public virtual void Activate(ActivateEventArgs eventArgs) { - ToggleOpen(); + ToggleOpen(eventArgs.User); } - private void ToggleOpen() + protected virtual void ToggleOpen(IEntity user) { + if (IsWeldedShut) + { + Owner.PopupMessage(user, Loc.GetString("It's welded completely shut!")); + return; + } + if (Open) { CloseStorage(); } else { - OpenStorage(); + TryOpenStorage(user); } } @@ -130,6 +168,7 @@ namespace Content.Server.GameObjects.Components { soundComponent.Play("/Audio/machines/closetclose.ogg"); } + _lastInternalOpenAttempt = default; } private void OpenStorage() @@ -238,12 +277,30 @@ namespace Content.Server.GameObjects.Components case RelayMovementEntityMessage msg: if (msg.Entity.HasComponent()) { - OpenStorage(); + var timing = IoCManager.Resolve(); + if (timing.CurTime < + _lastInternalOpenAttempt + InternalOpenAttemptDelay) + { + break; + } + + _lastInternalOpenAttempt = timing.CurTime; + TryOpenStorage(msg.Entity); } break; } } + protected virtual void TryOpenStorage(IEntity user) + { + if (IsWeldedShut) + { + Owner.PopupMessage(user, Loc.GetString("It's welded completely shut!")); + return; + } + OpenStorage(); + } + /// public bool Remove(IEntity entity) { @@ -284,14 +341,43 @@ namespace Content.Server.GameObjects.Components { protected override void GetData(IEntity user, EntityStorageComponent component, VerbData data) { - data.Text = component.Open ? "Close" : "Open"; + component.OpenVerbGetData(user, component, data); + } /// protected override void Activate(IEntity user, EntityStorageComponent component) { - component.ToggleOpen(); + component.ToggleOpen(user); } } + + protected virtual void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) + { + if (IsWeldedShut) + { + data.Visibility = VerbVisibility.Disabled; + var verb = Loc.GetString(component.Open ? "Close" : "Open"); + data.Text = Loc.GetString("{0} (welded shut)", verb); + return; + } + + data.Text = component.Open ? "Close" : "Open"; + } + + public bool InteractUsing(InteractUsingEventArgs eventArgs) + { + if (!CanWeldShut) + return false; + + if (!eventArgs.Using.TryGetComponent(out WelderComponent tool)) + return false; + + if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Welding, 1f)) + return false; + + IsWeldedShut ^= true; + return true; + } } } diff --git a/Content.Server/GameObjects/Components/Items/Storage/SecureEntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/SecureEntityStorageComponent.cs new file mode 100644 index 0000000000..374b85050c --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/Storage/SecureEntityStorageComponent.cs @@ -0,0 +1,157 @@ +using Content.Server.GameObjects.Components.Access; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Storage; +using Content.Shared.Interfaces; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Items.Storage +{ + [RegisterComponent] + [ComponentReference(typeof(EntityStorageComponent))] + [ComponentReference(typeof(IActivate))] + [ComponentReference(typeof(IStorageComponent))] + public class SecureEntityStorageComponent : EntityStorageComponent + { + public override string Name => "SecureEntityStorage"; + private bool _locked; + + [ViewVariables(VVAccess.ReadWrite)] + public bool Locked + { + get => _locked; + set + { + _locked = value; + + if (Owner.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(StorageVisuals.Locked, _locked); + } + } + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _locked, "locked", true); + } + + protected override void Startup() + { + base.Startup(); + + if (Owner.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(StorageVisuals.CanLock, true); + } + } + + public override void Activate(ActivateEventArgs eventArgs) + { + if (Locked) + { + DoToggleLock(eventArgs.User); + return; + } + + base.Activate(eventArgs); + } + + protected override void TryOpenStorage(IEntity user) + { + if (Locked) + { + Owner.PopupMessage(user, "It's locked!"); + return; + } + + base.TryOpenStorage(user); + } + + protected override void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data) + { + if (Locked) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + base.OpenVerbGetData(user, component, data); + } + + private void DoToggleLock(IEntity user) + { + if (Locked) + { + DoUnlock(user); + } + else + { + DoLock(user); + } + } + + private void DoUnlock(IEntity user) + { + if (CheckAccess(user)) return; + + Locked = false; + EntitySystem.Get().Play("/Audio/machines/door_lock_off.ogg", Owner, AudioParams.Default.WithVolume(-5)); + } + + private void DoLock(IEntity user) + { + if (CheckAccess(user)) return; + + Locked = true; + EntitySystem.Get().Play("/Audio/machines/door_lock_on.ogg", Owner, AudioParams.Default.WithVolume(-5)); + } + + private bool CheckAccess(IEntity user) + { + if (Owner.TryGetComponent(out AccessReader reader)) + { + if (!reader.IsAllowed(user)) + { + IoCManager.Resolve() + .PopupMessage(Owner, user, Loc.GetString("Access denied")); + return true; + } + } + + return false; + } + + [Verb] + private sealed class ToggleLockVerb : Verb + { + protected override void GetData(IEntity user, SecureEntityStorageComponent component, VerbData data) + { + if (component.Open) + { + data.Visibility = VerbVisibility.Invisible; + return; + } + + data.Text = component.Locked ? Loc.GetString("Unlock") : Loc.GetString("Lock"); + } + + protected override void Activate(IEntity user, SecureEntityStorageComponent component) + { + component.DoToggleLock(user); + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs b/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs index b992670ab2..6e6b051a86 100644 --- a/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs +++ b/Content.Shared/GameObjects/Components/Storage/SharedStorageComponent.cs @@ -86,6 +86,9 @@ namespace Content.Shared.GameObjects.Components.Storage [Serializable] public enum StorageVisuals { - Open + Open, + CanLock, + Welded, + Locked } } diff --git a/Resources/Audio/machines/door_lock_off.ogg b/Resources/Audio/machines/door_lock_off.ogg new file mode 100644 index 0000000000..1dbd10d197 Binary files /dev/null and b/Resources/Audio/machines/door_lock_off.ogg differ diff --git a/Resources/Audio/machines/door_lock_on.ogg b/Resources/Audio/machines/door_lock_on.ogg new file mode 100644 index 0000000000..8102fbfe4b Binary files /dev/null and b/Resources/Audio/machines/door_lock_on.ogg differ diff --git a/Resources/Prototypes/Entities/Buildings/Storage/Closets/closet_secure.yml b/Resources/Prototypes/Entities/Buildings/Storage/Closets/closet_secure.yml index 26606721c5..84980e5f0d 100644 --- a/Resources/Prototypes/Entities/Buildings/Storage/Closets/closet_secure.yml +++ b/Resources/Prototypes/Entities/Buildings/Storage/Closets/closet_secure.yml @@ -2,4 +2,19 @@ id: LockerSecureBase parent: LockerGeneric abstract: true - # TODO: Access components and such. + components: + - type: AccessReader + - type: SecureEntityStorage + - type: Sprite + netsync: false + sprite: Buildings/closet.rsi + layers: + - state: generic + - state: generic_door + map: ["enum.StorageVisualLayers.Door"] + - state: locked + map: ["enum.StorageVisualLayers.Lock"] + shader: unshaded + - state: welded + visible: false + map: ["enum.StorageVisualLayers.Welded"] diff --git a/Resources/Prototypes/Entities/Buildings/Storage/closet.yml b/Resources/Prototypes/Entities/Buildings/Storage/closet.yml index 58449ceb75..b802ea327f 100644 --- a/Resources/Prototypes/Entities/Buildings/Storage/closet.yml +++ b/Resources/Prototypes/Entities/Buildings/Storage/closet.yml @@ -10,6 +10,9 @@ - state: generic - state: generic_door map: ["enum.StorageVisualLayers.Door"] + - state: welded + visible: false + map: ["enum.StorageVisualLayers.Welded"] - type: Icon sprite: Buildings/closet.rsi state: generic_door diff --git a/Resources/Prototypes/Entities/Buildings/Storage/crate_base.yml b/Resources/Prototypes/Entities/Buildings/Storage/crate_base.yml index bdf48c1229..3707dd2f69 100644 --- a/Resources/Prototypes/Entities/Buildings/Storage/crate_base.yml +++ b/Resources/Prototypes/Entities/Buildings/Storage/crate_base.yml @@ -27,6 +27,7 @@ Anchored: false - type: EntityStorage Capacity: 60 + CanWeldShut: false - type: PlaceableSurface - type: Damageable - type: Destructible