diff --git a/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs b/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs index 6e1d1d952e..a0c04da38e 100644 --- a/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs +++ b/Content.Server/Administration/Commands/ClearBluespaceLockerLinks.cs @@ -27,7 +27,7 @@ public sealed class ClearBluespaceLockerLinks : IConsoleCommand var entityManager = IoCManager.Resolve(); - if (entityManager.TryGetComponent(entityUid, out var originComponent)) + 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 index eefee173b3..df518cf820 100644 --- a/Content.Server/Administration/Commands/LinkBluespaceLocker.cs +++ b/Content.Server/Administration/Commands/LinkBluespaceLocker.cs @@ -53,10 +53,16 @@ public sealed class LinkBluespaceLocker : IConsoleCommand entityManager.EnsureComponent(originUid, out var originBluespaceComponent); originBluespaceComponent.BluespaceLinks.Add(targetComponent); + entityManager.EnsureComponent(targetUid, out var targetBluespaceComponent); if (bidirectional) { - entityManager.EnsureComponent(targetUid, out var targetBluespaceComponent); targetBluespaceComponent.BluespaceLinks.Add(originComponent); } + else if (targetBluespaceComponent.BluespaceLinks.Count == 0) + { + targetBluespaceComponent.AllowSentient = false; + targetBluespaceComponent.TransportEntities = false; + targetBluespaceComponent.TransportGas = false; + } } } diff --git a/Content.Server/Storage/Components/BluespaceLockerComponent.cs b/Content.Server/Storage/Components/BluespaceLockerComponent.cs index 1bded5d3fa..ad53672f79 100644 --- a/Content.Server/Storage/Components/BluespaceLockerComponent.cs +++ b/Content.Server/Storage/Components/BluespaceLockerComponent.cs @@ -27,4 +27,35 @@ public sealed class BluespaceLockerComponent : Component /// [DataField("bluespaceLinks"), ViewVariables(VVAccess.ReadOnly)] public HashSet BluespaceLinks = new(); + + /// + /// Each time the system attempts to get a link, it will link additional lockers to ensure the minimum amount + /// are linked. + /// + [DataField("minBluespaceLinks"), ViewVariables(VVAccess.ReadWrite)] + public uint MinBluespaceLinks; + + /// + /// Determines if links automatically added are restricted to the same map + /// + [DataField("pickLinksFromSameMap"), ViewVariables(VVAccess.ReadWrite)] + public bool PickLinksFromSameMap; + + /// + /// Determines if links automatically added must have ResistLockerComponent + /// + [DataField("pickLinksFromResistLockers"), ViewVariables(VVAccess.ReadWrite)] + public bool PickLinksFromResistLockers = true; + + /// + /// Determines if links automatically added are restricted to being on a station + /// + [DataField("pickLinksFromStationGrids"), ViewVariables(VVAccess.ReadWrite)] + public bool PickLinksFromStationGrids = true; + + /// + /// Determines if links automatically added are bidirectional + /// + [DataField("autoLinksBidirectional"), ViewVariables(VVAccess.ReadWrite)] + public bool AutoLinksBidirectional; } diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index 73bb1716b8..e7c41267f3 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -1,14 +1,19 @@ using System.Linq; using Content.Server.Lock; using Content.Server.Mind.Components; +using Content.Server.Resist; +using Content.Server.Station.Components; using Content.Server.Storage.Components; using Content.Server.Tools.Systems; -using Microsoft.Extensions.DependencyModel; +using Content.Shared.Coordinates; +using Robust.Shared.Random; namespace Content.Server.Storage.EntitySystems; public sealed class BluespaceLockerSystem : EntitySystem { + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly WeldableSystem _weldableSystem = default!; [Dependency] private readonly LockSystem _lockSystem = default!; @@ -17,23 +22,27 @@ public sealed class BluespaceLockerSystem : EntitySystem { base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(PreOpen); SubscribeLocalEvent(PostClose); } + private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args) + { + GetTargetStorage(component); + } 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)]; + var targetContainerStorageComponent = GetTargetStorage(component); + if (targetContainerStorageComponent == null) + return; BluespaceLockerComponent? targetContainerBluespaceComponent = null; // Close target if it is open @@ -41,8 +50,7 @@ public sealed class BluespaceLockerSystem : EntitySystem _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 }) + if (!Resolve(targetContainerStorageComponent.Owner, ref targetContainerBluespaceComponent, false)) { // Move contained items if (component.TransportEntities) @@ -62,18 +70,84 @@ public sealed class BluespaceLockerSystem : EntitySystem } } + private bool ValidLink(BluespaceLockerComponent component, EntityStorageComponent link) + { + return link.Owner.Valid && link.LifeStage != ComponentLifeStage.Deleted; + } + + private bool ValidAutolink(BluespaceLockerComponent component, EntityStorageComponent link) + { + if (!ValidLink(component, link)) + return false; + + if (component.PickLinksFromSameMap && + link.Owner.ToCoordinates().GetMapId(_entityManager) == component.Owner.ToCoordinates().GetMapId(_entityManager)) + return false; + + if (component.PickLinksFromStationGrids && + !_entityManager.HasComponent(link.Owner.ToCoordinates().GetGridUid(_entityManager))) + return false; + + if (component.PickLinksFromResistLockers && + !_entityManager.HasComponent(link.Owner)) + return false; + + return true; + } + + private EntityStorageComponent? GetTargetStorage(BluespaceLockerComponent component) + { + while (true) + { + // Ensure MinBluespaceLinks + if (component.BluespaceLinks.Count < component.MinBluespaceLinks) + { + // Get an shuffle the list of all EntityStorages + var storages = _entityManager.EntityQuery().ToArray(); + _robustRandom.Shuffle(storages); + + // Add valid candidates till MinBluespaceLinks is met + foreach (var storage in storages) + { + if (!ValidAutolink(component, storage)) + continue; + + component.BluespaceLinks.Add(storage); + if (component.AutoLinksBidirectional) + { + _entityManager.EnsureComponent(storage.Owner, out var targetBluespaceComponent); + targetBluespaceComponent.BluespaceLinks.Add(_entityManager.GetComponent(component.Owner)); + } + if (component.BluespaceLinks.Count >= component.MinBluespaceLinks) + break; + } + } + + // If there are no possible link targets and no links, return null + if (component.BluespaceLinks.Count == 0) + return null; + + // Attempt to select, validate, and return a link + var links = component.BluespaceLinks.ToArray(); + var link = links[_robustRandom.Next(0, component.BluespaceLinks.Count)]; + if (ValidLink(component, link)) + return link; + component.BluespaceLinks.Remove(link); + } + } + + 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)]; + var targetContainerStorageComponent = GetTargetStorage(component); + if (targetContainerStorageComponent == null) + return; // Move contained items if (component.TransportEntities) diff --git a/Resources/Prototypes/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Entities/Structures/Walls/walls.yml index ed16346326..796e092ba5 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/walls.yml @@ -388,17 +388,31 @@ - type: entity parent: BaseWall + id: WallPlastitaniumIndestructible + name: plastitanium wall + suffix: indestructible + components: + - type: Tag + tags: + - Wall + - type: Sprite + sprite: Structures/Walls/plastitanium.rsi + - type: Icon + sprite: Structures/Walls/plastitanium.rsi + - type: IconSmooth + key: walls + base: plastitanium + +- type: entity + parent: WallPlastitaniumIndestructible id: WallPlastitanium name: plastitanium wall + suffix: "" components: - type: Tag tags: - Wall - RCDDeconstructWhitelist - - type: Sprite - sprite: Structures/Walls/plastitanium.rsi - - type: Icon - sprite: Structures/Walls/plastitanium.rsi - type: Destructible thresholds: - trigger: @@ -412,9 +426,6 @@ max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] - - type: IconSmooth - key: walls - base: plastitanium - type: entity parent: BaseWall