diff --git a/Content.Server/Administration/Commands/LinkBluespaceLocker.cs b/Content.Server/Administration/Commands/LinkBluespaceLocker.cs index df518cf820..59d16745dd 100644 --- a/Content.Server/Administration/Commands/LinkBluespaceLocker.cs +++ b/Content.Server/Administration/Commands/LinkBluespaceLocker.cs @@ -52,17 +52,17 @@ public sealed class LinkBluespaceLocker : IConsoleCommand } entityManager.EnsureComponent(originUid, out var originBluespaceComponent); - originBluespaceComponent.BluespaceLinks.Add(targetComponent); + originBluespaceComponent.BluespaceLinks.Add(targetUid); entityManager.EnsureComponent(targetUid, out var targetBluespaceComponent); if (bidirectional) { - targetBluespaceComponent.BluespaceLinks.Add(originComponent); + targetBluespaceComponent.BluespaceLinks.Add(originUid); } else if (targetBluespaceComponent.BluespaceLinks.Count == 0) { - targetBluespaceComponent.AllowSentient = false; - targetBluespaceComponent.TransportEntities = false; - targetBluespaceComponent.TransportGas = false; + targetBluespaceComponent.BehaviorProperties.TransportSentient = false; + targetBluespaceComponent.BehaviorProperties.TransportEntities = false; + targetBluespaceComponent.BehaviorProperties.TransportGas = false; } } } diff --git a/Content.Server/StationEvents/Events/BluespaceLocker.cs b/Content.Server/StationEvents/Events/BluespaceLocker.cs new file mode 100644 index 0000000000..cae89ca4ba --- /dev/null +++ b/Content.Server/StationEvents/Events/BluespaceLocker.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Content.Server.Resist; +using Content.Server.Station.Components; +using Content.Server.Storage.Components; +using Content.Shared.Access.Components; +using Content.Shared.Coordinates; +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + +public sealed class BluespaceLockerLink : StationEventSystem +{ + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + public override string Prototype => "BluespaceLockerLink"; + + public override void Started() + { + base.Started(); + + var targets = EntityQuery().ToList(); + _robustRandom.Shuffle(targets); + + foreach (var target in targets) + { + var potentialLink = target.Item1.Owner; + + if (HasComp(potentialLink) || + HasComp(potentialLink) || + !HasComp(potentialLink.ToCoordinates().GetGridUid(EntityManager))) + continue; + + using var compInitializeHandle = EntityManager.AddComponentUninitialized(potentialLink); + var comp = compInitializeHandle.Comp; + + comp.PickLinksFromSameMap = true; + comp.MinBluespaceLinks = 1; + comp.BluespaceEffectOnInit = true; + comp.BehaviorProperties.BluespaceEffectOnTeleportSource = true; + comp.AutoLinksBidirectional = true; + comp.AutoLinksUseProperties = true; + comp.AutoLinkProperties.BluespaceEffectOnTeleportSource = true; + + compInitializeHandle.Dispose(); + + Sawmill.Info($"Converted {ToPrettyString(potentialLink)} to bluespace locker"); + + return; + } + } +} diff --git a/Content.Server/Storage/Components/BluespaceLockerComponent.cs b/Content.Server/Storage/Components/BluespaceLockerComponent.cs index ad53672f79..93239c9b66 100644 --- a/Content.Server/Storage/Components/BluespaceLockerComponent.cs +++ b/Content.Server/Storage/Components/BluespaceLockerComponent.cs @@ -1,32 +1,16 @@ -namespace Content.Server.Storage.Components; +using System.Threading; + +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(); + public HashSet BluespaceLinks = new(); /// /// Each time the system attempts to get a link, it will link additional lockers to ensure the minimum amount @@ -54,8 +38,126 @@ public sealed class BluespaceLockerComponent : Component public bool PickLinksFromStationGrids = true; /// - /// Determines if links automatically added are bidirectional + /// Determines if links automatically added are restricted to having the same access + /// + [DataField("pickLinksFromSameAccess"), ViewVariables(VVAccess.ReadWrite)] + public bool PickLinksFromSameAccess = true; + + /// + /// Determines if links automatically added are restricted to existing bluespace lockers + /// + [DataField("pickLinksFromBluespaceLockers"), ViewVariables(VVAccess.ReadWrite)] + public bool PickLinksFromBluespaceLockers; + + /// + /// Determines if links automatically added are restricted to non-bluespace lockers + /// + [DataField("pickLinksFromNonBluespaceLockers"), ViewVariables(VVAccess.ReadWrite)] + public bool PickLinksFromNonBluespaceLockers = true; + + public CancellationTokenSource? CancelToken; + + /// + /// Determines if bluespace effect is show on component init + /// + [DataField("bluespaceEffectOnInit")] + public bool BluespaceEffectOnInit; + + /// + /// Determines if links automatically added get the source locker set as a target /// [DataField("autoLinksBidirectional"), ViewVariables(VVAccess.ReadWrite)] public bool AutoLinksBidirectional; + + /// + /// Determines if links automatically use + /// + [DataField("autoLinksUseProperties"), ViewVariables(VVAccess.ReadWrite)] + public bool AutoLinksUseProperties; + + public int UsesSinceLinkClear; + + /// + /// Determines properties of automatically created links + /// + [DataField("autoLinkProperties"), ViewVariables(VVAccess.ReadOnly)] + public BluespaceLockerBehaviorProperties AutoLinkProperties = new(); + + /// + /// Determines properties of this locker + /// + [DataField("behaviorProperties"), ViewVariables(VVAccess.ReadOnly)] + public BluespaceLockerBehaviorProperties BehaviorProperties = new(); +} + +[DataDefinition] +public record BluespaceLockerBehaviorProperties +{ + /// + /// Determines if gas will be transported. + /// + [DataField("transportGas"), ViewVariables(VVAccess.ReadWrite)] + public bool TransportGas { get; set; } = true; + + /// + /// Determines if entities will be transported. + /// + [DataField("transportEntities"), ViewVariables(VVAccess.ReadWrite)] + public bool TransportEntities { get; set; } = true; + + /// + /// Determines if entities with a Mind component will be transported. + /// + [DataField("transportSentient"), ViewVariables(VVAccess.ReadWrite)] + public bool TransportSentient { get; set; } = true; + + /// + /// Delay to wait after closing before transporting + /// + [DataField("delay"), ViewVariables(VVAccess.ReadWrite)] + public float Delay { get; set; } + + /// + /// Defines prototype to spawn for bluespace effect + /// + [DataField("bluespaceEffectPrototype"), ViewVariables(VVAccess.ReadWrite)] + public string BluespaceEffectPrototype { get; set; } = "EffectFlashBluespace"; + + /// + /// Determines if bluespace effect is show on teleport at the source + /// + [DataField("bluespaceEffectOnTeleportSource"), ViewVariables(VVAccess.ReadWrite)] + public bool BluespaceEffectOnTeleportSource { get; set; } + + /// + /// Determines if bluespace effect is show on teleport at the target + /// + [DataField("bluespaceEffectOnTeleportTarget"), ViewVariables(VVAccess.ReadWrite)] + public bool BluespaceEffectOnTeleportTarget { get; set; } + + /// + /// Uses left before the locker is destroyed. -1 indicates infinite + /// + [DataField("destroyAfterUses"), ViewVariables(VVAccess.ReadWrite)] + public int DestroyAfterUses { get; set; } = -1; + + /// + /// How to destroy the locker after it runs out of uses + /// + [DataField("destroyType"), ViewVariables(VVAccess.ReadWrite)] + public BluespaceLockerDestroyType DestroyType { get; set; } = BluespaceLockerDestroyType.Delete; + + /// + /// Uses left before the lockers links are cleared. -1 indicates infinite + /// + [DataField("clearLinksEvery"), ViewVariables(VVAccess.ReadWrite)] + public int ClearLinksEvery { get; set; } = -1; +} + +[Flags] +public enum BluespaceLockerDestroyType +{ + Delete, + DeleteComponent, + Explode, } diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index e7c41267f3..0d8390593c 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -1,10 +1,14 @@ using System.Linq; +using System.Threading; +using Content.Server.DoAfter; +using Content.Server.Explosion.EntitySystems; 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 Content.Shared.Access.Components; using Content.Shared.Coordinates; using Robust.Shared.Random; @@ -17,6 +21,8 @@ public sealed class BluespaceLockerSystem : EntitySystem [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly WeldableSystem _weldableSystem = default!; [Dependency] private readonly LockSystem _lockSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; public override void Initialize() { @@ -25,11 +31,20 @@ public sealed class BluespaceLockerSystem : EntitySystem SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(PreOpen); SubscribeLocalEvent(PostClose); + SubscribeLocalEvent(OnBluespaceLockerTeleportDelayComplete); } private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args) { - GetTargetStorage(component); + GetTarget(uid, component); + + if (component.BluespaceEffectOnInit) + BluespaceEffect(uid, component); + } + + private void BluespaceEffect(EntityUid uid, BluespaceLockerComponent component) + { + Spawn(component.BehaviorProperties.BluespaceEffectPrototype, uid.ToCoordinates()); } private void PreOpen(EntityUid uid, BluespaceLockerComponent component, StorageBeforeOpenEvent args) @@ -39,63 +54,110 @@ public sealed class BluespaceLockerSystem : EntitySystem if (!Resolve(uid, ref entityStorageComponent)) return; + component.CancelToken?.Cancel(); + // Select target - var targetContainerStorageComponent = GetTargetStorage(component); - if (targetContainerStorageComponent == null) + var target = GetTarget(uid, component); + if (target == null) return; - BluespaceLockerComponent? targetContainerBluespaceComponent = null; // Close target if it is open - if (targetContainerStorageComponent.Open) - _entityStorage.CloseStorage(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + if (target.Value.storageComponent.Open) + _entityStorage.CloseStorage(target.Value.uid, target.Value.storageComponent); // Apply bluespace effects if target is not a bluespace locker, otherwise let it handle it - if (!Resolve(targetContainerStorageComponent.Owner, ref targetContainerBluespaceComponent, false)) + if (target.Value.bluespaceLockerComponent == null) { // Move contained items - if (component.TransportEntities) - foreach (var entity in targetContainerStorageComponent.Contents.ContainedEntities.ToArray()) + if (component.BehaviorProperties.TransportEntities || component.BehaviorProperties.TransportSentient) + foreach (var entity in target.Value.storageComponent.Contents.ContainedEntities.ToArray()) { - if (!component.AllowSentient && EntityManager.HasComponent(entity)) - continue; - entityStorageComponent.Contents.Insert(entity, EntityManager); + if (EntityManager.HasComponent(entity)) + { + if (component.BehaviorProperties.TransportSentient) + entityStorageComponent.Contents.Insert(entity, EntityManager); + } + else if (component.BehaviorProperties.TransportEntities) + entityStorageComponent.Contents.Insert(entity, EntityManager); } // Move contained air - if (component.TransportGas) + if (component.BehaviorProperties.TransportGas) { - entityStorageComponent.Air.CopyFromMutable(targetContainerStorageComponent.Air); - targetContainerStorageComponent.Air.Clear(); + entityStorageComponent.Air.CopyFromMutable(target.Value.storageComponent.Air); + target.Value.storageComponent.Air.Clear(); } + + // Bluespace effects + if (component.BehaviorProperties.BluespaceEffectOnTeleportSource) + BluespaceEffect(target.Value.uid, component); + if (component.BehaviorProperties.BluespaceEffectOnTeleportTarget) + BluespaceEffect(uid, component); } + + DestroyAfterLimit(uid, component); } - private bool ValidLink(BluespaceLockerComponent component, EntityStorageComponent link) + private bool ValidLink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent) { - return link.Owner.Valid && link.LifeStage != ComponentLifeStage.Deleted; + return link.Valid && TryComp(link, out var linkStorage) && linkStorage.LifeStage != ComponentLifeStage.Deleted && link != locker; } - private bool ValidAutolink(BluespaceLockerComponent component, EntityStorageComponent link) + /// True if any HashSet in would grant access to + private bool AccessMatch(IReadOnlyCollection>? a, IReadOnlyCollection>? b) { - if (!ValidLink(component, link)) + if ((a == null || a.Count == 0) && (b == null || b.Count == 0)) + return true; + if (a != null && a.Any(aSet => aSet.Count == 0)) + return true; + if (b != null && b.Any(bSet => bSet.Count == 0)) + return true; + + if (a != null && b != null) + return a.Any(aSet => b.Any(aSet.SetEquals)); + return false; + } + + private bool ValidAutolink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent) + { + if (!ValidLink(locker, link, lockerComponent)) return false; - if (component.PickLinksFromSameMap && - link.Owner.ToCoordinates().GetMapId(_entityManager) == component.Owner.ToCoordinates().GetMapId(_entityManager)) + if (lockerComponent.PickLinksFromSameMap && + link.ToCoordinates().GetMapId(_entityManager) != locker.ToCoordinates().GetMapId(_entityManager)) return false; - if (component.PickLinksFromStationGrids && - !_entityManager.HasComponent(link.Owner.ToCoordinates().GetGridUid(_entityManager))) + if (lockerComponent.PickLinksFromStationGrids && + !HasComp(link.ToCoordinates().GetGridUid(_entityManager))) return false; - if (component.PickLinksFromResistLockers && - !_entityManager.HasComponent(link.Owner)) + if (lockerComponent.PickLinksFromResistLockers && + !HasComp(link)) return false; + if (lockerComponent.PickLinksFromSameAccess) + { + TryComp(locker, out var sourceAccess); + TryComp(link, out var targetAccess); + if (!AccessMatch(sourceAccess?.AccessLists, targetAccess?.AccessLists)) + return false; + } + + if (HasComp(link)) + { + if (lockerComponent.PickLinksFromNonBluespaceLockers) + return false; + } + else + { + if (lockerComponent.PickLinksFromBluespaceLockers) + return false; + } + return true; } - private EntityStorageComponent? GetTargetStorage(BluespaceLockerComponent component) + private (EntityUid uid, EntityStorageComponent storageComponent, BluespaceLockerComponent? bluespaceLockerComponent)? GetTarget(EntityUid lockerUid, BluespaceLockerComponent component) { while (true) { @@ -103,20 +165,27 @@ public sealed class BluespaceLockerSystem : EntitySystem if (component.BluespaceLinks.Count < component.MinBluespaceLinks) { // Get an shuffle the list of all EntityStorages - var storages = _entityManager.EntityQuery().ToArray(); + var storages = EntityQuery().ToArray(); _robustRandom.Shuffle(storages); // Add valid candidates till MinBluespaceLinks is met foreach (var storage in storages) { - if (!ValidAutolink(component, storage)) + var potentialLink = storage.Owner; + + if (!ValidAutolink(lockerUid, potentialLink, component)) continue; - component.BluespaceLinks.Add(storage); - if (component.AutoLinksBidirectional) + component.BluespaceLinks.Add(potentialLink); + if (component.AutoLinksBidirectional || component.AutoLinksUseProperties) { - _entityManager.EnsureComponent(storage.Owner, out var targetBluespaceComponent); - targetBluespaceComponent.BluespaceLinks.Add(_entityManager.GetComponent(component.Owner)); + var targetBluespaceComponent = EnsureComp(potentialLink); + + if (component.AutoLinksBidirectional) + targetBluespaceComponent.BluespaceLinks.Add(lockerUid); + + if (component.AutoLinksUseProperties) + targetBluespaceComponent.BehaviorProperties = component.AutoLinkProperties with {}; } if (component.BluespaceLinks.Count >= component.MinBluespaceLinks) break; @@ -130,61 +199,136 @@ public sealed class BluespaceLockerSystem : EntitySystem // 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; + if (ValidLink(lockerUid, link, component)) + return (link, Comp(link), CompOrNull(link)); component.BluespaceLinks.Remove(link); } } - private void PostClose(EntityUid uid, BluespaceLockerComponent component, StorageAfterCloseEvent args) + { + PostClose(uid, component); + } + + private void OnBluespaceLockerTeleportDelayComplete(EntityUid uid, BluespaceLockerComponent component, BluespaceLockerTeleportDelayComplete args) + { + PostClose(uid, component, false); + } + + private void PostClose(EntityUid uid, BluespaceLockerComponent component, bool doDelay = true) { EntityStorageComponent? entityStorageComponent = null; if (!Resolve(uid, ref entityStorageComponent)) return; + component.CancelToken?.Cancel(); + + // Do delay + if (doDelay && component.BehaviorProperties.Delay > 0) + { + EnsureComp(uid); + component.CancelToken = new CancellationTokenSource(); + + _doAfterSystem.DoAfter( + new DoAfterEventArgs(uid, component.BehaviorProperties.Delay, component.CancelToken.Token) + { + UserFinishedEvent = new BluespaceLockerTeleportDelayComplete() + }); + return; + } + // Select target - var targetContainerStorageComponent = GetTargetStorage(component); - if (targetContainerStorageComponent == null) + var target = GetTarget(uid, component); + if (target == null) return; // Move contained items - if (component.TransportEntities) + if (component.BehaviorProperties.TransportEntities || component.BehaviorProperties.TransportSentient) foreach (var entity in entityStorageComponent.Contents.ContainedEntities.ToArray()) { - if (!component.AllowSentient && EntityManager.HasComponent(entity)) - continue; - targetContainerStorageComponent.Contents.Insert(entity, EntityManager); + if (EntityManager.HasComponent(entity)) + { + if (component.BehaviorProperties.TransportSentient) + target.Value.storageComponent.Contents.Insert(entity, EntityManager); + } + else if (component.BehaviorProperties.TransportEntities) + target.Value.storageComponent.Contents.Insert(entity, EntityManager); } // Move contained air - if (component.TransportGas) + if (component.BehaviorProperties.TransportGas) { - targetContainerStorageComponent.Air.CopyFromMutable(entityStorageComponent.Air); + target.Value.storageComponent.Air.CopyFromMutable(entityStorageComponent.Air); entityStorageComponent.Air.Clear(); } // Open and empty target - if (targetContainerStorageComponent.Open) + if (target.Value.storageComponent.Open) { - _entityStorage.EmptyContents(targetContainerStorageComponent.Owner, targetContainerStorageComponent); - _entityStorage.ReleaseGas(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + _entityStorage.EmptyContents(target.Value.uid, target.Value.storageComponent); + _entityStorage.ReleaseGas(target.Value.uid, target.Value.storageComponent); } else { - if (targetContainerStorageComponent.IsWeldedShut) + if (target.Value.storageComponent.IsWeldedShut) { // It gets bluespaced open... - _weldableSystem.ForceWeldedState(targetContainerStorageComponent.Owner, false); - if (targetContainerStorageComponent.IsWeldedShut) - targetContainerStorageComponent.IsWeldedShut = false; + _weldableSystem.ForceWeldedState(target.Value.uid, false); + if (target.Value.storageComponent.IsWeldedShut) + target.Value.storageComponent.IsWeldedShut = false; } LockComponent? lockComponent = null; - if (Resolve(targetContainerStorageComponent.Owner, ref lockComponent, false) && lockComponent.Locked) - _lockSystem.Unlock(lockComponent.Owner, lockComponent.Owner, lockComponent); + if (Resolve(target.Value.uid, ref lockComponent, false) && lockComponent.Locked) + _lockSystem.Unlock(target.Value.uid, target.Value.uid, lockComponent); - _entityStorage.OpenStorage(targetContainerStorageComponent.Owner, targetContainerStorageComponent); + _entityStorage.OpenStorage(target.Value.uid, target.Value.storageComponent); + } + + // Bluespace effects + if (component.BehaviorProperties.BluespaceEffectOnTeleportSource) + BluespaceEffect(uid, component); + if (component.BehaviorProperties.BluespaceEffectOnTeleportTarget) + BluespaceEffect(target.Value.uid, component); + + DestroyAfterLimit(uid, component); + } + + private void DestroyAfterLimit(EntityUid uid, BluespaceLockerComponent component) + { + if (component.BehaviorProperties.ClearLinksEvery != -1) + { + component.UsesSinceLinkClear++; + if (component.BehaviorProperties.ClearLinksEvery >= component.UsesSinceLinkClear) + { + component.BluespaceLinks.Clear(); + component.UsesSinceLinkClear = 0; + } + } + + if (component.BehaviorProperties.DestroyAfterUses == -1) + return; + + component.BehaviorProperties.DestroyAfterUses--; + if (component.BehaviorProperties.DestroyAfterUses > 0) + return; + + switch (component.BehaviorProperties.DestroyType) + { + case BluespaceLockerDestroyType.Explode: + _explosionSystem.QueueExplosion(uid.ToCoordinates().ToMap(_entityManager), + ExplosionSystem.DefaultExplosionPrototypeId, 4, 1, 2, maxTileBreak: 0); + goto case BluespaceLockerDestroyType.Delete; + case BluespaceLockerDestroyType.Delete: + QueueDel(uid); + break; + case BluespaceLockerDestroyType.DeleteComponent: + RemComp(uid); + break; } } + + private sealed class BluespaceLockerTeleportDelayComplete : EntityEventArgs + { + } } diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index 6e1bfd514a..8ae2f49625 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -353,3 +353,25 @@ state: syndicate state_open: syndicate_open state_closed: syndicate_door + +# Bluespace +- type: entity + id: LockerBluespaceStation + name: bluespace locker + suffix: once to station + parent: LockerSyndicatePersonal + description: Advanced locker technology. + components: + - type: BluespaceLocker + minBluespaceLinks: 1 + bluespaceEffectOnInit: true + behaviorProperties: + delay: 0.5 + bluespaceEffectOnTeleportSource: true + bluespaceEffectOnTeleportTarget: true + destroyAfterUses: 2 + destroyType: Delete + autoLinksUseProperties: true + autoLinkProperties: + destroyAfterUses: 2 + destroyType: DeleteComponent diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml index e3a4343883..9f7c5dfded 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml @@ -127,8 +127,49 @@ parent: ClosetBase description: It's a storage unit. components: - - type: Appearance - visuals: - - type: StorageVisualizer - state_open: generic_open - state_closed: generic_door + - type: Appearance + visuals: + - type: StorageVisualizer + state_open: generic_open + state_closed: generic_door + +# Bluespace closet +- type: entity + id: ClosetBluespace + name: suspicious closet + suffix: Bluespace + parent: ClosetMaintenance + description: It's a storage unit... right? + components: + - type: BluespaceLocker + pickLinksFromSameMap: true + minBluespaceLinks: 1 + behaviorProperties: + bluespaceEffectOnTeleportSource: true + autoLinksBidirectional: true + autoLinksUseProperties: true + autoLinkProperties: + bluespaceEffectOnTeleportSource: true + +# Unstable bluespace closet +- type: entity + id: ClosetBluespaceUnstable + name: suspicious closet + suffix: Bluespace unstable + parent: ClosetMaintenance + description: It's a storage unit... right? + components: + - type: BluespaceLocker + pickLinksFromSameMap: true + minBluespaceLinks: 1 + behaviorProperties: + transportEntities: false + bluespaceEffectOnTeleportSource: true + clearLinksEvery: 2 + autoLinksBidirectional: true + autoLinksUseProperties: true + autoLinkProperties: + transportEntities: false + bluespaceEffectOnTeleportSource: true + destroyAfterUses: 2 + destroyType: DeleteComponent diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 2f8a5eaa5c..dc71e8bf4a 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -7,6 +7,16 @@ startAfter: 30 endAfter: 35 +- type: gameRule + id: BluespaceLockerLink + config: + !type:StationEventRuleConfiguration + id: BluespaceLockerLink + weight: 0 + reoccurrenceDelay: 5 + earliestStart: 1 + endAfter: 1 + - type: gameRule id: BreakerFlip config: