From 067932712acac423f0a56aa10a8742866bf9cc32 Mon Sep 17 00:00:00 2001 From: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Date: Mon, 19 Dec 2022 21:47:37 -0600 Subject: [PATCH] Add bluespace lockers (#12954) * add bluespace lockers * add command linkbluespacelocker * add command clearbluespacelockerlinks * fix unwelding method * move bluespace locker functionality to own component * add options to disable transporting certain things * remove unused imports * unlock target lockers when opening + minor optimization to unwelding --- .../Commands/ClearBluespaceLockerLinks.cs | 33 +++++ .../Commands/LinkBluespaceLocker.cs | 62 ++++++++++ Content.Server/Lock/LockSystem.cs | 5 +- .../Components/BluespaceLockerComponent.cs | 30 +++++ .../Components/EntityStorageComponent.cs | 1 + .../EntitySystems/BluespaceLockerSystem.cs | 116 ++++++++++++++++++ .../EntitySystems/EntityStorageSystem.cs | 3 +- 7 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs create mode 100644 Content.Server/Administration/Commands/LinkBluespaceLocker.cs create mode 100644 Content.Server/Storage/Components/BluespaceLockerComponent.cs create mode 100644 Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs diff --git a/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs b/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs new file mode 100644 index 0000000000..6e1d1d952e --- /dev/null +++ b/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs @@ -0,0 +1,33 @@ +using Content.Server.Storage.Components; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed class ClearBluespaceLockerLinks : IConsoleCommand +{ + public string Command => "clearbluespacelockerlinks"; + public string Description => "Removes the bluespace links of the given uid. Does not remove links this uid is the target of."; + public string Help => "Usage: clearbluespacelockerlinks "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!EntityUid.TryParse(args[0], out var entityUid)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var entityManager = IoCManager.Resolve(); + + if (entityManager.TryGetComponent(entityUid, out var originComponent)) + entityManager.RemoveComponent(entityUid, originComponent); + } +} diff --git a/Content.Server/Administration/Commands/LinkBluespaceLocker.cs b/Content.Server/Administration/Commands/LinkBluespaceLocker.cs new file mode 100644 index 0000000000..eefee173b3 --- /dev/null +++ b/Content.Server/Administration/Commands/LinkBluespaceLocker.cs @@ -0,0 +1,62 @@ +using Content.Server.Storage.Components; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Admin)] +public sealed class LinkBluespaceLocker : IConsoleCommand +{ + public string Command => "linkbluespacelocker"; + public string Description => "Links an entity, the target, to another as a bluespace locker target."; + public string Help => "Usage: linkbluespacelocker "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 3) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!Boolean.TryParse(args[0], out var bidirectional)) + { + shell.WriteError(Loc.GetString("shell-invalid-bool")); + return; + } + + if (!EntityUid.TryParse(args[1], out var originUid)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + if (!EntityUid.TryParse(args[2], out var targetUid)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + var entityManager = IoCManager.Resolve(); + + if (!entityManager.TryGetComponent(originUid, out var originComponent)) + { + shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component", ("uid", originUid), ("componentName", nameof(EntityStorageComponent)))); + return; + } + + if (!entityManager.TryGetComponent(targetUid, out var targetComponent)) + { + shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component", ("uid", targetUid), ("componentName", nameof(EntityStorageComponent)))); + return; + } + + entityManager.EnsureComponent(originUid, out var originBluespaceComponent); + originBluespaceComponent.BluespaceLinks.Add(targetComponent); + if (bidirectional) + { + entityManager.EnsureComponent(targetUid, out var targetBluespaceComponent); + targetBluespaceComponent.BluespaceLinks.Add(originComponent); + } + } +} diff --git a/Content.Server/Lock/LockSystem.cs b/Content.Server/Lock/LockSystem.cs index 27057b6bf1..84f7d7600a 100644 --- a/Content.Server/Lock/LockSystem.cs +++ b/Content.Server/Lock/LockSystem.cs @@ -109,12 +109,13 @@ namespace Content.Server.Lock return true; } - public void Unlock(EntityUid uid, EntityUid user, LockComponent? lockComp = null) + 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", EntityManager.GetComponent(lockComp.Owner).EntityName))); + if (user is {Valid: true}) + lockComp.Owner.PopupMessage(user.Value, Loc.GetString("lock-comp-do-unlock-success", ("entityName", EntityManager.GetComponent(lockComp.Owner).EntityName))); lockComp.Locked = false; if (lockComp.UnlockSound != null) diff --git a/Content.Server/Storage/Components/BluespaceLockerComponent.cs b/Content.Server/Storage/Components/BluespaceLockerComponent.cs new file mode 100644 index 0000000000..1bded5d3fa --- /dev/null +++ b/Content.Server/Storage/Components/BluespaceLockerComponent.cs @@ -0,0 +1,30 @@ +namespace Content.Server.Storage.Components; + +[RegisterComponent] +public sealed class BluespaceLockerComponent : Component +{ + /// + /// Determines if gas will be transported. + /// + [DataField("transportGas"), ViewVariables(VVAccess.ReadWrite)] + public bool TransportGas = true; + + /// + /// Determines if entities will be transported. + /// + [DataField("transportEntities"), ViewVariables(VVAccess.ReadWrite)] + public bool TransportEntities = true; + + /// + /// Determines if entities with a Mind component will be transported. + /// + [DataField("allowSentient"), ViewVariables(VVAccess.ReadWrite)] + public bool AllowSentient = true; + + /// + /// If length > 0, when something is added to the storage, it will instead be teleported to a random storage + /// from the list and the other storage will be opened. + /// + [DataField("bluespaceLinks"), ViewVariables(VVAccess.ReadOnly)] + public HashSet BluespaceLinks = new(); +} diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index 1f12339d87..8173cbb6bf 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -114,6 +114,7 @@ public sealed class StorageOpenAttemptEvent : CancellableEntityEventArgs Silent = silent; } } +public sealed class StorageBeforeOpenEvent : EventArgs { } public sealed class StorageAfterOpenEvent : EventArgs { } public sealed class StorageCloseAttemptEvent : CancellableEntityEventArgs { } public sealed class StorageBeforeCloseEvent : EventArgs diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs new file mode 100644 index 0000000000..73bb1716b8 --- /dev/null +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -0,0 +1,116 @@ +using System.Linq; +using Content.Server.Lock; +using Content.Server.Mind.Components; +using Content.Server.Storage.Components; +using Content.Server.Tools.Systems; +using Microsoft.Extensions.DependencyModel; + +namespace Content.Server.Storage.EntitySystems; + +public sealed class BluespaceLockerSystem : EntitySystem +{ + [Dependency] private readonly EntityStorageSystem _entityStorage = default!; + [Dependency] private readonly WeldableSystem _weldableSystem = default!; + [Dependency] private readonly LockSystem _lockSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(PreOpen); + SubscribeLocalEvent(PostClose); + } + + + private void PreOpen(EntityUid uid, BluespaceLockerComponent component, StorageBeforeOpenEvent args) + { + EntityStorageComponent? entityStorageComponent = null; + + if (component.BluespaceLinks is not { Count: > 0 }) + return; + + if (!Resolve(uid, ref entityStorageComponent)) + return; + + // Select target + var targetContainerStorageComponent = component.BluespaceLinks.ToArray()[new Random().Next(0, component.BluespaceLinks.Count)]; + BluespaceLockerComponent? targetContainerBluespaceComponent = null; + + // Close target if it is open + if (targetContainerStorageComponent.Open) + _entityStorage.CloseStorage(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + + // Apply bluespace effects if target is not a bluespace locker, otherwise let it handle it + if (!Resolve(targetContainerStorageComponent.Owner, ref targetContainerBluespaceComponent, false) || + targetContainerBluespaceComponent.BluespaceLinks is not { Count: > 0 }) + { + // Move contained items + if (component.TransportEntities) + foreach (var entity in targetContainerStorageComponent.Contents.ContainedEntities.ToArray()) + { + if (!component.AllowSentient && EntityManager.HasComponent(entity)) + continue; + entityStorageComponent.Contents.Insert(entity, EntityManager); + } + + // Move contained air + if (component.TransportGas) + { + entityStorageComponent.Air.CopyFromMutable(targetContainerStorageComponent.Air); + targetContainerStorageComponent.Air.Clear(); + } + } + } + + private void PostClose(EntityUid uid, BluespaceLockerComponent component, StorageAfterCloseEvent args) + { + EntityStorageComponent? entityStorageComponent = null; + + if (component.BluespaceLinks is not { Count: > 0 }) + return; + + if (!Resolve(uid, ref entityStorageComponent)) + return; + + // Select target + var targetContainerStorageComponent = component.BluespaceLinks.ToArray()[new Random().Next(0, component.BluespaceLinks.Count)]; + + // Move contained items + if (component.TransportEntities) + foreach (var entity in entityStorageComponent.Contents.ContainedEntities.ToArray()) + { + if (!component.AllowSentient && EntityManager.HasComponent(entity)) + continue; + targetContainerStorageComponent.Contents.Insert(entity, EntityManager); + } + + // Move contained air + if (component.TransportGas) + { + targetContainerStorageComponent.Air.CopyFromMutable(entityStorageComponent.Air); + entityStorageComponent.Air.Clear(); + } + + // Open and empty target + if (targetContainerStorageComponent.Open) + { + _entityStorage.EmptyContents(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + _entityStorage.ReleaseGas(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + } + else + { + if (targetContainerStorageComponent.IsWeldedShut) + { + // It gets bluespaced open... + _weldableSystem.ForceWeldedState(targetContainerStorageComponent.Owner, false); + if (targetContainerStorageComponent.IsWeldedShut) + targetContainerStorageComponent.IsWeldedShut = false; + } + LockComponent? lockComponent = null; + if (Resolve(targetContainerStorageComponent.Owner, ref lockComponent, false) && lockComponent.Locked) + _lockSystem.Unlock(lockComponent.Owner, lockComponent.Owner, lockComponent); + + _entityStorage.OpenStorage(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + } + } +} diff --git a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs index 64d189c185..9108591835 100644 --- a/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/EntityStorageSystem.cs @@ -164,6 +164,7 @@ public sealed class EntityStorageSystem : EntitySystem if (!Resolve(uid, ref component)) return; + RaiseLocalEvent(uid, new StorageBeforeOpenEvent()); component.Open = true; EmptyContents(uid, component); ModifyComponents(uid, component); @@ -414,7 +415,7 @@ public sealed class EntityStorageSystem : EntitySystem } } - private void ReleaseGas(EntityUid uid, EntityStorageComponent component) + public void ReleaseGas(EntityUid uid, EntityStorageComponent component) { if (!component.Airtight) return;