From: Fildrance Date: Mon, 26 May 2025 03:36:16 +0000 (+0300) Subject: Spray nozzle can suck puddles into tank directly! (#30600) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=291ccfbe23506679e44345ee1dab0070e14da47e;p=space-station-14.git Spray nozzle can suck puddles into tank directly! (#30600) * feat: now vacuum cleaner can suck solutions from floor * refactor using AbsorbentSystem instead of separate vacuum cleaner * refactor: remove unused vacuum cleaner files * refactor: renamed ConnectedContainerComponent to SlotBasedConnectedContainerComponent (and system) * fix: fix invalid comp name * fix: no more spray nozzle messaging about water inside bottles etc. * refactor: minor refactor in SlotBasedConnectedContainerSystem and adjustments after merge * refactor: cleanups * refactor: renaming * refactor: update to use _puddleSystem.GetAbsorbentReagents * refactor: changed interactions with SlotBasedConnectedContainerSystem into events * refactor: new sound and action delay adjusted to sound (amount tweaked a bit accordingly, almost) * refactor: added networking for SlotBasedConnectedContainerComponent * fix attribution for vacuum-cleaner-fast.ogg * trying to fix multi-license for mix sound file * remove empty line * refactor: remove trailing whitespace * by ref struct, brother --------- Co-authored-by: pa.pecherskij Co-authored-by: EmoGarbage404 --- diff --git a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs index 87ef41fe96..bf47768274 100644 --- a/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs +++ b/Content.IntegrationTests/Tests/Fluids/AbsorbentTest.cs @@ -33,6 +33,7 @@ public sealed class AbsorbentTest id: {AbsorbentDummyId} components: - type: Absorbent + useAbsorberSolution: true - type: SolutionContainerManager solutions: absorbed: @@ -94,7 +95,7 @@ public sealed class AbsorbentTest refillable = entityManager.SpawnEntity(RefillableDummyId, coordinates); entityManager.TryGetComponent(absorbent, out component); - solutionContainerSystem.TryGetSolution(absorbent, AbsorbentComponent.SolutionName, out var absorbentSoln, out var absorbentSolution); + solutionContainerSystem.TryGetSolution(absorbent, component.SolutionName, out var absorbentSoln, out var absorbentSolution); solutionContainerSystem.TryGetRefillableSolution(refillable, out var refillableSoln, out var refillableSolution); // Arrange @@ -152,7 +153,7 @@ public sealed class AbsorbentTest refillable = entityManager.SpawnEntity(SmallRefillableDummyId, coordinates); entityManager.TryGetComponent(absorbent, out component); - solutionContainerSystem.TryGetSolution(absorbent, AbsorbentComponent.SolutionName, out var absorbentSoln, out var absorbentSolution); + solutionContainerSystem.TryGetSolution(absorbent, component.SolutionName, out var absorbentSoln, out var absorbentSolution); solutionContainerSystem.TryGetRefillableSolution(refillable, out var refillableSoln, out var refillableSolution); // Arrange diff --git a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs index cc32d5a245..62303c2e35 100644 --- a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs @@ -91,7 +91,7 @@ public sealed class InjectorSystem : SharedInjectorSystem // Is the target a mob? If yes, use a do-after to give them time to respond. if (HasComp(target) || HasComp(target)) { - // Are use using an injector capible of targeting a mob? + // Are use using an injector capable of targeting a mob? if (entity.Comp.IgnoreMobs) return; diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index cb3cae10af..1177c24304 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -19,6 +19,8 @@ namespace Content.Server.Fluids.EntitySystems; /// public sealed class AbsorbentSystem : SharedAbsorbentSystem { + private static readonly EntProtoId Sparkles = "PuddleSparkle"; + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly PopupSystem _popups = default!; @@ -51,7 +53,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem private void UpdateAbsorbent(EntityUid uid, AbsorbentComponent component) { - if (!_solutionContainerSystem.TryGetSolution(uid, AbsorbentComponent.SolutionName, out _, out var solution)) + if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out _, out var solution)) return; var oldProgress = component.Progress.ShallowClone(); @@ -104,7 +106,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem public void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component) { - if (!_solutionContainerSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln)) + if (!_solutionContainerSystem.TryGetSolution(used, component.SolutionName, out var absorberSoln)) return; if (TryComp(used, out var useDelay) @@ -112,7 +114,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem return; // If it's a puddle try to grab from - if (!TryPuddleInteract(user, used, target, component, useDelay, absorberSoln.Value)) + if (!TryPuddleInteract(user, used, target, component, useDelay, absorberSoln.Value) && component.UseAbsorberSolution) { // If it's refillable try to transfer if (!TryRefillableInteract(user, used, target, component, useDelay, absorberSoln.Value)) @@ -282,36 +284,53 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem return true; } - // Check if we have any evaporative reagents on our absorber to transfer - var absorberSolution = absorberSoln.Comp.Solution; - var available = absorberSolution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(absorberSolution)); - - // No material - if (available == FixedPoint2.Zero) + Solution puddleSplit; + var isRemoved = false; + if (absorber.UseAbsorberSolution) { - _popups.PopupEntity(Loc.GetString("mopping-system-no-water", ("used", used)), user, user); - return true; - } + // Check if we have any evaporative reagents on our absorber to transfer + var absorberSolution = absorberSoln.Comp.Solution; + var available = absorberSolution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(absorberSolution)); + + // No material + if (available == FixedPoint2.Zero) + { + _popups.PopupEntity(Loc.GetString("mopping-system-no-water", ("used", used)), user, user); + return true; + } - var transferMax = absorber.PickupAmount; - var transferAmount = available > transferMax ? transferMax : available; + var transferMax = absorber.PickupAmount; + var transferAmount = available > transferMax ? transferMax : available; - var puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution)); - var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, _puddleSystem.GetAbsorbentReagents(absorberSolution)); + puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution)); + var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, _puddleSystem.GetAbsorbentReagents(absorberSolution)); - // Do tile reactions first - var transform = Transform(target); - var gridUid = transform.GridUid; - if (TryComp(gridUid, out MapGridComponent? mapGrid)) + // Do tile reactions first + var transform = Transform(target); + var gridUid = transform.GridUid; + if (TryComp(gridUid, out MapGridComponent? mapGrid)) + { + var tileRef = _mapSystem.GetTileRef(gridUid.Value, mapGrid, transform.Coordinates); + _puddleSystem.DoTileReactions(tileRef, absorberSplit); + } + _solutionContainerSystem.AddSolution(puddle.Solution.Value, absorberSplit); + } + else { - var tileRef = _mapSystem.GetTileRef(gridUid.Value, mapGrid, transform.Coordinates); - _puddleSystem.DoTileReactions(tileRef, absorberSplit); + puddleSplit = puddleSolution.SplitSolutionWithout(absorber.PickupAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution)); + // Despawn if we're done + if (puddleSolution.Volume == FixedPoint2.Zero) + { + // Spawn a *sparkle* + Spawn(Sparkles, GetEntityQuery().GetComponent(target).Coordinates); + QueueDel(target); + isRemoved = true; + } } - _solutionContainerSystem.AddSolution(puddle.Solution.Value, absorberSplit); _solutionContainerSystem.AddSolution(absorberSoln, puddleSplit); - _audio.PlayPvs(absorber.PickupSound, target); + _audio.PlayPvs(absorber.PickupSound, isRemoved ? used : target); if (useDelay != null) _useDelay.TryResetDelay((used, useDelay)); diff --git a/Content.Shared/Chemistry/Components/SlotBasedConnectedContainerComponent.cs b/Content.Shared/Chemistry/Components/SlotBasedConnectedContainerComponent.cs new file mode 100644 index 0000000000..2fde557941 --- /dev/null +++ b/Content.Shared/Chemistry/Components/SlotBasedConnectedContainerComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared.Containers; +using Content.Shared.Inventory; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Chemistry.Components; + +/// +/// Component for marking linked container in character slot, to which entity is bound. +/// +[RegisterComponent, Access(typeof(SlotBasedConnectedContainerSystem)), NetworkedComponent] +public sealed partial class SlotBasedConnectedContainerComponent : Component +{ + /// + /// The slot in which target container should be. + /// + [DataField(required: true)] + public SlotFlags TargetSlot; + + /// + /// A whitelist for determining whether container is valid or not . + /// + [DataField] + public EntityWhitelist? ContainerWhitelist; +} diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs index 99d1459340..f536beef2b 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Text; +using Content.Shared.Containers; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Robust.Shared.Map; @@ -162,6 +163,12 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem [NotNullWhen(true)] out Entity? entity, bool errorOnMissing = false) { + // use connected container instead of entity from arguments, if it exists. + var ev = new GetConnectedContainerEvent(); + RaiseLocalEvent(container, ref ev); + if (ev.ContainerEntity.HasValue) + container = ev.ContainerEntity.Value; + EntityUid uid; if (name is null) uid = container; diff --git a/Content.Shared/Containers/SlotBasedConnectedContainerSystem.cs b/Content.Shared/Containers/SlotBasedConnectedContainerSystem.cs new file mode 100644 index 0000000000..4970a54e33 --- /dev/null +++ b/Content.Shared/Containers/SlotBasedConnectedContainerSystem.cs @@ -0,0 +1,86 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Chemistry.Components; +using Content.Shared.Inventory; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; + +namespace Content.Shared.Containers; + +/// +/// System for getting container that is linked to subject entity. Container is supposed to be present in certain character slot. +/// Can be used for linking ammo feeder, solution source for spray nozzle, etc. +/// +public sealed class SlotBasedConnectedContainerSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnGettingConnectedContainer); + } + + /// + /// Try get connected container entity in character slots for . + /// + /// + /// Entity for which connected container is required. If + /// is used - tries to find container in slot, returns false and null otherwise. + /// + /// Found connected container entity or null. + /// True if connected container was found, false otherwise. + public bool TryGetConnectedContainer(EntityUid uid, [NotNullWhen(true)] out EntityUid? slotEntity) + { + if (!TryComp(uid, out var component)) + { + slotEntity = null; + return false; + } + + return TryGetConnectedContainer(uid, component.TargetSlot, component.ContainerWhitelist, out slotEntity); + } + + private void OnGettingConnectedContainer(Entity ent, ref GetConnectedContainerEvent args) + { + if (TryGetConnectedContainer(ent, ent.Comp.TargetSlot, ent.Comp.ContainerWhitelist, out var val)) + args.ContainerEntity = val; + } + + private bool TryGetConnectedContainer(EntityUid uid, SlotFlags slotFlag, EntityWhitelist? providerWhitelist, [NotNullWhen(true)] out EntityUid? slotEntity) + { + slotEntity = null; + + if (!_containers.TryGetContainingContainer((uid, null, null), out var container)) + return false; + + var user = container.Owner; + if (!_inventory.TryGetContainerSlotEnumerator(user, out var enumerator, slotFlag)) + return false; + + while (enumerator.NextItem(out var item)) + { + if (_whitelistSystem.IsWhitelistFailOrNull(providerWhitelist, item)) + continue; + + slotEntity = item; + return true; + } + + return false; + } +} + +/// +/// Event for an attempt of getting container, connected to entity on which event was raised. +/// Fills if connected container exists. +/// +[ByRefEvent] +public struct GetConnectedContainerEvent +{ + /// + /// Container entity, if it exists, or null. + /// + public EntityUid? ContainerEntity; +} diff --git a/Content.Shared/Fluids/AbsorbentComponent.cs b/Content.Shared/Fluids/AbsorbentComponent.cs index 450ecc0df6..2d1a922381 100644 --- a/Content.Shared/Fluids/AbsorbentComponent.cs +++ b/Content.Shared/Fluids/AbsorbentComponent.cs @@ -11,23 +11,28 @@ namespace Content.Shared.Fluids; [RegisterComponent, NetworkedComponent] public sealed partial class AbsorbentComponent : Component { - public const string SolutionName = "absorbed"; - public Dictionary Progress = new(); + /// + /// Name for solution container, that should be used for absorbed solution storage and as source of absorber solution. + /// Default is 'absorbed'. + /// + [DataField] + public string SolutionName = "absorbed"; + /// /// How much solution we can transfer in one interaction. /// - [DataField("pickupAmount")] + [DataField] public FixedPoint2 PickupAmount = FixedPoint2.New(100); - [DataField("pickupSound")] + [DataField] public SoundSpecifier PickupSound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg") { Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation), }; - [DataField("transferSound")] public SoundSpecifier TransferSound = + [DataField] public SoundSpecifier TransferSound = new SoundPathSpecifier("/Audio/Effects/Fluids/slosh.ogg") { Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(-3f), @@ -38,4 +43,11 @@ public sealed partial class AbsorbentComponent : Component { Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(-3f), }; + + /// + /// Marker that absorbent component owner should try to use 'absorber solution' to replace solution to be absorbed. + /// Target solution will be simply consumed into container if set to false. + /// + [DataField] + public bool UseAbsorberSolution = true; } diff --git a/Content.Shared/Weapons/Ranged/Components/AmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/AmmoProviderComponent.cs index 9a7fdc07de..8e2f767f4d 100644 --- a/Content.Shared/Weapons/Ranged/Components/AmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/AmmoProviderComponent.cs @@ -3,4 +3,4 @@ using Robust.Shared.GameStates; namespace Content.Shared.Weapons.Ranged.Components; [NetworkedComponent] -public abstract partial class AmmoProviderComponent : Component {} +public abstract partial class AmmoProviderComponent : Component; diff --git a/Content.Shared/Weapons/Ranged/Components/ClothingSlotAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/ClothingSlotAmmoProviderComponent.cs index e363fc9fe1..418e712c7b 100644 --- a/Content.Shared/Weapons/Ranged/Components/ClothingSlotAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/ClothingSlotAmmoProviderComponent.cs @@ -1,6 +1,4 @@ -using Content.Shared.Inventory; using Content.Shared.Weapons.Ranged.Systems; -using Content.Shared.Whitelist; using Robust.Shared.GameStates; namespace Content.Shared.Weapons.Ranged.Components; @@ -10,17 +8,4 @@ namespace Content.Shared.Weapons.Ranged.Components; /// to an entity in the user's clothing slot. /// [RegisterComponent, NetworkedComponent, Access(typeof(SharedGunSystem))] -public sealed partial class ClothingSlotAmmoProviderComponent : AmmoProviderComponent -{ - /// - /// The slot that the ammo provider should be located in. - /// - [DataField("targetSlot", required: true)] - public SlotFlags TargetSlot; - - /// - /// A whitelist for determining whether or not an ammo provider is valid. - /// - [DataField("providerWhitelist")] - public EntityWhitelist? ProviderWhitelist; -} +public sealed partial class ClothingSlotAmmoProviderComponent : AmmoProviderComponent; diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs index 7ef57df539..12bbe0c312 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Clothing.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Shared.Inventory; +using Content.Shared.Containers; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; @@ -7,8 +6,6 @@ namespace Content.Shared.Weapons.Ranged.Systems; public partial class SharedGunSystem { - [Dependency] private readonly InventorySystem _inventory = default!; - private void InitializeClothing() { SubscribeLocalEvent(OnClothingTakeAmmo); @@ -17,38 +14,21 @@ public partial class SharedGunSystem private void OnClothingTakeAmmo(EntityUid uid, ClothingSlotAmmoProviderComponent component, TakeAmmoEvent args) { - if (!TryGetClothingSlotEntity(uid, component, out var entity)) + var getConnectedContainerEvent = new GetConnectedContainerEvent(); + RaiseLocalEvent(uid, ref getConnectedContainerEvent); + if(!getConnectedContainerEvent.ContainerEntity.HasValue) return; - RaiseLocalEvent(entity.Value, args); + + RaiseLocalEvent(getConnectedContainerEvent.ContainerEntity.Value, args); } private void OnClothingAmmoCount(EntityUid uid, ClothingSlotAmmoProviderComponent component, ref GetAmmoCountEvent args) { - if (!TryGetClothingSlotEntity(uid, component, out var entity)) + var getConnectedContainerEvent = new GetConnectedContainerEvent(); + RaiseLocalEvent(uid, ref getConnectedContainerEvent); + if (!getConnectedContainerEvent.ContainerEntity.HasValue) return; - RaiseLocalEvent(entity.Value, ref args); - } - - private bool TryGetClothingSlotEntity(EntityUid uid, ClothingSlotAmmoProviderComponent component, [NotNullWhen(true)] out EntityUid? slotEntity) - { - slotEntity = null; - - if (!Containers.TryGetContainingContainer((uid, null, null), out var container)) - return false; - var user = container.Owner; - - if (!_inventory.TryGetContainerSlotEnumerator(user, out var enumerator, component.TargetSlot)) - return false; - - while (enumerator.NextItem(out var item)) - { - if (_whitelistSystem.IsWhitelistFailOrNull(component.ProviderWhitelist, item)) - continue; - - slotEntity = item; - return true; - } - return false; + RaiseLocalEvent(getConnectedContainerEvent.ContainerEntity.Value, ref args); } } diff --git a/Resources/Audio/Effects/Fluids/attributions.yml b/Resources/Audio/Effects/Fluids/attributions.yml index aebe3e3c3f..c23a5f5c7f 100644 --- a/Resources/Audio/Effects/Fluids/attributions.yml +++ b/Resources/Audio/Effects/Fluids/attributions.yml @@ -22,3 +22,13 @@ license: "CC-BY-SA-3.0" copyright: "Created by the_toilet_guy" source: "https://freesound.org/people/the_toilet_guy/sounds/98770/" + +- files: ["vacuum-cleaner-fast.ogg"] + license: "CC0-1.0" + copyright: "Created by kyles" + source: "https://freesound.org/people/kyles/sounds/637927/" + +- files: ["vacuum-cleaner-fast.ogg"] + license: "CC0-1.0" + copyright: "Created by BrendanSound12 mixed by Fildrance" + source: "https://freesound.org/people/BrendanSound12/sounds/445165/" diff --git a/Resources/Audio/Effects/Fluids/vacuum-cleaner-fast.ogg b/Resources/Audio/Effects/Fluids/vacuum-cleaner-fast.ogg new file mode 100644 index 0000000000..bf20520b23 Binary files /dev/null and b/Resources/Audio/Effects/Fluids/vacuum-cleaner-fast.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index 6a7234b810..e09ba46e12 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -34,6 +34,7 @@ size: Large sprite: Objects/Specific/Janitorial/mop.rsi - type: Absorbent + useAbsorberSolution: true - type: SolutionContainerManager solutions: absorbed: @@ -89,6 +90,7 @@ sprite: Objects/Specific/Janitorial/advmop.rsi - type: Absorbent pickupAmount: 100 + useAbsorberSolution: true - type: UseDelay delay: 1.0 - type: SolutionRegeneration @@ -322,6 +324,7 @@ sprite: Objects/Specific/Janitorial/rag.rsi - type: Absorbent pickupAmount: 15 + useAbsorberSolution: true - type: Construction graph: Rag node: rag diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/spraynozzle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/spraynozzle.yml index 616a799aff..b6e48f701b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/spraynozzle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/spraynozzle.yml @@ -21,9 +21,18 @@ - FullAuto soundGunshot: path: /Audio/Weapons/Guns/Gunshots/water_spray.ogg + - type: Absorbent + pickupAmount: 35 + solutionName: tank + useAbsorberSolution: false + pickupSound: + path: /Audio/Effects/Fluids/vacuum-cleaner-fast.ogg - type: Appearance - type: ClothingSlotAmmoProvider + - type: SlotBasedConnectedContainer targetSlot: BACK - providerWhitelist: + containerWhitelist: tags: - NozzleBackTank + - type: UseDelay + delay: 1.0