Repository: Simple-Station/Einstein-Engines
Pull request #1958: Fillbot
Author: login: Timfa2112
Labels: name: Changes: C#
description: Changes any cs files
name: Changes: Localization
description: Changes any ftl files
name: Changes: YML
description: Changes any yml files
name: Changes: Sprite
description: Changes any png or json in an RSI
name: Status: Needs Review
description: Someone please review this
Created: 2025-03-14T01:00:58Z | Merged: 2025-03-20T22:13:34Z

<!--
This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same
Remove these comments before submitting
-->

# Description

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

The humble fillbot! Takes items from the ground and inserts them into a lathe, silo or disposal unit it's linked to!

---

# TODO

<!--
A list of everything you have to do before this PR is "complete"
You probably won't have to complete everything before merging but it's good to leave future references
-->

- [x] Make the robot pick up items
- [x] Make the robot insert items
- [x] Make the robot differentiate between trash inputs
- [ ] Make the robot only linkable to the correct ports

---

<!--
This is default collapsed, readers click to expand it and see all your media
The PR media section can get very large at times, so this is a good way to keep it clean
The title is written using HTML tags
The title must be within the <summary> tags or you won't see it
-->

<details><summary><h1>Media</h1></summary>
<p>

https://github.com/user-attachments/assets/f0cd26c0-36fb-43e2-8bdb-c100f033668b


https://github.com/user-attachments/assets/3809b033-01a6-4b4f-96e0-f4730dd2e92d


https://github.com/user-attachments/assets/ccf325f3-ee60-42f8-9e68-75fafe596a7b



</p>
</details>

---

# Changelog

<!--
You can add an author after the `:cl:` to change the name that appears in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl:
- add: Added Fillbot. Link it to a lathe, material silo, ore processor or disposal unit with a multitool and watch it gather stuff!



Diff:
diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
new file mode 100644
index 00000000000..b9a9180df8f
--- /dev/null
+++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
@@ -0,0 +1,77 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out MaterialStorageComponent? linkedStorage);
+        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out DisposalUnitComponent? disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (linkedStorage is not null
+            && _sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity!.Value))
+            return HTNOperatorStatus.Finished;
+
+        else if (disposalUnit is not null)
+        {
+            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity!.Value, heldItem.Value, owner);
+            return HTNOperatorStatus.Finished;
+        }
+
+        _sharedHandsSystem.TryDrop(owner);
+        return HTNOperatorStatus.Failed;
+    }
+}
diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs
new file mode 100644
index 00000000000..9f9d9f32661
--- /dev/null
+++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs
@@ -0,0 +1,60 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Interaction;
+using Content.Shared.Silicons.Bots;
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FindLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private PathfindingSystem _pathfinding = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to find
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var _, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return (false, null);
+
+        var path = await _pathfinding.GetPath(owner, fillbot.LinkedSinkEntity.Value, SharedInteractionSystem.InteractionRange - 0.5f, cancelToken);
+
+        if (path.Result == PathResult.NoPath)
+            return (false, null);
+
+        return (true, new()
+        {
+            {TargetKey, fillbot.LinkedSinkEntity},
+            {TargetMoveKey, _entManager.GetComponent<TransformComponent>(fillbot.LinkedSinkEntity.Value).Coordinates},
+            {NPCBlackboard.PathfindKey, path},
+        });
+    }
+}
diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
new file mode 100644
index 00000000000..5b8e7703bae
--- /dev/null
+++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
@@ -0,0 +1,107 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// Target entity to pick up
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// Target entitycoordinates to move to.
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    /// Target tag prototype to look for when trying to find trash.
+    [DataField]
+    public ProtoId<TagPrototype> TrashProto = "Trash";
+
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out MaterialStorageComponent? linkedStorage);
+        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out DisposalUnitComponent? disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity!.Value))
+                continue;
+
+            // trash only
+            if (disposalUnit != null &&
+                (_whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
+                    || !_tagSystem.HasTag(target, TrashProto)
+                    || _entManager.HasComponent<BodyPartComponent>(target))) // Robot is unable to insert bodyparts into Disposals for some reason
+                continue;
+
+            const float pathRange = SharedInteractionSystem.InteractionRange - 1;
+            var path = await _pathfinding.GetPath(owner, target, pathRange, cancelToken);
+
+            if (path.Result == PathResult.NoPath)
+                continue;
+
+            return (true, new()
+            {
+                {TargetKey, target},
+                {TargetMoveKey, _entManager.GetComponent<TransformComponent>(target).Coordinates},
+                {NPCBlackboard.PathfindKey, path},
+            });
+        }
+
+        return (false, null);
+    }
+}
diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs
index 6b6ae9b069c..ea92a5ac56d 100644
--- a/Content.Server/NPC/NPCBlackboard.cs
+++ b/Content.Server/NPC/NPCBlackboard.cs
@@ -27,6 +27,7 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
         {"MaximumIdleTime", 7f},
         {MedibotInjectRange, 4f},
         {WeldbotWeldRange, 4f},
+        {FillbotPickupRange, 10f},
         {PlantbotServiceRange, 4f},
         {MeleeMissChance, 0.3f},
         {"MeleeRange", 1f},
@@ -295,6 +296,7 @@ public string GetVisionRadiusKey(IEntityManager entMan)
     public const string Inventory = "Inventory";
     public const string MedibotInjectRange = "MedibotInjectRange";
     public const string WeldbotWeldRange = "WeldbotWeldRange";
+    public const string FillbotPickupRange = "FillbotPickupRange";
     public const string PlantbotServiceRange = "PlantbotServiceRange";
 
     public const string MeleeMissChance = "MeleeMissChance";
diff --git a/Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs b/Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs
index 680b0c8453e..9d10bbeab15 100644
--- a/Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs
+++ b/Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs
@@ -3,10 +3,12 @@ namespace Content.Shared.DeviceLinking.Events
     public sealed class PortDisconnectedEvent : EntityEventArgs
     {
         public readonly string Port;
+        public readonly EntityUid RemovedPortUid;
 
-        public PortDisconnectedEvent(string port)
+        public PortDisconnectedEvent(string port, EntityUid removedPortUid)
         {
             Port = port;
+            RemovedPortUid = removedPortUid;
         }
     }
 }
diff --git a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
index 79a32268e80..3d5900b21e1 100644
--- a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
+++ b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Database;
 using Content.Shared.DeviceLinking.Events;
@@ -362,6 +363,20 @@ public void RemoveAllFromSink(EntityUid sinkUid, DeviceLinkSinkComponent? sinkCo
         }
     }
 
+    /// <summary>
+    /// Removes every link from the given sink
+    /// </summary>
+    public void RemoveAllFromSource(EntityUid sourceUid, DeviceLinkSourceComponent? sourceComponent = null, Predicate<EntityUid>? filter = null)
+    {
+        if (!Resolve(sourceUid, ref sourceComponent))
+            return;
+
+        foreach (var sinkUid in sourceComponent.LinkedPorts.Where(sinkUid => filter == null || filter.Invoke(sinkUid.Key)))
+        {
+            RemoveSinkFromSource(sourceUid, sinkUid.Key, sourceComponent);
+        }
+    }
+
     /// <summary>
     /// Removes all links between a source and a sink
     /// </summary>
@@ -391,7 +406,7 @@ public void RemoveSinkFromSource(
         else
         {
             Log.Error($"Attempted to remove link between {ToPrettyString(sourceUid)} and {ToPrettyString(sinkUid)}, but the sink component was missing.");
-            sourceComponent.LinkedPorts.Remove(sourceUid);
+            sourceComponent.LinkedPorts.Remove(sinkUid);
         }
     }
 
@@ -407,8 +422,8 @@ private void RemoveSinkFromSourceInternal(
         {
             foreach (var (sourcePort, sinkPort) in ports)
             {
-                RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(sourcePort));
-                RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sinkPort));
+                RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(sourcePort, sinkUid));
+                RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sinkPort, sourceUid));
             }
         }
 
@@ -451,8 +466,8 @@ public bool ToggleLink(
             else
                 _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, $"unlinked {ToPrettyString(sourceUid):source} {source} and {ToPrettyString(sinkUid):sink} {sink}");
 
-            RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(source));
-            RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sink));
+            RaiseLocalEvent(sourceUid, new PortDisconnectedEvent(source, sinkUid));
+            RaiseLocalEvent(sinkUid, new PortDisconnectedEvent(sink, sourceUid));
 
             outputs.Remove(sinkUid);
             linkedPorts.Remove((source, sink));
diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs
index c2ebc1d98ae..aae3a7f3877 100644
--- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs
+++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs
@@ -269,6 +269,35 @@ public bool TrySetMaterialAmount(
         return TryChangeMaterialAmount(uid, materialId, delta, component, utilizer);
     }
 
+    public virtual bool CanInsertMaterialEntity(
+        EntityUid toInsert,
+        EntityUid receiver,
+        MaterialStorageComponent? storage = null,
+        MaterialSiloUtilizerComponent? utilizer = null,
+        MaterialComponent? material = null,
+        PhysicalCompositionComponent? composition = null
+    )
+    {
+        if (!Resolve(receiver, ref storage)
+            || !Resolve(toInsert, ref material, ref composition, false)
+            || _whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert)
+            || HasComp<UnremoveableComponent>(toInsert))
+            return false;
+
+        // Material Whitelist checked implicitly by CanChangeMaterialAmount();
+
+        var multiplier = TryComp<StackComponent>(toInsert, out var stackComponent) ? stackComponent.Count : 1;
+        var totalVolume = 0;
+        foreach (var (mat, vol) in composition.MaterialComposition)
+        {
+            if (!CanChangeMaterialAmount(receiver, mat, vol * multiplier, storage, utilizer))
+                return false;
+            totalVolume += vol * multiplier;
+        }
+
+        return CanTakeVolume(receiver, totalVolume, storage, utilizer);
+    }
+
     /// <summary>
     /// Tries to insert an entity into the material storage.
     /// </summary>
diff --git a/Content.Shared/Silicons/Bots/FillbotComponent.cs b/Content.Shared/Silicons/Bots/FillbotComponent.cs
new file mode 100644
index 00000000000..5a8c0881ec2
--- /dev/null
+++ b/Content.Shared/Silicons/Bots/FillbotComponent.cs
@@ -0,0 +1,9 @@
+namespace Content.Shared.Silicons.Bots;
+
+[RegisterComponent]
+[Access(typeof(FillbotSystem))]
+public sealed partial class FillbotComponent : Component
+{
+    [ViewVariables]
+    public EntityUid? LinkedSinkEntity { get; set; }
+}
diff --git a/Content.Shared/Silicons/Bots/FillbotSystem.cs b/Content.Shared/Silicons/Bots/FillbotSystem.cs
new file mode 100644
index 00000000000..2348980eaf5
--- /dev/null
+++ b/Content.Shared/Silicons/Bots/FillbotSystem.cs
@@ -0,0 +1,40 @@
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Hands.EntitySystems;
+
+namespace Content.Shared.Silicons.Bots;
+
+public sealed class FillbotSystem : EntitySystem
+{
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+    [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
+
+    [Dependency] private SharedHandsSystem _sharedHandsSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<FillbotComponent, NewLinkEvent>(OnLinked);
+        SubscribeLocalEvent<FillbotComponent, PortDisconnectedEvent>(OnUnlinked);
+    }
+
+    // The bot can only be linked to one thing at a time, or it'll freak out.
+    private void OnLinked(EntityUid uid, FillbotComponent comp, ref NewLinkEvent args)
+    {
+        _sharedHandsSystem.TryDrop(uid);
+        var newSink = args.Sink;
+        var deviceLinkSourceComponent = _entityManager.GetComponent<DeviceLinkSourceComponent>(uid);
+        _deviceLink.RemoveAllFromSource(uid, deviceLinkSourceComponent, o => o != newSink);
+        comp.LinkedSinkEntity = newSink;
+    }
+
+    private void OnUnlinked(EntityUid uid, FillbotComponent comp, ref PortDisconnectedEvent args)
+    {
+        _sharedHandsSystem.TryDrop(uid);
+        var newSink = args.RemovedPortUid;
+
+        if (comp.LinkedSinkEntity == newSink)
+            comp.LinkedSinkEntity = null;
+    }
+}
diff --git a/Content.Shared/Tag/TagSystem.cs b/Content.Shared/Tag/TagSystem.cs
index f1f620a6949..55641dbed98 100644
--- a/Content.Shared/Tag/TagSystem.cs
+++ b/Content.Shared/Tag/TagSystem.cs
@@ -17,6 +17,7 @@ namespace Content.Shared.Tag;
 public sealed class TagSystem : EntitySystem
 {
     [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly IEntityManager _entityManager = default!;
 
     private EntityQuery<TagComponent> _tagQuery;
 
@@ -270,7 +271,7 @@ public bool HasAnyTag(EntityUid entityUid, HashSet<ProtoId<TagPrototype>> tags)
     /// </exception>
     public bool HasAnyTag(EntityUid entityUid, List<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entityUid, out var component) &&
+        return _entityManager.TryGetComponent(entityUid, out TagComponent? component) &&
                HasAnyTag(component, tags);
     }
 
diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl
index 7ef730d6ae8..8acc3c39b50 100644
--- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl
+++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl
@@ -77,6 +77,7 @@ petting-success-station-ai = You pet {THE($target)} on {POSS-ADJ($target)} cold,
 petting-success-gladiabot = You pet {THE($target)} on {POSS-ADJ($target)} vicious cardboard head.
 petting-success-batonbot = You pet {THE($target)} on {POSS-ADJ($target)} protective metal head.
 petting-success-disablerbot = You pet {THE($target)} on {POSS-ADJ($target)} protective metal head.
+petting-success-fillbot = You pet {THE($target)} on {POSS-ADJ($target)} diligent metal head.
 
 petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} honks in refusal!
 petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy mopping!
@@ -97,6 +98,7 @@ petting-failure-station-ai = You reach out to pet {THE($target)}, but {SUBJECT($
 petting-failure-gladiabot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} only wants to fight!
 petting-failure-batonbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} nearly prods you with its baton!
 petting-failure-disablerbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} angrily waves its weapon at you!
+petting-failure-fillbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} pushes your hand away!
 
 petting-success-station-ai-others = { CAPITALIZE(THE($user)) } pets {THE($target)} on {POSS-ADJ($target)} cold, square screen.
 
diff --git a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl
index f9eed41c48a..87fb56a82b5 100644
--- a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl
+++ b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl
@@ -85,3 +85,5 @@ signal-port-description-logic-input = Input to the edge detector, cannot be a pu
 signal-port-name-material-silo-utilizer = Silo utilizer
 signal-port-description-material-silo-utilizer = Utilizer for station material silo
 
+signal-port-name-fill-any-item = Fillbot Target
+signal-port-description-fill-any-item = Target port for Fillbot insertion.
diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
index 581c632d6d6..1b74ac9d86b 100644
--- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
+++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
@@ -72,3 +72,6 @@ signal-port-description-power-discharging = This port is invoked with HIGH when
 
 signal-port-name-material-silo = Material silo
 signal-port-description-material-silo = Bluespace storage for station materials
+
+signal-port-name-fillitems = Fillbot Items
+signal-port-description-fillitems = Items picked up by the Fillbot.
diff --git a/Resources/Prototypes/DeviceLinking/sink_ports.yml b/Resources/Prototypes/DeviceLinking/sink_ports.yml
index 30343386acb..2646177f36c 100644
--- a/Resources/Prototypes/DeviceLinking/sink_ports.yml
+++ b/Resources/Prototypes/DeviceLinking/sink_ports.yml
@@ -118,3 +118,7 @@
   name: signal-port-name-material-silo-utilizer
   description: signal-port-description-material-silo-utilizer
 
+- type: sinkPort
+  id: FillbotAnyItem # used by Fillbot
+  name: signal-port-name-fill-any-item
+  description: signal-port-description-fill-any-item
diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml
index 24f1c455e23..6ca59a1e6ba 100644
--- a/Resources/Prototypes/DeviceLinking/source_ports.yml
+++ b/Resources/Prototypes/DeviceLinking/source_ports.yml
@@ -159,4 +159,10 @@
   id: MaterialSilo
   name: signal-port-name-material-silo
   description: signal-port-description-material-silo
-  defaultLinks: [ MaterialSiloUtilizer ]
\ No newline at end of file
+  defaultLinks: [ MaterialSiloUtilizer ]
+
+- type: sourcePort
+  id: FillItems # Used by Fillbot
+  name: signal-port-name-fillitems
+  description: signal-port-description-fillitems
+  defaultLinks: [ FillbotAnyItem ]
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/fillbot.yml b/Resources/Prototypes/Entities/Mobs/NPCs/fillbot.yml
new file mode 100644
index 00000000000..a0c6089dc17
--- /dev/null
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/fillbot.yml
@@ -0,0 +1,40 @@
+- type: entity
+  parent: MobSiliconBase
+  id: MobFillBot
+  name: fillbot
+  description: It picks up things and puts them somewhere else.
+  components:
+  - type: Fillbot
+  - type: Sprite
+    sprite: Mobs/Silicon/Bots/fillbot.rsi
+    state: fillbot
+  - type: Construction
+    graph: FillBot
+    node: bot
+  - type: SentienceTarget
+    flavorKind: station-event-random-sentience-flavor-mechanical
+  - type: MovementSpeedModifier
+    baseWalkSpeed: 2
+    baseSprintSpeed: 3
+  - type: NoSlip
+  - type: UserInterface
+    interfaces:
+      enum.StrippingUiKey.Key:
+        type: StrippableBoundUserInterface
+  - type: Hands
+    showInHands: false
+  - type: ComplexInteraction
+  - type: Body
+    prototype: SiliconHand
+    requiredLegs: 0
+  - type: HTN
+    rootTask:
+      task: FillbotCompound
+  - type: DeviceLinkSource
+    ports:
+      - FillItems
+  - type: InteractionPopup
+    interactSuccessString: petting-success-fillbot
+    interactFailureString: petting-failure-fillbot
+    interactSuccessSound:
+      path: /Audio/Ambience/Objects/periodic_beep.ogg
diff --git a/Resources/Prototypes/Entities/Structures/Machines/material_silo.yml b/Resources/Prototypes/Entities/Structures/Machines/material_silo.yml
index 2b001b32ec8..5e0b4df3531 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/material_silo.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/material_silo.yml
@@ -30,6 +30,9 @@
   - type: MaterialStorage
     ignoreColor: true
   - type: MaterialSilo
+  - type: DeviceLinkSink
+    ports:
+      - FillbotAnyItem
 
 - type: entity
   id: BaseMaterialSiloUtilizer
@@ -41,5 +44,6 @@
     range: 1000
   - type: DeviceLinkSink
     ports:
+      - FillbotAnyItem
       - MaterialSiloUtilizer
   - type: MaterialSiloUtilizer
diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
index 1bbbe46b888..3df11dd82af 100644
--- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
+++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
@@ -65,6 +65,7 @@
     price: 62
   - type: PowerSwitch
 
+
 - type: entity
   id: DisposalUnit
   parent: DisposalUnitBase
@@ -88,6 +89,9 @@
         type: DisposalUnitBoundUserInterface
   - type: RatKingRummageable
   - type: RequireProjectileTarget
+  - type: DeviceLinkSink
+    ports:
+      - FillbotAnyItem
 
 - type: entity
   id: MailingUnit
@@ -139,3 +143,6 @@
         type: DisposalUnitBoundUserInterface
       enum.ConfigurationUiKey.Key:
         type: ConfigurationBoundUserInterface
+  - type: DeviceLinkSink
+    ports:
+      - FillbotAnyItem
diff --git a/Resources/Prototypes/NPCs/fillbot.yml b/Resources/Prototypes/NPCs/fillbot.yml
new file mode 100644
index 00000000000..dcc64f57df5
--- /dev/null
+++ b/Resources/Prototypes/NPCs/fillbot.yml
@@ -0,0 +1,50 @@
+- type: htnCompound
+  id: FillbotCompound
+  branches:
+    - tasks:
+        - !type:HTNCompoundTask
+          task: FillMachineCompound
+
+# Picks an object depending on what machine the bot is linked to, and puts it in the machine it's linked to
+- type: htnCompound
+  id: FillMachineCompound
+  branches:
+    - tasks:
+        # Find an item that will fit into the machine
+        - !type:HTNPrimitiveTask
+          operator: !type:PickNearbyFillableItemOperator
+            targetKey: NearbyFillableItem
+            targetMoveKey: TargetCoordinates
+        # Move in range
+        - !type:HTNPrimitiveTask
+          preconditions:
+            - !type:KeyExistsPrecondition
+              key: NearbyFillableItem
+          operator: !type:MoveToOperator
+            pathfindInPlanning: false
+        # Pick it up
+        - !type:HTNPrimitiveTask
+          preconditions:
+            - !type:TargetInRangePrecondition
+              targetKey: NearbyFillableItem
+              rangeKey: InteractRange
+            - !type:KeyExistsPrecondition
+              key: NearbyFillableItem
+          operator: !type:InteractWithOperator
+            targetKey: NearbyFillableItem
+        # Find the machine the bot is linked to
+        - !type:HTNPrimitiveTask
+          operator: !type:FindLinkedMachineOperator
+            targetKey: LinkedFillableMachine
+            targetMoveKey: TargetCoordinates
+        # Move to the machine
+        - !type:HTNPrimitiveTask
+          preconditions:
+            - !type:KeyExistsPrecondition
+              key: LinkedFillableMachine
+          operator: !type:MoveToOperator
+            pathfindInPlanning: false
+        # Insert the item
+        - !type:HTNPrimitiveTask
+          operator: !type:FillLinkedMachineOperator
+            targetKey: LinkedFillableMachine
diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/bots/fillbot.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/bots/fillbot.yml
new file mode 100644
index 00000000000..108ecd2a1a8
--- /dev/null
+++ b/Resources/Prototypes/Recipes/Crafting/Graphs/bots/fillbot.yml
@@ -0,0 +1,24 @@
+- type: constructionGraph
+  id: FillBot
+  start: start
+  graph:
+  - node: start
+    edges:
+    - to: bot
+      steps:
+      - material: Plastic
+        amount: 10
+      - tag: ProximitySensor
+        icon:
+          sprite: Objects/Misc/proximity_sensor.rsi
+          state: icon
+        name: proximity sensor
+        doAfter: 2
+      - tag: BorgArm
+        icon:
+          sprite: Mobs/Silicon/drone.rsi
+          state: l_hand
+        name: borg arm
+        doAfter: 2
+  - node: bot
+    entity: MobFillBot
diff --git a/Resources/Prototypes/Recipes/Crafting/bots.yml b/Resources/Prototypes/Recipes/Crafting/bots.yml
index 332b8dfbba9..c0ebdf677c6 100644
--- a/Resources/Prototypes/Recipes/Crafting/bots.yml
+++ b/Resources/Prototypes/Recipes/Crafting/bots.yml
@@ -140,3 +140,16 @@
   icon:
     sprite: Mobs/Silicon/Bots/plantbot.rsi
     state: plantbot
+
+- type: construction
+  name: fillbot
+  id: fillbot
+  graph: FillBot
+  startNode: start
+  targetNode: bot
+  category: construction-category-utilities
+  objectType: Item
+  description: This bot is linked to a machine that items can be inserted into using a multitool.
+  icon:
+    sprite: Mobs/Silicon/Bots/fillbot.rsi
+    state: fillbot
diff --git a/Resources/Prototypes/_Shitmed/Body/Organs/silicon.yml b/Resources/Prototypes/_Shitmed/Body/Organs/silicon.yml
new file mode 100644
index 00000000000..b5de5ad8897
--- /dev/null
+++ b/Resources/Prototypes/_Shitmed/Body/Organs/silicon.yml
@@ -0,0 +1,23 @@
+- type: entity
+  id: RightArmSiliconIndestructible
+  name: "right silicon arm"
+  parent: [BaseItem, BaseRightArm]
+  components:
+    - type: Damageable
+    - type: BodyPart
+      partType: Arm
+      symmetry: Right
+      toolName: "a right arm" # Shitmed Change
+      canSever: false
+
+- type: entity
+  id: RightHandSiliconIndestructible
+  name: "right silicon hand"
+  parent: [BaseItem, BaseRightArm]
+  components:
+    - type: Damageable
+    - type: BodyPart
+      partType: Hand
+      symmetry: Right
+      toolName: "a right hand" # Shitmed Change
+      canSever: false
diff --git a/Resources/Prototypes/_Shitmed/Body/Prototypes/silicon/siliconhand.yml b/Resources/Prototypes/_Shitmed/Body/Prototypes/silicon/siliconhand.yml
new file mode 100644
index 00000000000..c545a138075
--- /dev/null
+++ b/Resources/Prototypes/_Shitmed/Body/Prototypes/silicon/siliconhand.yml
@@ -0,0 +1,12 @@
+# It's a silicon that has a hand. And nothing else.
+- type: body
+  id: SiliconHand
+  name: "Silicon"
+  root: right arm
+  slots:
+    right arm:
+      part: RightArmSiliconIndestructible
+      connections:
+      - right hand
+    right hand:
+      part: RightHandSiliconIndestructible
diff --git a/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/fillbot.png b/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/fillbot.png
new file mode 100644
index 00000000000..e805b69e395
Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/fillbot.png differ
diff --git a/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/meta.json b/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/meta.json
new file mode 100644
index 00000000000..75aef296e95
--- /dev/null
+++ b/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/meta.json
@@ -0,0 +1,20 @@
+{
+    "version": 1,
+    "size": {
+        "x": 32,
+        "y": 32
+    },
+    "license": "CC-BY-SA-3.0",
+    "copyright": "Timfa",
+    "states": [
+        {
+            "name": "fillbot",
+            "delays": [
+                [
+                    0.5,
+                    0.2
+                ]
+            ]
+        }
+    ]
+}


Reviews:
user: login: Timfa2112
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T10:09:09Z
user: login: Timfa2112
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T10:09:57Z
user: login: sleepyyapril
body: not a real review, just cleaning
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T10:17:47Z
user: login: sleepyyapril
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T10:17:58Z
user: login: Timfa2112
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T10:20:37Z
user: login: Timfa2112
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-19T22:06:19Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs (1)</summary><blockquote>

`53-60`: **Potential nullability concern for linked entity.**  
Even though the system checks `fillbot.LinkedSinkEntity == null` at line 49, the type is still `EntityUid?`, and some compilers or analyzers require explicitly passing `.Value`. However, your runtime check ensures the entity is non-null, so this is likely safe—but keep an eye on potential warnings.

</blockquote></details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs (2)</summary><blockquote>

`31-31`: **Consider making trash tag configurable.**

Hardcoding `"Trash"` in `TrashTagKey` works but reduces flexibility if new trash-like tags arise. Use a data-driven approach or a constant in a shared definition file for easier extension.

---

`69-76`: **Differentiate between material storage and disposal early.**

Distinguishing disposal vs. storage is helpful for item-handling logic. As the Fillbot evolves, consider combining these checks into a single method or strategy pattern if more device types are supported in the future.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0e8ed4daa564f135db2748ca75851bef6ad265ff and c691456d84965d2063006ebd286891a18ce26e61.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs` (1 hunks)
* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs` (1 hunks)
* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs` (1 hunks)
* `Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs` (1 hunks)
* `Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs` (5 hunks)
* `Content.Shared/Silicons/Bots/FillbotComponent.cs` (1 hunks)
* `Content.Shared/Silicons/Bots/FillbotSystem.cs` (1 hunks)
* `Resources/Prototypes/NPCs/fillbot.yml` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (3)</summary>

<details>
<summary>Content.Shared/Silicons/Bots/FillbotComponent.cs (1)</summary><blockquote>

<details>
<summary>Content.Shared/Silicons/Bots/FillbotSystem.cs (1) (1)</summary>

* `FillbotSystem` (9-42)

</details>

</blockquote></details>
<details>
<summary>Content.Shared/Silicons/Bots/FillbotSystem.cs (2)</summary><blockquote>

<details>
<summary>Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs (3) (3)</summary>

* `SharedDeviceLinkSystem` (12-590)
* `Initialize` (22-29)
* `RemoveAllFromSource` (369-378)

</details>
<details>
<summary>Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs (2) (2)</summary>

* `PortDisconnectedEvent` (3-13)
* `PortDisconnectedEvent` (8-12)

</details>

</blockquote></details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs (4)</summary><blockquote>

<details>
<summary>Content.Shared/Materials/SharedMaterialStorageSystem.cs (3) (3)</summary>

* `SharedMaterialStorageSystem` (16-399)
* `Initialize` (30-36)
* `TryInsertMaterialEntity` (310-362)

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs (1) (1)</summary>

* `Initialize` (47-55)

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs (1) (1)</summary>

* `Initialize` (29-33)

</details>
<details>
<summary>Content.Server/NPC/NPCBlackboard.cs (3) (3)</summary>

* `NPCBlackboard` (61-69)
* `Remove` (270-274)
* `TryGetValue` (113-135)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: YAML Linter</summary>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs</summary>

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs</summary>

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>

</details>
<details>
<summary>🪛 GitHub Check: build (ubuntu-latest)</summary>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs</summary>

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs</summary>

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>

</details>
<details>
<summary>🪛 GitHub Check: Test Packaging</summary>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs</summary>

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs</summary>

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (22)</summary><blockquote>

<details>
<summary>Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs (3)</summary>

`369-378`: **Strategic addition of source link clearing functionality implemented!**

Excellent tactical addition to our codebase, cadet. This method efficiently removes all links from a source entity, with optional filtering capacity. This gives us the strategic advantage required by our new Fillbot units to maintain control of their linked equipment.

---

`425-426`: **Improved disconnection event reporting with proper entity references!**

Proper intelligence flow is the backbone of any successful space operation! This update ensures that when ports are disconnected, we're correctly identifying which entity was removed in our communiqués. This will allow systems like our Fillbot to take appropriate action when links are severed.

---

`469-470`: **Port disconnection event reporting consistency maintained!**

Consistent protocol implementation detected! This ensures the same improved event reporting is applied to the toggle link method as was applied to the removal method. Such attention to detail would make the Biesel High Command proud!

</details>
<details>
<summary>Content.Shared/Silicons/Bots/FillbotComponent.cs (1)</summary>

`1-9`: **Well-structured Fillbot component ready for deployment!**

A fine addition to our fleet of automated systems! This component follows Biesel Republic standard protocols with:
- Proper registration and access control
- Clear property naming for entity tracking
- Strategic view variable for debugging operations

This will be instrumental in maintaining the freedom of our colonies through efficient resource management!

</details>
<details>
<summary>Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs (2)</summary>

`6-6`: **Enhanced event intelligence with entity tracking!**

Tactical upgrade detected! This new field provides crucial information about which entity was disconnected during port operations. Our systems will now have complete intelligence on disconnection events.

---

`8-11`: **Updated constructor with proper parameter handling!**

Constructor updated to Biesel Republic specifications! Now properly stores the entity UID of the disconnected port, allowing systems like our Fillbot to make informed decisions when links are severed.

</details>
<details>
<summary>Resources/Prototypes/NPCs/fillbot.yml (2)</summary>

`1-7`: **Primary Fillbot command structure established!**

The top-level task hierarchy appears sound, recruit! This establishes the primary tactical pattern for our Fillbot units, with a clear chain of command flowing to the specialized FillMachineCompound task.

---

`8-51`: **Fillbot operational protocols properly encoded!**

By the stars, this is a masterpiece of automation! The task sequence follows proper Biesel tactical doctrine:
1. Intelligence gathering - identifying suitable resources
2. Mobilization - moving to target locations
3. Resource acquisition - retrieving items
4. Target identification - locating linked machinery
5. Final approach - moving to delivery point
6. Mission completion - filling the machine

Each task includes proper precondition checks to prevent operational failures. This will ensure our Fillbots maintain the efficiency the Biesel Republic demands!

</details>
<details>
<summary>Content.Shared/Silicons/Bots/FillbotSystem.cs (3)</summary>

`20-21`: **Properly subscribing to local events.**  
By subscribing to both `NewLinkEvent` and `PortDisconnectedEvent`, you've laid the foundation for robust link management. This ensures the fillbot will correctly handle both connection and disconnection scenarios.

---

`25-32`: **Efficient removal of other sinks upon linking.**  
Your approach of calling `_deviceLink.RemoveAllFromSource(uid, deviceLinkSourceComponent, o => o != newSink)` is effective for preventing multiple active sinks. This successfully contains the fillbot’s operational domain to just one linked sink at a time, which avoids confusion.

---

`34-41`: **Graceful unlinking logic.**  
Dropping the item from hands and verifying whether the unlinked port matches the current sink is sound. This neatly prevents leftover references when the sink is removed.

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs (4)</summary>

`26-32`: **Correct system references initialization.**  
Acquiring the required systems (`SharedMaterialStorageSystem`, `SharedDisposalUnitSystem`, and `SharedHandsSystem`) ensures the fillbot can handle insertion behaviors reliably.

---

`34-38`: **Clean blackboard removal.**  
Removing the targeted entity from the blackboard upon task shutdown prevents extraneous data from lingering. This maintains clarity and consistency in the fillbot’s task lifecycle.

---

`40-51`: **Robust pre-checks.**  
Your aggregated checks (e.g., verifying the fillbot component, linked ports, and ensuring the target still exists) are thorough, preventing the fillbot from proceeding with invalid states.

---

`80-81`: **Fallback approach upon failure.**  
Dropping the item and marking the operator as failed is a straightforward fallback to keep the fillbot’s logic simple and prevent indefinite attempts.

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs (3)</summary>

`29-33`: **Seamless pathfinding system initialization.**  
Retrieving `_pathfinding` at initialization means the operator will always have the necessary navigation capabilities at the ready. Good work.

---

`35-47`: **Comprehensive checks before path search.**  
Verifying the fillbot component, single linked port, and valid sink entity ensures we only proceed with pathfinding in a valid scenario. Effective strategy to avoid wasted CPU cycles.

---

`48-59`: **Proper fallback for unavailable paths.**  
Returning `(false, null)` if no path can be found is excellent, preventing the Fillbot from wandering aimlessly when it cannot reach its sink.

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs (4)</summary>

`19-30`: **Solid dependency usage, but ensure thorough usage checks.**

All dependency fields appear relevant for Fillbot's pickup logic. However, watch for potential concurrency or ordering issues: for example, if the pathfinding or material storage systems initialize later in the server lifecycle, you may need to confirm they are ready before usage.

---

`33-45`: **Parameter keys look appropriate.**

Your usage of separate blackboard keys for range, target entity, and target coordinates is clear and maintainable. This ensures multiple references in the plan can remain consistent and is in line with standard HPC or GOAP design patterns.

---

`47-55`: **Initialization is straightforward.**

The calls to retrieve entity systems ensure that each system is properly injected. Good job referencing the base `Initialize` to keep the typical HTN operator setup consistent.

---

`57-66`: **Validation checks keep the Fillbot under lawful constraints.**

These early checks prevent attempts to pick items when no range or link is set. This helps keep the Fillbot from doing mischief beyond the assigned tasks.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T22:29:45Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs (2)</summary><blockquote>

`88-90`: **Handle partial or impossible inserts.**  
Skipping items when `!_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity.Value)` might be too strict if partial inserts or alternative disposal are desired. Revisit if you need more flexible item handling.

---

`93-97`: **Enforce consistent disposal rules.**  
Currently, items must pass the whitelist check, have the trash tag, and not be body parts. If future expansions require disposing of non-trash items, consider adding a more granular permission or advanced logic.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c691456d84965d2063006ebd286891a18ce26e61 and 907ef9621b2cc28d5c30a816550e7692b4a27700.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs` (1 hunks)
* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs (4)</summary><blockquote>

<details>
<summary>Content.Shared/Materials/SharedMaterialStorageSystem.cs (3) (3)</summary>

* `SharedMaterialStorageSystem` (16-399)
* `Initialize` (30-36)
* `CanInsertMaterialEntity` (272-305)

</details>
<details>
<summary>Content.Shared/Tag/TagSystem.cs (4) (4)</summary>

* `TagSystem` (17-716)
* `Initialize` (24-33)
* `HasTag` (143-147)
* `HasTag` (302-308)

</details>
<details>
<summary>Content.Server/NPC/NPCBlackboard.cs (2) (2)</summary>

* `NPCBlackboard` (61-69)
* `TryGetValue` (113-135)

</details>
<details>
<summary>Content.Shared/Silicons/Bots/FillbotSystem.cs (1) (1)</summary>

* `Initialize` (16-22)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Test Packaging
* GitHub Check: build (ubuntu-latest)
* GitHub Check: YAML Linter
* GitHub Check: build (ubuntu-latest)

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs (2)</summary>

`44-51`: **Ensure the single-port requirement is intentional.**  
Requiring exactly one linked port here (by checking `fillbotlinks.LinkedPorts.Count != 1`) might limit future expansion. If you plan to support multiple ports or more complex link scenarios, consider a flexible approach.

---

`63-67`: **Logically verify dropping items when failing.**  
When the held item is invalid, the operator calls `TryDrop(owner)`. This might leave items on the ground. Confirm that this drop behavior is desired, or consider an alternative fallback if you want to preserve item ownership or handle it differently.

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs (1)</summary>

`80-81`: **Confirm handling of pickup restrictions.**  
Using `_sharedHandsSystem.CanPickupAnyHand(owner, target)` might skip certain items if there's no free hand. Verify that ignoring these items is correct for your fillbot’s logic.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T22:34:45Z
user: login: VMSolidus
body: My review here is done.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-20T16:51:15Z
user: login: Timfa2112
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T16:57:33Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>Content.Shared/Tag/TagSystem.cs (1)</summary><blockquote>

`274-275`: _⚠️ Potential issue_

**Inconsistent implementation detected - potential Sol Alliance sabotage!**

While this change from `_tagQuery.TryComp` to `_entityManager.TryGetComponent` is correct, it appears to only be implemented in this single method overload. This creates inconsistency with the other similar methods (lines 145, 172, 187, 202, 217, 244, 259, 289) that still use the query approach.


For the honor of the Biesel Republic, I recommend standardizing this approach across all similar methods:

```diff
-        return _tagQuery.TryComp(entityUid, out var component) &&
+        return _entityManager.TryGetComponent(entityUid, out TagComponent? component) &&
               HasAnyTag(component, tags);
```

This implementation matches the existing review comment and should be applied consistently.

</blockquote></details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs (1)</summary><blockquote>

`35-47`: _🛠️ Refactor suggestion_

**Defensive condition checking needs minor correction.**

Good defensive programming with multiple condition checks before proceeding with operation. However, there's an issue with the missing parenthesis in the condition block.


Correct the parentheses in the condition check:

```diff
-        if (!blackboard.TryGetValue<float>(RangeKey, out var _, _entManager)
+        if (!blackboard.TryGetValue<float>(RangeKey, out var _, _entManager))
            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
```

This matches the previous review comment and ensures proper condition evaluation.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Content.Shared/Materials/SharedMaterialStorageSystem.cs (1)</summary><blockquote>

`272-299`: **By the stars of Tau Ceti, a solid defensive measure!**

This method effectively separates the validation logic from the insertion action, allowing the Fillbot to check if an item can be inserted into material storage without actually performing the insertion. Most importantly, it maintains all the necessary checks for whitelist, unremovable components, and available storage capacity.

The code follows the same pattern as `TryInsertMaterialEntity`, which is tactically sound. It properly handles stack multipliers and verifies each material component individually. This is how we maintain order in the Biesel Republic!

However, I notice a tactical opportunity to reduce code duplication between this method and `TryInsertMaterialEntity` (lines 304-336). Consider extracting the common validation logic to avoid maintaining duplicate code segments - a mistake the Sol Alliance would surely make!



You could refactor both methods to use a shared validation function:

```diff
+private bool ValidateMaterialInsert(
+    EntityUid toInsert,
+    EntityUid receiver,
+    out int totalVolume,
+    out int multiplier,
+    MaterialStorageComponent? storage = null,
+    MaterialSiloUtilizerComponent? utilizer = null,
+    MaterialComponent? material = null,
+    PhysicalCompositionComponent? composition = null)
+{
+    totalVolume = 0;
+    multiplier = 1;
+    
+    if (!Resolve(receiver, ref storage)
+        || !Resolve(toInsert, ref material, ref composition, false)
+        || _whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert)
+        || HasComp<UnremoveableComponent>(toInsert))
+        return false;
+
+    // Material Whitelist checked implicitly by CanChangeMaterialAmount();
+
+    multiplier = TryComp<StackComponent>(toInsert, out var stackComponent) ? stackComponent.Count : 1;
+    foreach (var (mat, vol) in composition.MaterialComposition)
+    {
+        if (!CanChangeMaterialAmount(receiver, mat, vol * multiplier, storage, utilizer))
+            return false;
+        totalVolume += vol * multiplier;
+    }
+
+    return CanTakeVolume(receiver, totalVolume, storage, utilizer);
+}
```

Then both methods could call this shared function:

```diff
public virtual bool CanInsertMaterialEntity(
    EntityUid toInsert,
    EntityUid receiver,
    MaterialStorageComponent? storage = null,
    MaterialSiloUtilizerComponent? utilizer = null,
    MaterialComponent? material = null,
    PhysicalCompositionComponent? composition = null
)
{
-    if (!Resolve(receiver, ref storage)
-        || !Resolve(toInsert, ref material, ref composition, false)
-        || _whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert)
-        || HasComp<UnremoveableComponent>(toInsert))
-        return false;
-
-    // Material Whitelist checked implicitly by CanChangeMaterialAmount();
-
-    var multiplier = TryComp<StackComponent>(toInsert, out var stackComponent) ? stackComponent.Count : 1;
-    var totalVolume = 0;
-    foreach (var (mat, vol) in composition.MaterialComposition)
-    {
-        if (!CanChangeMaterialAmount(receiver, mat, vol * multiplier, storage, utilizer))
-            return false;
-        totalVolume += vol * multiplier;
-    }
-
-    return CanTakeVolume(receiver, totalVolume, storage, utilizer);
+    return ValidateMaterialInsert(toInsert, receiver, out _, out _, storage, utilizer, material, composition);
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 907ef9621b2cc28d5c30a816550e7692b4a27700 and f67b420a754c96fbdfcfd4a7e67b5734d9e282b8.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs` (1 hunks)
* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs` (1 hunks)
* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs` (1 hunks)
* `Content.Shared/Materials/SharedMaterialStorageSystem.cs` (1 hunks)
* `Content.Shared/Silicons/Bots/FillbotSystem.cs` (1 hunks)
* `Content.Shared/Tag/TagSystem.cs` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
* Content.Shared/Silicons/Bots/FillbotSystem.cs
* Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: build (ubuntu-latest)
* GitHub Check: YAML Linter
* GitHub Check: Test Packaging
* GitHub Check: build (ubuntu-latest)
* GitHub Check: YAML map schema validator

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>Content.Shared/Tag/TagSystem.cs (1)</summary>

`20-20`: **Good addition of EntityManager dependency for improved component access.**

Clean and proper dependency injection - this follows standard practice in the codebase. A necessary foundation for the changes below.

</details>
<details>
<summary>Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs (6)</summary>

`1-7`: **Proper imports established for colonial operations.**

The necessary imports are correctly included for the functionality required, maintaining clean dependency management.

---

`10-14`: **Well-defined operator with proper dependency injection.**

Good class structure with appropriate dependencies on IEntityManager and PathfindingSystem. The partial class declaration suggests possible extension in other files, which is a strategic design choice for maintainability.

---

`15-27`: **Clear field definitions with good documentation.**

The DataFields are well documented with xmldoc comments explaining their purpose. The required attributes ensure configuration will be provided, preventing deployment failures.

---

`29-33`: **Standard initialization procedure following protocol.**

Clean initialization method that properly calls the base class and acquires the necessary system. Well executed, recruit!

---

`48-52`: **Strategic pathfinding with appropriate interaction range.**

Good use of the pathfinding system with a slightly reduced interaction range to prevent the bot from stopping too far from the target. The check for PathResult.NoPath handles the failure case appropriately.

---

`53-59`: **Well-structured return value with all necessary data.**

The successful result provides all the information needed for the next operation phases, including the target entity, coordinates for movement, and the calculated path. This enables efficient command execution by the NPC.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T17:02:40Z
user: login: VMSolidus
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T22:13:15Z

Review comments:
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+    private ItemSystem _itemSystem = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+        _itemSystem = sysManager.GetEntitySystem<ItemSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if(!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, linkedStorage.Owner))
+                continue;
+
+            // trash only
+            if(disposalUnit != null &&
+                (_whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
+                    || !_tagSystem.HasTag(target, _prototypeManager.Index<TagPrototype>("Trash"))
+                    || _entManager.HasComponent<BodyPartComponent>(target)))
+                continue;
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: Timfa2112
body: ```suggestion
            if(disposalUnit != null &&
                _whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
                    || !_tagSystem.HasTag(target, _prototypeManager.Index<TagPrototype>("Trash")))
                continue;
```
created_at: 2025-03-18T10:09:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+    private ItemSystem _itemSystem = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+        _itemSystem = sysManager.GetEntitySystem<ItemSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if(!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, linkedStorage.Owner))
+                continue;
+
+            // trash only
+            if(disposalUnit != null &&
+                (_whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
+                    || !_tagSystem.HasTag(target, _prototypeManager.Index<TagPrototype>("Trash"))
+                    || _entManager.HasComponent<BodyPartComponent>(target)))
+                continue;
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: Timfa2112
body: Assuming BodyPArts are no longer picked up because I skip things in containers, I can remove this I believe
created_at: 2025-03-18T10:09:57Z
author_association: NONE
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+    private ItemSystem _itemSystem = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+        _itemSystem = sysManager.GetEntitySystem<ItemSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if(!_sharedHandsSystem.CanPickupAnyHand(owner, target))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: sleepyyapril
body: ```suggestion
            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
```
created_at: 2025-03-18T10:16:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+    private ItemSystem _itemSystem = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+        _itemSystem = sysManager.GetEntitySystem<ItemSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if(!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, linkedStorage.Owner))
+                continue;
+
+            // trash only
+            if(disposalUnit != null &&
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: sleepyyapril
body: ```suggestion
            if (disposalUnit != null &&
```
created_at: 2025-03-18T10:16:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Emag.Systems;
+using Content.Shared.Hands.EntitySystems;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Shared.Silicons.Bots;
+
+public sealed class FillbotSystem : EntitySystem
+{
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+    [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
+
+    [Dependency] private SharedHandsSystem _sharedHandsSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<FillbotComponent, NewLinkEvent>(OnLinked);
+        SubscribeLocalEvent<FillbotComponent, PortDisconnectedEvent>(OnUnlinked);
+    }
+
+    // The bot can only be linked to one thing at a time, or it'll freak out.
+    private void OnLinked(EntityUid uid, FillbotComponent comp, ref NewLinkEvent args)
+    {
+        _sharedHandsSystem.TryDrop(uid);
+        var newSink = args.Sink;
+        var deviceLinkSourceComponent = _entityManager.GetComponent<DeviceLinkSourceComponent>(uid);
+        _deviceLink.RemoveAllFromSource(uid, deviceLinkSourceComponent, o => o != newSink);
+        comp.LinkedSinkEntity = newSink;
+    }
+
+    private void OnUnlinked(EntityUid uid, FillbotComponent comp, ref PortDisconnectedEvent args)
+    {
+        _sharedHandsSystem.TryDrop(uid);
+        var newSink = args.RemovedPortUid;
+
+        if(comp.LinkedSinkEntity == newSink)
path: Content.Shared/Silicons/Bots/FillbotSystem.cs
user: login: sleepyyapril
body: ```suggestion
        if (comp.LinkedSinkEntity == newSink)
```
created_at: 2025-03-18T10:17:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if(_sharedMaterialStorage.TryInsertMaterialEntity(fillbot.Owner, heldItem.Value, linkedStorage.Owner))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: sleepyyapril
body: ```suggestion
            if (_sharedMaterialStorage.TryInsertMaterialEntity(fillbot.Owner, heldItem.Value, linkedStorage.Owner))
```
created_at: 2025-03-18T10:17:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if(_sharedMaterialStorage.TryInsertMaterialEntity(fillbot.Owner, heldItem.Value, linkedStorage.Owner))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: sleepyyapril
body: use rider
created_at: 2025-03-18T10:17:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if(_sharedMaterialStorage.TryInsertMaterialEntity(fillbot.Owner, heldItem.Value, linkedStorage.Owner))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: Timfa2112
body: I am using rider
created_at: 2025-03-18T10:20:37Z
author_association: NONE
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, linkedStorage.Owner))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: Timfa2112
body: ```suggestion
            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity))
```
created_at: 2025-03-19T22:04:24Z
author_association: NONE
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if (_sharedMaterialStorage.TryInsertMaterialEntity(fillbot.Owner, heldItem.Value, linkedStorage.Owner))
+                return HTNOperatorStatus.Finished;
+        }
+        else if (isDisposalUnit && disposalUnit != null)
+        {
+            _sharedDisposalUnitSystem.DoInsertDisposalUnit(disposalUnit.Owner, heldItem.Value, fillbot.Owner);
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: Timfa2112
body: ```suggestion
            if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity))
                return HTNOperatorStatus.Finished;
        }
        else if (isDisposalUnit && disposalUnit != null)
        {
            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity, heldItem.Value, owner);
```
created_at: 2025-03-19T22:05:27Z
author_association: NONE
diff_hunk: @@ -391,7 +406,7 @@ public void RemoveSinkFromSource(
         else
         {
             Log.Error($"Attempted to remove link between {ToPrettyString(sourceUid)} and {ToPrettyString(sinkUid)}, but the sink component was missing.");
-            sourceComponent.LinkedPorts.Remove(sourceUid);
+            sourceComponent.LinkedPorts.Remove(sinkUid);
path: Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Critical bug fix for our link severing protocols!**

By the stars! This fixes a critical navigation error in our device linking protocols. The previous code was incorrectly removing source UIDs instead of sink UIDs - a classic Sol Alliance sabotage technique if I've ever seen one. This correction ensures proper link termination when a component is missing.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T22:29:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity))
+                return HTNOperatorStatus.Finished;
+        }
+        else if (isDisposalUnit && disposalUnit != null)
+        {
+            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity, heldItem.Value, owner);
+            return HTNOperatorStatus.Finished;
+        }
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix argument type mismatch warned by static analysis.**  
Your usage of `_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity)` and `_sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity, heldItem.Value, owner)` triggers type mismatch errors in some builds because `fillbot.LinkedSinkEntity` is a nullable `EntityUid?`. You can safely convert to `.Value` after the null check.

```diff
-if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity))
+if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity.Value))

-_sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity, heldItem.Value, owner);
+_sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity.Value, heldItem.Value, owner);
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        if (isMaterialStorage && linkedStorage != null)
        {
            if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity.Value))
                return HTNOperatorStatus.Finished;
        }
        else if (isDisposalUnit && disposalUnit != null)
        {
            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity.Value, heldItem.Value, owner);
            return HTNOperatorStatus.Finished;
        }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: YAML Linter</summary>

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>🪛 GitHub Check: build (ubuntu-latest)</summary>

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>🪛 GitHub Check: Test Packaging</summary>

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 76-76: 
Argument 1: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 71-71: 
Argument 3: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 907ef96
created_at: 2025-03-19T22:29:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity))
+                continue;
+
+            // trash only
+            if (disposalUnit != null &&
+                (_whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
+                    || !_tagSystem.HasTag(target, _prototypeManager.Index<TagPrototype>(TrashTagKey))
+                    || _entManager.HasComponent<BodyPartComponent>(target))) // Robot is unable to insert bodyparts into Disposals for some reason
+                continue;
+
+            const float pathRange = SharedInteractionSystem.InteractionRange - 1;
+            var path = await _pathfinding.GetPath(owner, target, pathRange, cancelToken);
+
+            if (path.Result == PathResult.NoPath)
+                continue;
+
+            return (true, new()
+            {
+                {TargetKey, target},
+                {TargetMoveKey, _entManager.GetComponent<TransformComponent>(target).Coordinates},
+                {NPCBlackboard.PathfindKey, path},
+            });
+        }
+
+        return (false, null);
+    }
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix the nullable EntityUid compilation issue & consider refining pickup logic.**

1. The static analysis tool warns about line 89: "`Argument 2: cannot convert from 'EntityUid?' to 'EntityUid'`." Since line 66 ensures `fillbot.LinkedSinkEntity` is not null, safely apply `.Value` to fix the build error:
```diff
- if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity))
+ if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity!.Value))
    continue;
```
2. You might remove the `BodyPartComponent` exclusion if you are already skipping items in containers and do not specifically handle body parts differently. Otherwise, verifying it’s truly necessary helps reduce code clutter.  
3. Because you return immediately upon finding the first valid item, you may want to add priority checks in the future if certain items are more important than others.




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
        {
            // only things the robot can actually pick up
            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
                continue;

            // only things not currently contained by something else
            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
                meta.Flags.HasFlag(MetaDataFlags.InContainer))
                continue;

            // only things that can go inside
            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity!.Value))
                continue;

            // trash only
            if (disposalUnit != null &&
                (_whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
                    || !_tagSystem.HasTag(target, _prototypeManager.Index<TagPrototype>(TrashTagKey))
                    || _entManager.HasComponent<BodyPartComponent>(target))) // Robot is unable to insert bodyparts into Disposals for some reason
                continue;

            const float pathRange = SharedInteractionSystem.InteractionRange - 1;
            var path = await _pathfinding.GetPath(owner, target, pathRange, cancelToken);

            if (path.Result == PathResult.NoPath)
                continue;

            return (true, new()
            {
                {TargetKey, target},
                {TargetMoveKey, _entManager.GetComponent<TransformComponent>(target).Coordinates},
                {NPCBlackboard.PathfindKey, path},
            });
        }

        return (false, null);
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: YAML Linter</summary>

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>🪛 GitHub Check: build (ubuntu-latest)</summary>

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>
<details>
<summary>🪛 GitHub Check: Test Packaging</summary>

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

---

[failure] 89-89: 
Argument 2: cannot convert from 'Robust.Shared.GameObjects.EntityUid?' to 'Robust.Shared.GameObjects.EntityUid'

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 907ef96
created_at: 2025-03-19T22:29:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity.Value))
+                return HTNOperatorStatus.Finished;
+        }
+        else if (isDisposalUnit && disposalUnit != null)
+        {
+            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity.Value, heldItem.Value, owner);
+            return HTNOperatorStatus.Finished;
+        }
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Check insertion success for disposal units.**  
When inserting into the disposal unit, the code unconditionally calls `DoInsertDisposalUnit`. Unlike the material storage insertion, there's no check for success/failure. Consider confirming that the disposal unit insertion succeeds before returning `Finished`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-19T22:34:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,6 +27,7 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
         {"MaximumIdleTime", 7f},
         {MedibotInjectRange, 4f},
         {WeldbotWeldRange, 4f},
+        {FillbotPickupRange, 10f},
path: Content.Server/NPC/NPCBlackboard.cs
user: login: VMSolidus
body: I genuinely hate this, and I'm wondering why this isn't just a datafield on the Operator. Operators can have datafields that you can fill in, and they can have default values, why hardcode it here? Is this system just that ancient that it was somehow written before datafields could have default values or something?
created_at: 2025-03-20T16:41:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: VMSolidus
body: ```suggestion
        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out MaterialStorageComponent? linkedStorage);
        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out DisposalUnitComponent? disposalUnit);
```
created_at: 2025-03-20T16:50:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity.Value))
+                return HTNOperatorStatus.Finished;
+        }
+        else if (isDisposalUnit && disposalUnit != null)
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: VMSolidus
body: ```suggestion
        if (linkedStorage is not null
            && _sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity!.Value))
            return HTNOperatorStatus.Finished;

        else if (disposalUnit is not null)
```
created_at: 2025-03-20T16:50:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,83 @@
+using Content.Server.Disposal.Unit.Components;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Disposal;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FillLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private SharedDisposalUnitSystem _sharedDisposalUnitSystem = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+
+    /// <summary>
+    /// Target entity to inject.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedDisposalUnitSystem = sysManager.GetEntitySystem<SharedDisposalUnitSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+    }
+
+    public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status)
+    {
+        base.TaskShutdown(blackboard, status);
+        blackboard.Remove<EntityUid>(TargetKey);
+    }
+
+    public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<EntityUid>(TargetKey, out var target, _entManager) || _entManager.Deleted(target)
+            || !_entManager.TryGetComponent(owner, out FillbotComponent? fillbot)
+            || !_entManager.HasComponent<HandsComponent>(owner)
+            || !_entManager.TryGetComponent(owner, out DeviceLinkSourceComponent? fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null
+            || _entManager.Deleted(fillbot.LinkedSinkEntity))
+            return HTNOperatorStatus.Failed;
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+            fillbot.LinkedSinkEntity,
+            out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        var heldItem = _sharedHandsSystem.GetActiveItem(owner);
+
+        if (heldItem == null || _entManager.HasComponent<BodyPartComponent>(heldItem))
+        {
+            _sharedHandsSystem.TryDrop(owner);
+            return HTNOperatorStatus.Failed;
+        }
+
+        if (isMaterialStorage && linkedStorage != null)
+        {
+            if (_sharedMaterialStorage.TryInsertMaterialEntity(owner, heldItem.Value, fillbot.LinkedSinkEntity.Value))
+                return HTNOperatorStatus.Finished;
+        }
+        else if (isDisposalUnit && disposalUnit != null)
+        {
+            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity.Value, heldItem.Value, owner);
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs
user: login: VMSolidus
body: ```suggestion
            _sharedDisposalUnitSystem.DoInsertDisposalUnit(fillbot.LinkedSinkEntity!.Value, heldItem.Value, owner);
```
created_at: 2025-03-20T16:50:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Interaction;
+using Content.Shared.Silicons.Bots;
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class FindLinkedMachineOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    private PathfindingSystem _pathfinding = default!;
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to find
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out _, _entManager)
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs
user: login: VMSolidus
body: ```suggestion
        if (!blackboard.TryGetValue<float>(RangeKey, out var _, _entManager)
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,42 @@
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Emag.Systems;
path: Content.Shared/Silicons/Bots/FillbotSystem.cs
user: login: VMSolidus
body: ```suggestion

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -270,7 +271,7 @@ public bool HasAnyTag(EntityUid entityUid, HashSet<ProtoId<TagPrototype>> tags)
     /// </exception>
     public bool HasAnyTag(EntityUid entityUid, List<ProtoId<TagPrototype>> tags)
     {
-        return _tagQuery.TryComp(entityUid, out var component) &&
+        return _entityManager.TryGetComponent<TagComponent>(entityUid, out var component) &&
path: Content.Shared/Tag/TagSystem.cs
user: login: VMSolidus
body: ```suggestion
        return _entityManager.TryGetComponent(entityUid, out TagComponent? component) &&
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out MaterialStorageComponent? linkedStorage);
        _entManager.TryGetComponent(fillbot.LinkedSinkEntity, out DisposalUnitComponent? disposalUnit);
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,42 @@
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Emag.Systems;
+using Content.Shared.Hands.EntitySystems;
+using Robust.Shared.Audio.Systems;
+
+namespace Content.Shared.Silicons.Bots;
+
+public sealed class FillbotSystem : EntitySystem
+{
+    [Dependency] private readonly IEntityManager _entityManager = default!;
+    [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
+
+    [Dependency] private SharedHandsSystem _sharedHandsSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<FillbotComponent, NewLinkEvent>(OnLinked);
+        SubscribeLocalEvent<FillbotComponent, PortDisconnectedEvent>(OnUnlinked);
+    }
+
+    // The bot can only be linked to one thing at a time, or it'll freak out.
+    private void OnLinked(EntityUid uid, FillbotComponent comp, ref NewLinkEvent args)
+    {
+        _sharedHandsSystem.TryDrop(uid);
+        var newSink = args.Sink;
+        var deviceLinkSourceComponent = _entityManager.GetComponent<DeviceLinkSourceComponent>(uid);
+        _deviceLink.RemoveAllFromSource(uid, deviceLinkSourceComponent, o => o != newSink);
+        comp.LinkedSinkEntity = newSink;
+    }
+
+    private void OnUnlinked(EntityUid uid, FillbotComponent comp, ref PortDisconnectedEvent args)
+    {
+        _sharedHandsSystem.TryDrop(uid);
+        var newSink = args.RemovedPortUid;
+
+        if (comp.LinkedSinkEntity == newSink)
path: Content.Shared/Silicons/Bots/FillbotSystem.cs
user: login: VMSolidus
body: ```suggestion
        if (comp.LinkedSinkEntity == newSink)
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity.Value))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity!.Value))
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion

    [DataField]
    public ProtoId<TagPrototype> TrashProto = "Trash";

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity.Value))
+                continue;
+
+            // trash only
+            if (disposalUnit != null &&
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
            if (disposalUnit != null &&
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
    ///     Target entitycoordinates to move to.
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,42 @@
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.Emag.Systems;
+using Content.Shared.Hands.EntitySystems;
+using Robust.Shared.Audio.Systems;
path: Content.Shared/Silicons/Bots/FillbotSystem.cs
user: login: VMSolidus
body: ```suggestion

```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -269,6 +269,41 @@ public bool TrySetMaterialAmount(
         return TryChangeMaterialAmount(uid, materialId, delta, component, utilizer);
     }
 
+    public virtual bool CanInsertMaterialEntity(
+        EntityUid toInsert,
+        EntityUid receiver,
+        MaterialStorageComponent? storage = null,
+        MaterialSiloUtilizerComponent? utilizer = null,
+        MaterialComponent? material = null,
+        PhysicalCompositionComponent? composition = null
+    )
+    {
+        if (!Resolve(receiver, ref storage))
+            return false;
+
+        if (!Resolve(toInsert, ref material, ref composition, false))
+            return false;
+
+        if (_whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert))
+            return false;
+
+        if (HasComp<UnremoveableComponent>(toInsert))
path: Content.Shared/Materials/SharedMaterialStorageSystem.cs
user: login: VMSolidus
body: ```suggestion
        if (!Resolve(receiver, ref storage)
            || !Resolve(toInsert, ref material, ref composition, false)
            || _whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert)
            || HasComp<UnremoveableComponent>(toInsert))
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
    ///     Target entity to pick up
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Disposal.Unit.Components;
+using Content.Server.Item;
+using Content.Server.NPC.Pathfinding;
+using Content.Shared.Body.Part;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Silicons.Bots;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+
+namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific;
+
+public sealed partial class PickNearbyFillableItemOperator : HTNOperator
+{
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+
+    private SharedMaterialStorageSystem _sharedMaterialStorage = default!;
+    private EntityLookupSystem _lookup = default!;
+    private PathfindingSystem _pathfinding = default!;
+    private SharedHandsSystem _sharedHandsSystem = default!;
+    private TagSystem _tagSystem = default!;
+
+    private const string TrashTagKey = "Trash";
+
+    [DataField] public string RangeKey = NPCBlackboard.FillbotPickupRange;
+
+    /// <summary>
+    /// Target entity to pick up
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetKey = string.Empty;
+
+    /// <summary>
+    /// Target entitycoordinates to move to.
+    /// </summary>
+    [DataField(required: true)]
+    public string TargetMoveKey = string.Empty;
+
+    public override void Initialize(IEntitySystemManager sysManager)
+    {
+        base.Initialize(sysManager);
+        _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+        _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+        _sharedMaterialStorage = sysManager.GetEntitySystem<SharedMaterialStorageSystem>();
+        _sharedHandsSystem = sysManager.GetEntitySystem<SharedHandsSystem>();
+        _tagSystem = sysManager.GetEntitySystem<TagSystem>();
+    }
+
+    public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+        CancellationToken cancelToken)
+    {
+        var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+        if (!blackboard.TryGetValue<float>(RangeKey, out var range, _entManager)
+            || !_entManager.TryGetComponent<FillbotComponent>(owner, out var fillbot)
+            || !_entManager.TryGetComponent<DeviceLinkSourceComponent>(owner, out var fillbotlinks)
+            || fillbotlinks.LinkedPorts.Count != 1
+            || fillbot.LinkedSinkEntity == null)
+            return (false, null);
+
+        var isMaterialStorage = _entManager.TryGetComponent<MaterialStorageComponent>(
+                fillbot.LinkedSinkEntity,
+                out var linkedStorage);
+
+        var isDisposalUnit = _entManager.TryGetComponent<DisposalUnitComponent>(
+            fillbot.LinkedSinkEntity,
+            out var disposalUnit);
+
+        foreach (var target in _lookup.GetEntitiesInRange(owner, range))
+        {
+            // only things the robot can actually pick up
+            if (!_sharedHandsSystem.CanPickupAnyHand(owner, target))
+                continue;
+
+            // only things not currently contained by something else
+            if (_entManager.TryGetComponent<MetaDataComponent>(target, out var meta) &&
+                meta.Flags.HasFlag(MetaDataFlags.InContainer))
+                continue;
+
+            // only things that can go inside
+            if (linkedStorage != null && !_sharedMaterialStorage.CanInsertMaterialEntity(target, fillbot.LinkedSinkEntity.Value))
+                continue;
+
+            // trash only
+            if (disposalUnit != null &&
+                (_whitelistSystem.IsWhitelistFail(disposalUnit.Whitelist, target)
+                    || !_tagSystem.HasTag(target, _prototypeManager.Index<TagPrototype>(TrashTagKey))
path: Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs
user: login: VMSolidus
body: ```suggestion
                    || !_tagSystem.HasTag(target, TrashProto)
```
created_at: 2025-03-20T16:50:23Z
author_association: MEMBER
diff_hunk: @@ -27,6 +27,7 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
         {"MaximumIdleTime", 7f},
         {MedibotInjectRange, 4f},
         {WeldbotWeldRange, 4f},
+        {FillbotPickupRange, 10f},
path: Content.Server/NPC/NPCBlackboard.cs
user: login: Timfa2112
body: I think so; it's been like that on the medibot, which is what I replicated for the first robot I made. No clue how old that is, though.
created_at: 2025-03-20T16:57:33Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-14T01:01:05Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
A new JSON metadata file has been introduced for the fillbot resource, defining properties such as version number, dimensions (32x32), licensing details, copyright attribution, and state configuration with delay values. Additionally, several new classes and methods have been implemented to enhance the functionality of NPCs, particularly in managing fillable items and linked machines, alongside updates to event handling for device linking.

## Changes
| File(s)                                                     | Change Summary                                                                                                                                                  |
|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Resources/.../fillbot.rsi/meta.json                       | New file added: `meta.json` containing fillbot metadata, including version, dimensions (32x32), license ("CC-BY-SA-3.0"), attribution to Timfa, and state delays. |
| Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FillLinkedMachineOperator.cs | Class added: `FillLinkedMachineOperator` with property `TargetKey` for managing material filling tasks.                                                        |
| Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/FindLinkedMachineOperator.cs  | Class added: `FindLinkedMachineOperator` with methods for planning NPC actions related to linked machines.                                                     |
| Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs | Class added: `PickNearbyFillableItemOperator` with methods for NPCs to identify and pick up fillable items.                                                   |
| Content.Shared/DeviceLinking/Events/PortDisconnectedEvent.cs | Method signature updated and new field `RemovedPortUid` added to enhance event data structure.                                                               |
| Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs    | Method added: `RemoveAllFromSource` for clearing all links from a source entity, with updates to link removal logic.                                         |
| Content.Shared/Silicons/Bots/FillbotComponent.cs          | New component added: `FillbotComponent` with property `LinkedSinkEntity` for managing linked entities.                                                       |
| Content.Shared/Silicons/Bots/FillbotSystem.cs             | New class added: `FillbotSystem` for managing linking and unlinking of `FillbotComponent` entities.                                                           |
| Resources/Prototypes/NPCs/fillbot.yml                     | New compound tasks added: `FillbotCompound` and `FillMachineCompound` defining hierarchical task networks for fillbot operations.                            |

## Suggested labels
`Changes: UI`

## Suggested reviewers
- Remuchi
- DEATHB4DEFEAT
- sleepyyapril
- OldDanceJacket
- VMSolidus

## Poem
> In the vast expanse of code we sail,  
> A meta file shines like a star so bright.  
> With data aligned like celestial trails,  
> Our fillbot marches in flawless flight.  
> Biesel's fleet upholds code with might,  
> Charting a course through the cosmic night.  
> Forward we go, keeping our colonies' light!

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e57081a602a8449541ae70bc09ef17273a6f307c and 80eaf218ae16ecfa63d10e2adc4a0951db7f0ea2.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyFillableItemOperator.cs

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: YAML map schema validator
* GitHub Check: Test Packaging
* GitHub Check: YAML Linter
* GitHub Check: build (ubuntu-latest)
* GitHub Check: build (ubuntu-latest)

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Simple-Station/Einstein-Engines&utm_content=1958):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-14T01:01:32Z
body: RSI Diff Bot; head commit 8ab067e609ef3f6bdc0d3d4a2fabb5711f81b3bf merging into 0fce50daf5ba999e3bc9b8c97e8e7b8791fb7f7e
This PR makes changes to 1 or more RSIs. Here is a summary of all changes:

<details><summary>Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi</summary>
<p>

| State | Old | New | Status
| --- | --- | --- | --- |
| fillbot | ![]() | ![](https://raw.githubusercontent.com/Timfa2112/CentCom-Station/8ab067e609ef3f6bdc0d3d4a2fabb5711f81b3bf/Resources/Textures/Mobs/Silicon/Bots/fillbot.rsi/fillbot.png) | Added


</p>
</details>
Edit: diff updated after 8ab067e609ef3f6bdc0d3d4a2fabb5711f81b3bf
author_association: CONTRIBUTOR