diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 1b2b63cc5e..7bbe23c825 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -323,7 +323,8 @@ namespace Content.Client.Entry "Spreader", "GrowingKudzu", "MonkeyAccent", - "ReplacementAccent" + "ReplacementAccent", + "ResistLocker" }; } } diff --git a/Content.Server/Lock/LockSystem.cs b/Content.Server/Lock/LockSystem.cs index 969a70c40f..0ea9399dd5 100644 --- a/Content.Server/Lock/LockSystem.cs +++ b/Content.Server/Lock/LockSystem.cs @@ -98,6 +98,27 @@ namespace Content.Server.Lock return true; } + public void Unlock(EntityUid uid, EntityUid user, LockComponent? lockComp = null) + { + if (!Resolve(uid, ref lockComp)) + return; + + lockComp.Owner.PopupMessage(user, Loc.GetString("lock-comp-do-unlock-success", ("entityName", Name: EntityManager.GetComponent(lockComp.Owner).EntityName))); + lockComp.Locked = false; + + if (lockComp.UnlockSound != null) + { + SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.UnlockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5)); + } + + if (EntityManager.TryGetComponent(lockComp.Owner, out AppearanceComponent? appearanceComp)) + { + appearanceComp.SetData(StorageVisuals.Locked, false); + } + + RaiseLocalEvent(lockComp.Owner, new LockToggledEvent(false)); + } + public bool TryUnlock(EntityUid uid, EntityUid user, LockComponent? lockComp = null) { if (!Resolve(uid, ref lockComp)) @@ -109,21 +130,7 @@ namespace Content.Server.Lock if (!HasUserAccess(uid, user, quiet: false)) return false; - lockComp.Owner.PopupMessage(user, Loc.GetString("lock-comp-do-unlock-success", ("entityName", Name: EntityManager.GetComponent(lockComp.Owner).EntityName))); - lockComp.Locked = false; - - if(lockComp.UnlockSound != null) - { - SoundSystem.Play(Filter.Pvs(lockComp.Owner), lockComp.UnlockSound.GetSound(), lockComp.Owner, AudioParams.Default.WithVolume(-5)); - } - - if (EntityManager.TryGetComponent(lockComp.Owner, out AppearanceComponent? appearanceComp)) - { - appearanceComp.SetData(StorageVisuals.Locked, false); - } - - RaiseLocalEvent(lockComp.Owner, new LockToggledEvent(false)); - + Unlock(uid, user, lockComp); return true; } diff --git a/Content.Server/Resist/ResistLockerComponent.cs b/Content.Server/Resist/ResistLockerComponent.cs new file mode 100644 index 0000000000..4d6e46df3c --- /dev/null +++ b/Content.Server/Resist/ResistLockerComponent.cs @@ -0,0 +1,32 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; +using Robust.Shared.Analyzers; +using System.Threading; + +namespace Content.Server.Resist; + +[RegisterComponent] +[Friend(typeof(ResistLockerSystem))] +public class ResistLockerComponent : Component +{ + public override string Name => "ResistLocker"; + + /// + /// How long will this locker take to kick open, defaults to 2 minutes + /// + [ViewVariables] + [DataField("resistTime")] + public float ResistTime = 120f; + + /// + /// For quick exit if the player attempts to move while already resisting + /// + [ViewVariables] + public bool IsResisting = false; + + /// + /// Cancellation token used to cancel the DoAfter if the container is opened before it's complete + /// + public CancellationTokenSource? CancelToken; +} diff --git a/Content.Server/Resist/ResistLockerSystem.cs b/Content.Server/Resist/ResistLockerSystem.cs new file mode 100644 index 0000000000..b1cfc382ed --- /dev/null +++ b/Content.Server/Resist/ResistLockerSystem.cs @@ -0,0 +1,114 @@ +using Content.Shared.Movement; +using Robust.Shared.GameObjects; +using Content.Server.Storage.Components; +using Content.Server.DoAfter; +using Content.Server.Lock; +using Robust.Shared.IoC; +using Robust.Shared.Player; +using Robust.Shared.Containers; +using Content.Server.Popups; +using Robust.Shared.Localization; + +namespace Content.Server.Resist; + +public class ResistLockerSystem : EntitySystem +{ + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly LockSystem _lockSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRelayMovement); + SubscribeLocalEvent(OnDoAfterComplete); + SubscribeLocalEvent(OnDoAfterCancelled); + SubscribeLocalEvent(OnRemovedFromContainer); + } + + private void OnRelayMovement(EntityUid uid, ResistLockerComponent component, RelayMovementEntityEvent args) + { + if (component.IsResisting) + return; + + if (!TryComp(uid, out EntityStorageComponent? storageComponent)) + return; + + if (TryComp(uid, out var lockComponent) && lockComponent.Locked || storageComponent.IsWeldedShut) + { + AttemptResist(args.Entity, uid, storageComponent, component); + } + } + + private void AttemptResist(EntityUid user, EntityUid target, EntityStorageComponent? storageComponent = null, ResistLockerComponent? resistLockerComponent = null) + { + if (!Resolve(target, ref storageComponent, ref resistLockerComponent)) + return; + + resistLockerComponent.CancelToken = new(); + var doAfterEventArgs = new DoAfterEventArgs(user, resistLockerComponent.ResistTime, resistLockerComponent.CancelToken.Token, target) + { + BreakOnTargetMove = false, + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = false, //No hands 'cause we be kickin' + TargetFinishedEvent = new ResistDoAfterComplete(user, target), + TargetCancelledEvent = new ResistDoAfterCancelled(user) + }; + + resistLockerComponent.IsResisting = true; + _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-start-resisting"), user, Filter.Entities(user)); + _doAfterSystem.DoAfter(doAfterEventArgs); + } + + private void OnDoAfterComplete(EntityUid uid, ResistLockerComponent component, ResistDoAfterComplete ev) + { + component.IsResisting = false; + + if (TryComp(uid, out var storageComponent)) + { + if (storageComponent.IsWeldedShut) + storageComponent.IsWeldedShut = false; + + if (TryComp(ev.Target, out var lockComponent)) + _lockSystem.Unlock(uid, ev.User, lockComponent); + + component.CancelToken = null; + storageComponent.TryOpenStorage(ev.User); + } + } + + private void OnDoAfterCancelled(EntityUid uid, ResistLockerComponent component, ResistDoAfterCancelled ev) + { + component.IsResisting = false; + component.CancelToken = null; + _popupSystem.PopupEntity(Loc.GetString("resist-locker-component-resist-interrupted"), ev.User, Filter.Entities(ev.User)); + } + + private void OnRemovedFromContainer(EntityUid uid, ResistLockerComponent component, EntRemovedFromContainerMessage message) + { + component.CancelToken?.Cancel(); + } + + private class ResistDoAfterComplete : EntityEventArgs + { + public readonly EntityUid User; + public readonly EntityUid Target; + public ResistDoAfterComplete(EntityUid userUid, EntityUid target) + { + User = userUid; + Target = target; + } + } + + private class ResistDoAfterCancelled : EntityEventArgs + { + public readonly EntityUid User; + + public ResistDoAfterCancelled(EntityUid userUid) + { + User = userUid; + } + } +} diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index bada7c58ca..67d99a880c 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -183,7 +183,9 @@ namespace Content.Server.Storage.Components { if (IsWeldedShut) { - if (!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message")); + if (!silent && !Contents.Contains(user)) + Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message")); + return false; } diff --git a/Resources/Locale/en-US/resist/components/resist-locker-component.ftl b/Resources/Locale/en-US/resist/components/resist-locker-component.ftl new file mode 100644 index 0000000000..fb524cca41 --- /dev/null +++ b/Resources/Locale/en-US/resist/components/resist-locker-component.ftl @@ -0,0 +1,2 @@ +resist-locker-component-start-resisting = You begin to kick at the door! +resist-locker-component-resist-interrupted = Your attempts to kick at the door were interrupted! diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml index a68b891b68..ab7bce4241 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/base_structureclosets.yml @@ -4,6 +4,7 @@ name: closet description: A standard-issue Nanotrasen storage unit. components: + - type: ResistLocker - type: Transform noRot: true - type: Sprite