diff --git a/Content.Server/StationEvents/Events/BluespaceLocker.cs b/Content.Server/StationEvents/Events/BluespaceLocker.cs index cae89ca4ba..a9be23db58 100644 --- a/Content.Server/StationEvents/Events/BluespaceLocker.cs +++ b/Content.Server/StationEvents/Events/BluespaceLocker.cs @@ -2,6 +2,7 @@ using Content.Server.Resist; using Content.Server.Station.Components; using Content.Server.Storage.Components; +using Content.Server.Storage.EntitySystems; using Content.Shared.Access.Components; using Content.Shared.Coordinates; using Robust.Shared.Random; @@ -11,6 +12,7 @@ namespace Content.Server.StationEvents.Events; public sealed class BluespaceLockerLink : StationEventSystem { [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly BluespaceLockerSystem _bluespaceLocker = default!; public override string Prototype => "BluespaceLockerLink"; @@ -30,18 +32,17 @@ public sealed class BluespaceLockerLink : StationEventSystem !HasComp(potentialLink.ToCoordinates().GetGridUid(EntityManager))) continue; - using var compInitializeHandle = EntityManager.AddComponentUninitialized(potentialLink); - var comp = compInitializeHandle.Comp; + var comp = AddComp(potentialLink); comp.PickLinksFromSameMap = true; comp.MinBluespaceLinks = 1; - comp.BluespaceEffectOnInit = true; comp.BehaviorProperties.BluespaceEffectOnTeleportSource = true; comp.AutoLinksBidirectional = true; comp.AutoLinksUseProperties = true; + comp.AutoLinkProperties.BluespaceEffectOnInit = true; comp.AutoLinkProperties.BluespaceEffectOnTeleportSource = true; - - compInitializeHandle.Dispose(); + _bluespaceLocker.GetTarget(potentialLink, comp); + _bluespaceLocker.BluespaceEffect(potentialLink, comp, comp, true); Sawmill.Info($"Converted {ToPrettyString(potentialLink)} to bluespace locker"); diff --git a/Content.Server/Storage/Components/BluespaceLockerComponent.cs b/Content.Server/Storage/Components/BluespaceLockerComponent.cs index 93239c9b66..233219faec 100644 --- a/Content.Server/Storage/Components/BluespaceLockerComponent.cs +++ b/Content.Server/Storage/Components/BluespaceLockerComponent.cs @@ -57,12 +57,6 @@ public sealed class BluespaceLockerComponent : Component 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 /// @@ -75,8 +69,12 @@ public sealed class BluespaceLockerComponent : Component [DataField("autoLinksUseProperties"), ViewVariables(VVAccess.ReadWrite)] public bool AutoLinksUseProperties; + [DataField("usesSinceLinkClear"), ViewVariables(VVAccess.ReadWrite)] public int UsesSinceLinkClear; + [DataField("bluespaceEffectMinInterval"), ViewVariables(VVAccess.ReadOnly)] + public uint BluespaceEffectNextTime { get; set; } + /// /// Determines properties of automatically created links /// @@ -111,12 +109,30 @@ public record BluespaceLockerBehaviorProperties [DataField("transportSentient"), ViewVariables(VVAccess.ReadWrite)] public bool TransportSentient { get; set; } = true; + /// + /// Determines if the the locker will act on opens. + /// + [DataField("actOnOpen"), ViewVariables(VVAccess.ReadWrite)] + public bool ActOnOpen { get; set; } = true; + + /// + /// Determines if the the locker will act on closes. + /// + [DataField("actOnClose"), ViewVariables(VVAccess.ReadWrite)] + public bool ActOnClose { get; set; } = true; + /// /// Delay to wait after closing before transporting /// [DataField("delay"), ViewVariables(VVAccess.ReadWrite)] public float Delay { get; set; } + /// + /// Determines if bluespace effect is show on component init + /// + [DataField("bluespaceEffectOnInit"), ViewVariables(VVAccess.ReadWrite)] + public bool BluespaceEffectOnInit; + /// /// Defines prototype to spawn for bluespace effect /// @@ -135,12 +151,25 @@ public record BluespaceLockerBehaviorProperties [DataField("bluespaceEffectOnTeleportTarget"), ViewVariables(VVAccess.ReadWrite)] public bool BluespaceEffectOnTeleportTarget { get; set; } + /// + /// Determines the minimum interval between bluespace effects + /// + /// + [DataField("bluespaceEffectMinInterval"), ViewVariables(VVAccess.ReadWrite)] + public double BluespaceEffectMinInterval { get; set; } = 2; + /// /// Uses left before the locker is destroyed. -1 indicates infinite /// [DataField("destroyAfterUses"), ViewVariables(VVAccess.ReadWrite)] public int DestroyAfterUses { get; set; } = -1; + /// + /// Minimum number of entities that must be transported to count a use for + /// + [DataField("destroyAfterUsesMinItemsToCountUse"), ViewVariables(VVAccess.ReadWrite)] + public int DestroyAfterUsesMinItemsToCountUse { get; set; } + /// /// How to destroy the locker after it runs out of uses /// @@ -152,6 +181,18 @@ public record BluespaceLockerBehaviorProperties /// [DataField("clearLinksEvery"), ViewVariables(VVAccess.ReadWrite)] public int ClearLinksEvery { get; set; } = -1; + + /// + /// Determines if cleared links have their component removed + /// + [DataField("clearLinksDebluespaces"), ViewVariables(VVAccess.ReadWrite)] + public bool ClearLinksDebluespaces { get; set; } + + /// + /// Links will not be valid if they're not bidirectional + /// + [DataField("invalidateOneWayLinks"), ViewVariables(VVAccess.ReadWrite)] + public bool InvalidateOneWayLinks { get; set; } } [Flags] diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index 0d8390593c..467391594e 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -11,13 +11,14 @@ using Content.Server.Tools.Systems; using Content.Shared.Access.Components; using Content.Shared.Coordinates; using Robust.Shared.Random; +using Robust.Shared.Timing; 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 IGameTiming _timing = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly WeldableSystem _weldableSystem = default!; [Dependency] private readonly LockSystem _lockSystem = default!; @@ -38,23 +39,42 @@ public sealed class BluespaceLockerSystem : EntitySystem { GetTarget(uid, component); - if (component.BluespaceEffectOnInit) - BluespaceEffect(uid, component); + if (component.BehaviorProperties.BluespaceEffectOnInit) + BluespaceEffect(uid, component, component, true); } - private void BluespaceEffect(EntityUid uid, BluespaceLockerComponent component) + public void BluespaceEffect(EntityUid effectTargetUid, BluespaceLockerComponent effectSourceComponent, BluespaceLockerComponent? effectTargetComponent, bool bypassLimit = false) { - Spawn(component.BehaviorProperties.BluespaceEffectPrototype, uid.ToCoordinates()); + if (!bypassLimit && Resolve(effectTargetUid, ref effectTargetComponent, false)) + if (effectTargetComponent!.BehaviorProperties.BluespaceEffectMinInterval > 0) + { + var curTimeTicks = _timing.CurTick.Value; + if (curTimeTicks < effectTargetComponent.BluespaceEffectNextTime) + return; + + effectTargetComponent.BluespaceEffectNextTime = curTimeTicks + (uint) (_timing.TickRate * effectTargetComponent.BehaviorProperties.BluespaceEffectMinInterval); + } + + Spawn(effectSourceComponent.BehaviorProperties.BluespaceEffectPrototype, effectTargetUid.ToCoordinates()); } private void PreOpen(EntityUid uid, BluespaceLockerComponent component, StorageBeforeOpenEvent args) { EntityStorageComponent? entityStorageComponent = null; + int transportedEntities = 0; if (!Resolve(uid, ref entityStorageComponent)) return; - component.CancelToken?.Cancel(); + if (component.CancelToken != null) + { + component.CancelToken.Cancel(); + component.CancelToken = null; + return; + } + + if (!component.BehaviorProperties.ActOnOpen) + return; // Select target var target = GetTarget(uid, component); @@ -74,11 +94,17 @@ public sealed class BluespaceLockerSystem : EntitySystem { if (EntityManager.HasComponent(entity)) { - if (component.BehaviorProperties.TransportSentient) - entityStorageComponent.Contents.Insert(entity, EntityManager); + if (!component.BehaviorProperties.TransportSentient) + continue; + + entityStorageComponent.Contents.Insert(entity, EntityManager); + transportedEntities++; } else if (component.BehaviorProperties.TransportEntities) + { entityStorageComponent.Contents.Insert(entity, EntityManager); + transportedEntities++; + } } // Move contained air @@ -90,17 +116,28 @@ public sealed class BluespaceLockerSystem : EntitySystem // Bluespace effects if (component.BehaviorProperties.BluespaceEffectOnTeleportSource) - BluespaceEffect(target.Value.uid, component); + BluespaceEffect(target.Value.uid, component, target.Value.bluespaceLockerComponent); if (component.BehaviorProperties.BluespaceEffectOnTeleportTarget) - BluespaceEffect(uid, component); + BluespaceEffect(uid, component, component); } - DestroyAfterLimit(uid, component); + DestroyAfterLimit(uid, component, transportedEntities); } - private bool ValidLink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent) + private bool ValidLink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent, bool intendToLink = false) { - return link.Valid && TryComp(link, out var linkStorage) && linkStorage.LifeStage != ComponentLifeStage.Deleted && link != locker; + if (!link.Valid || + !TryComp(link, out var linkStorage) || + linkStorage.LifeStage == ComponentLifeStage.Deleted || + link == locker) + return false; + + if (lockerComponent.BehaviorProperties.InvalidateOneWayLinks && + !(intendToLink && lockerComponent.AutoLinksBidirectional) && + !(HasComp(link) && Comp(link).BluespaceLinks.Contains(locker))) + return false; + + return true; } /// True if any HashSet in would grant access to @@ -120,15 +157,15 @@ public sealed class BluespaceLockerSystem : EntitySystem private bool ValidAutolink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent) { - if (!ValidLink(locker, link, lockerComponent)) + if (!ValidLink(locker, link, lockerComponent, true)) return false; if (lockerComponent.PickLinksFromSameMap && - link.ToCoordinates().GetMapId(_entityManager) != locker.ToCoordinates().GetMapId(_entityManager)) + link.ToCoordinates().GetMapId(EntityManager) != locker.ToCoordinates().GetMapId(EntityManager)) return false; if (lockerComponent.PickLinksFromStationGrids && - !HasComp(link.ToCoordinates().GetGridUid(_entityManager))) + !HasComp(link.ToCoordinates().GetGridUid(EntityManager))) return false; if (lockerComponent.PickLinksFromResistLockers && @@ -157,7 +194,7 @@ public sealed class BluespaceLockerSystem : EntitySystem return true; } - private (EntityUid uid, EntityStorageComponent storageComponent, BluespaceLockerComponent? bluespaceLockerComponent)? GetTarget(EntityUid lockerUid, BluespaceLockerComponent component) + public (EntityUid uid, EntityStorageComponent storageComponent, BluespaceLockerComponent? bluespaceLockerComponent)? GetTarget(EntityUid lockerUid, BluespaceLockerComponent component) { while (true) { @@ -179,13 +216,26 @@ public sealed class BluespaceLockerSystem : EntitySystem component.BluespaceLinks.Add(potentialLink); if (component.AutoLinksBidirectional || component.AutoLinksUseProperties) { - var targetBluespaceComponent = EnsureComp(potentialLink); + var targetBluespaceComponent = CompOrNull(potentialLink); - if (component.AutoLinksBidirectional) + if (targetBluespaceComponent == null) + { + using var compInitializeHandle = + EntityManager.AddComponentUninitialized(potentialLink); + targetBluespaceComponent = compInitializeHandle.Comp; + + if (component.AutoLinksBidirectional) + targetBluespaceComponent.BluespaceLinks.Add(lockerUid); + + if (component.AutoLinksUseProperties) + targetBluespaceComponent.BehaviorProperties = component.AutoLinkProperties with {}; + + compInitializeHandle.Dispose(); + } + else if (component.AutoLinksBidirectional) + { targetBluespaceComponent.BluespaceLinks.Add(lockerUid); - - if (component.AutoLinksUseProperties) - targetBluespaceComponent.BehaviorProperties = component.AutoLinkProperties with {}; + } } if (component.BluespaceLinks.Count >= component.MinBluespaceLinks) break; @@ -194,7 +244,12 @@ public sealed class BluespaceLockerSystem : EntitySystem // If there are no possible link targets and no links, return null if (component.BluespaceLinks.Count == 0) + { + if (component.MinBluespaceLinks == 0) + RemComp(lockerUid); + return null; + } // Attempt to select, validate, and return a link var links = component.BluespaceLinks.ToArray(); @@ -218,12 +273,16 @@ public sealed class BluespaceLockerSystem : EntitySystem private void PostClose(EntityUid uid, BluespaceLockerComponent component, bool doDelay = true) { EntityStorageComponent? entityStorageComponent = null; + int transportedEntities = 0; if (!Resolve(uid, ref entityStorageComponent)) return; component.CancelToken?.Cancel(); + if (!component.BehaviorProperties.ActOnClose) + return; + // Do delay if (doDelay && component.BehaviorProperties.Delay > 0) { @@ -249,11 +308,17 @@ public sealed class BluespaceLockerSystem : EntitySystem { if (EntityManager.HasComponent(entity)) { - if (component.BehaviorProperties.TransportSentient) - target.Value.storageComponent.Contents.Insert(entity, EntityManager); + if (!component.BehaviorProperties.TransportSentient) + continue; + + target.Value.storageComponent.Contents.Insert(entity, EntityManager); + transportedEntities++; } else if (component.BehaviorProperties.TransportEntities) + { target.Value.storageComponent.Contents.Insert(entity, EntityManager); + transportedEntities++; + } } // Move contained air @@ -287,20 +352,27 @@ public sealed class BluespaceLockerSystem : EntitySystem // Bluespace effects if (component.BehaviorProperties.BluespaceEffectOnTeleportSource) - BluespaceEffect(uid, component); + BluespaceEffect(uid, component, component); if (component.BehaviorProperties.BluespaceEffectOnTeleportTarget) - BluespaceEffect(target.Value.uid, component); + BluespaceEffect(target.Value.uid, component, target.Value.bluespaceLockerComponent); - DestroyAfterLimit(uid, component); + DestroyAfterLimit(uid, component, transportedEntities); } - private void DestroyAfterLimit(EntityUid uid, BluespaceLockerComponent component) + private void DestroyAfterLimit(EntityUid uid, BluespaceLockerComponent component, int transportedEntities) { + if (component.BehaviorProperties.DestroyAfterUsesMinItemsToCountUse > transportedEntities) + return; + if (component.BehaviorProperties.ClearLinksEvery != -1) { component.UsesSinceLinkClear++; - if (component.BehaviorProperties.ClearLinksEvery >= component.UsesSinceLinkClear) + if (component.BehaviorProperties.ClearLinksEvery <= component.UsesSinceLinkClear) { + if (component.BehaviorProperties.ClearLinksDebluespaces) + foreach (var link in component.BluespaceLinks) + RemComp(link); + component.BluespaceLinks.Clear(); component.UsesSinceLinkClear = 0; } @@ -316,12 +388,13 @@ public sealed class BluespaceLockerSystem : EntitySystem switch (component.BehaviorProperties.DestroyType) { case BluespaceLockerDestroyType.Explode: - _explosionSystem.QueueExplosion(uid.ToCoordinates().ToMap(_entityManager), + _explosionSystem.QueueExplosion(uid.ToCoordinates().ToMap(EntityManager), ExplosionSystem.DefaultExplosionPrototypeId, 4, 1, 2, maxTileBreak: 0); goto case BluespaceLockerDestroyType.Delete; case BluespaceLockerDestroyType.Delete: QueueDel(uid); break; + default: case BluespaceLockerDestroyType.DeleteComponent: RemComp(uid); break; diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index 8ae2f49625..66e669ef57 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -364,14 +364,18 @@ components: - type: BluespaceLocker minBluespaceLinks: 1 - bluespaceEffectOnInit: true behaviorProperties: - delay: 0.5 + delay: 1 + actOnOpen: false + bluespaceEffectOnInit: true bluespaceEffectOnTeleportSource: true bluespaceEffectOnTeleportTarget: true - destroyAfterUses: 2 + destroyAfterUses: 1 + destroyAfterUsesMinItemsToCountUse: 1 destroyType: Delete autoLinksUseProperties: true autoLinkProperties: + actOnOpen: false + actOnClose: false 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 9f7c5dfded..18b2a1ed3a 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml @@ -163,12 +163,15 @@ pickLinksFromSameMap: true minBluespaceLinks: 1 behaviorProperties: + clearLinksDebluespaces: true transportEntities: false bluespaceEffectOnTeleportSource: true clearLinksEvery: 2 autoLinksBidirectional: true autoLinksUseProperties: true + usesSinceLinkClear: -1 # hacky autoLinkProperties: + invalidateOneWayLinks: true transportEntities: false bluespaceEffectOnTeleportSource: true destroyAfterUses: 2 diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 1905f9f55e..99209d355d 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -492,6 +492,8 @@ public sealed class $CLASS$ : Shared$CLASS$ { True True True + True + True True True <data />