From 7d0b06f551de22d92b08e04bc5c058e1980e5aad Mon Sep 17 00:00:00 2001 From: JustinTime <41876089+JustinTether@users.noreply.github.com> Date: Wed, 5 Jan 2022 23:18:48 -0700 Subject: [PATCH] Add ability to resist out of welded lockers and locked closets (#5958) * Enable lighting of Cigars with IsHotEvent Subscribed to IsHotEvent in ThrusterSystem, added AfterInteractEvent to SmokingSystem.Cigar which will set SmokableState to Lit if IsHotEvent returns true This should mean that cigs/Cigars should light on anything hot * Revert "Enable lighting of Cigars with IsHotEvent" This reverts commit db896e1f80940892a87d34aa7182b9c605fa53bc. * Add the ability to kick your way out of welded or locked closets Added a ResistLocker component/system that hooks into OnRelayMove to begin resisting out of lockers so that players aren't stuck in lockers forever Also added check to EntityStorageComponent to not print out welded shut message if the entity attempting to open the locker is inside of it. * Changes based on review minor formatting changes make components passed into AttemptResist nullable and resolve them inside of the function Remove incorrectly named .ftl file * Changes based on review Ditched async DoAfter Used PopupSystem over User.PopupMessage Added Cancel token to ResistLockerComponent Subscribed to EntRemovedFromContainer event to cancel DoAfter if the player is removed from a container * I am in formatting hell, apparently * Changes based on review Added TryComp for the EntityStorageComponent instead of the event passed one * Changes based on review * Apply suggestions from code review Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> * Formatting changes based on review * Shuffle functions around in LockSystem to properly trigger visualizer Moved all the unlocking logic to a Lock method inside of LockSystem and TryUnlock calls this method if the user passes all of the access checks * Formatting, replacing Resolve with TryComp and making AttemptResist arguments optional Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> * Check if the player is inside THE container not A container me no read good * ok ok I give my code is bad Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> --- Content.Client/Entry/IgnoredComponents.cs | 3 +- Content.Server/Lock/LockSystem.cs | 37 +++--- .../Resist/ResistLockerComponent.cs | 32 +++++ Content.Server/Resist/ResistLockerSystem.cs | 114 ++++++++++++++++++ .../Components/EntityStorageComponent.cs | 4 +- .../components/resist-locker-component.ftl | 2 + .../Storage/Closets/base_structureclosets.yml | 1 + 7 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 Content.Server/Resist/ResistLockerComponent.cs create mode 100644 Content.Server/Resist/ResistLockerSystem.cs create mode 100644 Resources/Locale/en-US/resist/components/resist-locker-component.ftl 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