]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Borg hands & hand whitelisting (#38668)
authorpathetic meowmeow <uhhadd@gmail.com>
Tue, 12 Aug 2025 22:21:42 +0000 (18:21 -0400)
committerGitHub <noreply@github.com>
Tue, 12 Aug 2025 22:21:42 +0000 (15:21 -0700)
* Borg hands & hand whitelisting

* yaml linted

* yaml linted (x2)

* yaml linted (x3)

* my storage tests so pass

* no need for SetCount

* ok new stuff you can get fixed too

* oops

* staque

* what if we addressed feedback

* my place so holder

* what if we addresesd feedback

* what if i did it correctly

* terminating or deleted

34 files changed:
Content.Client/Stack/StackSystem.cs
Content.Client/UserInterface/Controls/SlotControl.cs
Content.Client/UserInterface/Systems/Hands/HandsUIController.cs
Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml.cs
Content.Client/UserInterface/Systems/Inventory/Controls/ItemStatusPanel.xaml.cs
Content.Server/Construction/ConstructionSystem.Interactions.cs
Content.Server/Silicons/Borgs/BorgSystem.Modules.cs
Content.Server/Stack/StackSystem.cs
Content.Shared/Construction/Components/MachineBoardComponent.cs
Content.Shared/Hands/Components/HandsComponent.cs
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs [new file with mode: 0644]
Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs
Content.Shared/Stacks/SharedStackSystem.cs
Content.Shared/Stacks/StackComponent.cs
Resources/Locale/en-US/robotics/borg_modules.ftl [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml
Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml
Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml
Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml
Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml
Resources/Prototypes/Entities/Objects/Materials/ingots.yml
Resources/Prototypes/Entities/Objects/Materials/materials.yml
Resources/Prototypes/Entities/Objects/Materials/parts.yml
Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml
Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml
Resources/Prototypes/Entities/Objects/Specific/chemistry.yml
Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml
Resources/Prototypes/tags.yml

index d12e9900a6e2a848fa1e6dd282b7486452c8f228..d7d1c34ae2fd6d41778a8327147f7d24baf9e776 100644 (file)
@@ -28,22 +28,8 @@ namespace Content.Client.Stack
 
             base.SetCount(uid, amount, component);
 
-            if (component.Lingering &&
-                TryComp<SpriteComponent>(uid, out var sprite))
-            {
-                // tint the stack gray and make it transparent if it's lingering.
-                var color = component.Count == 0 && component.Lingering
-                    ? Color.DarkGray.WithAlpha(0.65f)
-                    : Color.White;
-
-                for (var i = 0; i < sprite.AllLayers.Count(); i++)
-                {
-                    _sprite.LayerSetColor((uid, sprite), i, color);
-                }
-            }
-
             // TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call.
-            if (component.Count <= 0 && !component.Lingering)
+            if (component.Count <= 0)
             {
                 Xform.DetachEntity(uid, Transform(uid));
                 return;
index a684bb05efd72994ce5b36de6c3914c24d15da30..2b43f2397defc7bb6b350c77abf8c4bde15eba17 100644 (file)
@@ -1,9 +1,11 @@
 using System.Numerics;
 using Content.Client.Cooldown;
 using Content.Client.UserInterface.Systems.Inventory.Controls;
+using Robust.Client.GameObjects;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
 using Robust.Shared.Input;
+using Robust.Shared.Prototypes;
 
 namespace Content.Client.UserInterface.Controls
 {
@@ -20,6 +22,7 @@ namespace Content.Client.UserInterface.Controls
         public CooldownGraphic CooldownDisplay { get; }
 
         private SpriteView SpriteView { get; }
+        private EntityPrototypeView ProtoView { get; }
 
         public EntityUid? Entity => SpriteView.Entity;
 
@@ -141,6 +144,13 @@ namespace Content.Client.UserInterface.Controls
                 SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize),
                 OverrideDirection = Direction.South
             });
+            AddChild(ProtoView = new EntityPrototypeView
+            {
+                Visible = false,
+                Scale = new Vector2(2, 2),
+                SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize),
+                OverrideDirection = Direction.South
+            });
 
             AddChild(HoverSpriteView = new SpriteView
             {
@@ -209,10 +219,33 @@ namespace Content.Client.UserInterface.Controls
             HoverSpriteView.SetEntity(null);
         }
 
+        /// <summary>
+        /// Causes the control to display a placeholder prototype, optionally faded
+        /// </summary>
         public void SetEntity(EntityUid? ent)
         {
             SpriteView.SetEntity(ent);
+            SpriteView.Visible = true;
+            ProtoView.Visible = false;
+            UpdateButtonTexture();
+        }
+
+        /// <summary>
+        /// Causes the control to display a placeholder prototype, optionally faded
+        /// </summary>
+        public void SetPrototype(EntProtoId? proto, bool fade)
+        {
+            ProtoView.SetPrototype(proto);
+            SpriteView.Visible = false;
+            ProtoView.Visible = true;
+
             UpdateButtonTexture();
+
+            if (ProtoView.Entity is not { } ent || !fade)
+                return;
+
+            var sprites = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
+            sprites.SetColor((ent.Owner, ent.Comp1), Color.DarkGray.WithAlpha(0.65f));
         }
 
         private void UpdateButtonTexture()
index ecaddd8f98bee68ecddd97e66a26855d35d6da16..a20f4b35f83138a7097a199fc0d566d2a46693b6 100644 (file)
@@ -12,6 +12,7 @@ using Robust.Client.Player;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controllers;
 using Robust.Shared.Input;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
@@ -73,7 +74,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
     {
         if (entity.Owner != _player.LocalEntity)
             return;
-        AddHand(name, location);
+        if (_handsSystem.TryGetHand((entity.Owner, entity.Comp), name, out var hand))
+            AddHand(name, hand.Value);
     }
 
     private void OnRemoveHand(Entity<HandsComponent> entity, string name)
@@ -139,7 +141,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
         _playerHandsComponent = handsComp;
         foreach (var (name, hand) in handsComp.Comp.Hands)
         {
-            var handButton = AddHand(name, hand.Location);
+            var handButton = AddHand(name, hand);
 
             if (_handsSystem.TryGetHeldItem(handsComp.AsNullable(), name, out var held) &&
                 _entities.TryGetComponent(held, out VirtualItemComponent? virt))
@@ -147,11 +149,25 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
                 handButton.SetEntity(virt.BlockingEntity);
                 handButton.Blocked = true;
             }
-            else
+            else if (held != null)
             {
                 handButton.SetEntity(held);
                 handButton.Blocked = false;
             }
+            else
+            {
+                if (hand.EmptyRepresentative is { } representative)
+                {
+                    // placeholder, view it
+                    SetRepresentative(handButton, representative);
+                }
+                else
+                {
+                    // otherwise empty
+                    handButton.SetEntity(null);
+                }
+                handButton.Blocked = false;
+            }
         }
 
         if (handsComp.Comp.ActiveHandId == null)
@@ -159,6 +175,11 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
         SetActiveHand(handsComp.Comp.ActiveHandId);
     }
 
+    private void SetRepresentative(HandButton handButton, EntProtoId prototype)
+    {
+        handButton.SetPrototype(prototype, true);
+    }
+
     private void HandBlocked(string handName)
     {
         if (!_handLookup.TryGetValue(handName, out var hand))
@@ -203,7 +224,12 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
             hand.Blocked = false;
         }
 
-        UpdateHandStatus(hand, entity);
+        if (_playerHandsComponent != null &&
+            _player.LocalSession?.AttachedEntity is { } playerEntity &&
+            _handsSystem.TryGetHand((playerEntity, _playerHandsComponent), name, out var handData))
+        {
+            UpdateHandStatus(hand, entity, handData);
+        }
     }
 
     private void OnItemRemoved(string name, EntityUid entity)
@@ -212,8 +238,19 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
         if (hand == null)
             return;
 
+        if (_playerHandsComponent != null &&
+            _player.LocalSession?.AttachedEntity is { } playerEntity &&
+            _handsSystem.TryGetHand((playerEntity, _playerHandsComponent), name, out var handData))
+        {
+            UpdateHandStatus(hand, null, handData);
+            if (handData?.EmptyRepresentative is { } representative)
+            {
+                SetRepresentative(hand, representative);
+                return;
+            }
+        }
+
         hand.SetEntity(null);
-        UpdateHandStatus(hand, null);
     }
 
     private HandsContainer GetFirstAvailableContainer()
@@ -276,13 +313,13 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
             if (foldedLocation == HandUILocation.Left)
             {
                 _statusHandLeft = handControl;
-                HandsGui.UpdatePanelEntityLeft(heldEnt);
+                HandsGui.UpdatePanelEntityLeft(heldEnt, hand.Value);
             }
             else
             {
                 // Middle or right
                 _statusHandRight = handControl;
-                HandsGui.UpdatePanelEntityRight(heldEnt);
+                HandsGui.UpdatePanelEntityRight(heldEnt, hand.Value);
             }
 
             HandsGui.SetHighlightHand(foldedLocation);
@@ -295,9 +332,9 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
         return handControl;
     }
 
-    private HandButton AddHand(string handName, HandLocation location)
+    private HandButton AddHand(string handName, Hand hand)
     {
-        var button = new HandButton(handName, location);
+        var button = new HandButton(handName, hand.Location);
         button.StoragePressed += StorageActivate;
         button.Pressed += HandPressed;
 
@@ -313,10 +350,16 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
             GetFirstAvailableContainer().AddButton(button);
         }
 
+        if (hand.EmptyRepresentative is { } representative)
+        {
+            SetRepresentative(button, representative);
+        }
+        UpdateHandStatus(button, null, hand);
+
         // If we don't have a status for this hand type yet, set it.
         // This means we have status filled by default in most scenarios,
         // otherwise the user'd need to switch hands to "activate" the hands the first time.
-        if (location.GetUILocation() == HandUILocation.Left)
+        if (hand.Location.GetUILocation() == HandUILocation.Left)
             _statusHandLeft ??= button;
         else
             _statusHandRight ??= button;
@@ -480,12 +523,12 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
         }
     }
 
-    private void UpdateHandStatus(HandButton hand, EntityUid? entity)
+    private void UpdateHandStatus(HandButton hand, EntityUid? entity, Hand? handData)
     {
         if (hand == _statusHandLeft)
-            HandsGui?.UpdatePanelEntityLeft(entity);
+            HandsGui?.UpdatePanelEntityLeft(entity, handData);
 
         if (hand == _statusHandRight)
-            HandsGui?.UpdatePanelEntityRight(entity);
+            HandsGui?.UpdatePanelEntityRight(entity, handData);
     }
 }
index b3650136e7ad2519a3fb63c5d730f3dccbad2875..f6802de28fb259d61f80dad42a18fc657ea5cd95 100644 (file)
@@ -19,14 +19,14 @@ public sealed partial class HotbarGui : UIWidget
         LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
     }
 
-    public void UpdatePanelEntityLeft(EntityUid? entity)
+    public void UpdatePanelEntityLeft(EntityUid? entity, Hand? hand)
     {
-        StatusPanelLeft.Update(entity);
+        StatusPanelLeft.Update(entity, hand);
     }
 
-    public void UpdatePanelEntityRight(EntityUid? entity)
+    public void UpdatePanelEntityRight(EntityUid? entity, Hand? hand)
     {
-        StatusPanelRight.Update(entity);
+        StatusPanelRight.Update(entity, hand);
     }
 
     public void SetHighlightHand(HandUILocation? hand)
index f7f6f2b561370f83cde1e41767f92b6010376b95..dcc6221e46d9ba559988cea1dad4a5a957fbb468 100644 (file)
@@ -17,6 +17,7 @@ public sealed partial class ItemStatusPanel : Control
     [Dependency] private readonly IEntityManager _entityManager = default!;
 
     [ViewVariables] private EntityUid? _entity;
+    [ViewVariables] private Hand? _hand;
 
     // Tracked so we can re-run SetSide() if the theme changes.
     private HandUILocation _side;
@@ -101,29 +102,45 @@ public sealed partial class ItemStatusPanel : Control
     protected override void FrameUpdate(FrameEventArgs args)
     {
         base.FrameUpdate(args);
-        UpdateItemName();
+        UpdateItemName(_hand);
     }
 
-    public void Update(EntityUid? entity)
+    public void Update(EntityUid? entity, Hand? hand)
     {
-        ItemNameLabel.Visible = entity != null;
-        NoItemLabel.Visible = entity == null;
+        if (entity == _entity && hand == _hand)
+            return;
 
+        _hand = hand;
         if (entity == null)
         {
-            ItemNameLabel.Text = "";
             ClearOldStatus();
             _entity = null;
+
+            if (hand?.EmptyLabel is { } label)
+            {
+                ItemNameLabel.Visible = true;
+                NoItemLabel.Visible = false;
+
+                ItemNameLabel.Text = Loc.GetString(label);
+            }
+            else
+            {
+                ItemNameLabel.Visible = false;
+                NoItemLabel.Visible = true;
+
+                ItemNameLabel.Text = "";
+            }
+
             return;
         }
 
-        if (entity != _entity)
-        {
-            _entity = entity.Value;
-            BuildNewEntityStatus();
+        ItemNameLabel.Visible = true;
+        NoItemLabel.Visible = false;
 
-            UpdateItemName();
-        }
+        _entity = entity.Value;
+        BuildNewEntityStatus();
+
+        UpdateItemName(hand);
     }
 
     public void UpdateHighlight(bool highlight)
@@ -131,14 +148,14 @@ public sealed partial class ItemStatusPanel : Control
         HighlightPanel.Visible = highlight;
     }
 
-    private void UpdateItemName()
+    private void UpdateItemName(Hand? hand)
     {
         if (_entity == null)
             return;
 
         if (!_entityManager.TryGetComponent<MetaDataComponent>(_entity, out var meta) || meta.Deleted)
         {
-            Update(null);
+            Update(null, hand);
             return;
         }
 
index 74c856a6f17a8eff1ea353e02b8a60b7a521415e..dd69fe4e13804bb24cee14949243afac42266879 100644 (file)
@@ -276,8 +276,8 @@ namespace Content.Server.Construction
                     if(!insertStep.EntityValid(insert, EntityManager, Factory))
                         return HandleResult.False;
 
-                    // Unremovable items can't be inserted, unless they are a lingering stack
-                    if(HasComp<UnremoveableComponent>(insert) && (!TryComp<StackComponent>(insert, out var comp) || !comp.Lingering))
+                    // Unremovable items can't be inserted
+                    if(HasComp<UnremoveableComponent>(insert))
                         return HandleResult.False;
 
                     // If we're only testing whether this step would be handled by the given event, then we're done.
index 07fb1cb30b1ad31caaaefbb03680ff5b34b56020..bbd62d7a015ea3f1a7bdff352954655cf86c52d8 100644 (file)
@@ -52,7 +52,7 @@ public sealed partial class BorgSystem
 
     private void OnProvideItemStartup(EntityUid uid, ItemBorgModuleComponent component, ComponentStartup args)
     {
-        component.ProvidedContainer = Container.EnsureContainer<Container>(uid, component.ProvidedContainerId);
+        Container.EnsureContainer<Container>(uid, component.HoldingContainer);
     }
 
     private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args)
@@ -187,43 +187,43 @@ public sealed partial class BorgSystem
         if (!TryComp<HandsComponent>(chassis, out var hands))
             return;
 
+        if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
+            return;
+
         var xform = Transform(chassis);
-        foreach (var itemProto in component.Items)
+
+        for (var i = 0; i < component.Hands.Count; i++)
         {
-            EntityUid item;
+            var hand = component.Hands[i];
+            var handId = $"{uid}-hand-{i}";
 
-            if (!component.ItemsCreated)
-            {
-                item = Spawn(itemProto, xform.Coordinates);
-            }
-            else
+            _hands.AddHand((chassis, hands), handId, hand.Hand);
+            EntityUid? item = null;
+
+            if (component.StoredItems is not null)
             {
-                item = component.ProvidedContainer.ContainedEntities
-                    .FirstOrDefault(ent => Prototype(ent)?.ID == itemProto.Id);
-                if (!item.IsValid())
+                if (component.StoredItems.TryGetValue(handId, out var storedItem))
                 {
-                    Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}");
-                    continue;
+                    item = storedItem;
+                    _container.Remove(storedItem, container, force: true);
                 }
-
-                _container.Remove(item, component.ProvidedContainer, force: true);
             }
-
-            if (!item.IsValid())
+            else if (hand.Item is { } itemProto)
             {
-                Log.Debug("no valid item");
-                continue;
+                item = Spawn(itemProto, xform.Coordinates);
             }
 
-            var handId = $"{uid}-item{component.HandCounter}";
-            component.HandCounter++;
-            _hands.AddHand((chassis, hands), handId, HandLocation.Middle);
-            _hands.DoPickup(chassis, handId, item, hands);
-            EnsureComp<UnremoveableComponent>(item);
-            component.ProvidedItems.Add(handId, item);
+            if (item is { } pickUp)
+            {
+                _hands.DoPickup(chassis, handId, pickUp, hands);
+                if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null)
+                {
+                    EnsureComp<UnremoveableComponent>(pickUp);
+                }
+            }
         }
 
-        component.ItemsCreated = true;
+        Dirty(uid, component);
     }
 
     private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
@@ -234,27 +234,33 @@ public sealed partial class BorgSystem
         if (!TryComp<HandsComponent>(chassis, out var hands))
             return;
 
+        if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
+            return;
+
         if (TerminatingOrDeleted(uid))
-        {
-            foreach (var (hand, item) in component.ProvidedItems)
-            {
-                QueueDel(item);
-                _hands.RemoveHand(chassis, hand);
-            }
-            component.ProvidedItems.Clear();
             return;
-        }
 
-        foreach (var (handId, item) in component.ProvidedItems)
+        component.StoredItems ??= new();
+
+        for (var i = 0; i < component.Hands.Count; i++)
         {
-            if (LifeStage(item) <= EntityLifeStage.MapInitialized)
+            var handId = $"{uid}-hand-{i}";
+
+            if (_hands.TryGetHeldItem(chassis, handId, out var held))
             {
-                RemComp<UnremoveableComponent>(item);
-                _container.Insert(item, component.ProvidedContainer);
+                RemComp<UnremoveableComponent>(held.Value);
+                _container.Insert(held.Value, container);
+                component.StoredItems[handId] = held.Value;
             }
+            else
+            {
+                component.StoredItems.Remove(handId);
+            }
+
             _hands.RemoveHand(chassis, handId);
         }
-        component.ProvidedItems.Clear();
+
+        Dirty(uid, component);
     }
 
     /// <summary>
@@ -286,8 +292,8 @@ public sealed partial class BorgSystem
                 if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
                     continue;
 
-                if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count &&
-                    containedItemModuleComp.Items.All(itemModuleComp.Items.Contains))
+                if (containedItemModuleComp.Hands.Count == itemModuleComp.Hands.Count &&
+                    containedItemModuleComp.Hands.All(itemModuleComp.Hands.Contains))
                 {
                     if (user != null)
                         Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value);
index 61153be401078a06d530cb58ceae596c98f76b58..a24cb2df423c9d944c4c2832e4008f0b7798474f 100644 (file)
@@ -33,7 +33,7 @@ namespace Content.Server.Stack
             base.SetCount(uid, amount, component);
 
             // Queue delete stack if count reaches zero.
-            if (component.Count <= 0 && !component.Lingering)
+            if (component.Count <= 0)
                 QueueDel(uid);
         }
 
index 0469b59fd1e7de59b2eccdd91a0ab3ee192f5f96..292b17dcf16699231ce91475689121aa7915b485 100644 (file)
@@ -33,6 +33,12 @@ public sealed partial class MachineBoardComponent : Component
     public EntProtoId Prototype;
 }
 
+/// <summary>
+/// Marker component for any item that's machine board-like without necessarily being a MachineBoardComponent
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class CircuitboardComponent : Component;
+
 [DataDefinition, Serializable]
 public partial struct GenericPartInfo
 {
index 2aa97e78417ae1f586da83da639add1e97bc7e11..30422210d42670c455f21beca4152127111a619b 100644 (file)
@@ -1,6 +1,8 @@
 using Content.Shared.DisplacementMap;
 using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Whitelist;
 using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Hands.Components;
@@ -106,16 +108,45 @@ public sealed partial class HandsComponent : Component
 public partial record struct Hand
 {
     [DataField]
-    public HandLocation Location = HandLocation.Right;
+    public HandLocation Location = HandLocation.Middle;
+
+    /// <summary>
+    /// The label to be displayed for this hand when it does not contain an entity
+    /// </summary>
+    [DataField]
+    public LocId? EmptyLabel;
+
+    /// <summary>
+    /// The prototype ID of a "representative" entity prototype for what this hand could hold, used in the UI.
+    /// It is not map-initted.
+    /// </summary>
+    [DataField]
+    public EntProtoId? EmptyRepresentative;
+
+    /// <summary>
+    /// What this hand is allowed to hold
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Whitelist;
+
+    /// <summary>
+    /// What this hand is not allowed to hold
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? Blacklist;
 
     public Hand()
     {
 
     }
 
-    public Hand(HandLocation location)
+    public Hand(HandLocation location, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
     {
         Location = location;
+        EmptyLabel = emptyLabel;
+        EmptyRepresentative = emptyRepresentative;
+        Whitelist = whitelist;
+        Blacklist = blacklist;
     }
 }
 
index 4558f6c5287970860e7d8b5fb7d4c0f06785d31d..ea13004313015ecf46262bbe4bcb46a9718ed064 100644 (file)
@@ -185,6 +185,9 @@ public abstract partial class SharedHandsSystem
         if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity))
             return false;
 
+        if (!CheckWhitelists((uid, handsComp), handId, entity))
+            return false;
+
         if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container))
         {
             if (!ContainerSystem.CanRemove(entity, container))
diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Whitelist.cs
new file mode 100644 (file)
index 0000000..dbb1978
--- /dev/null
@@ -0,0 +1,15 @@
+using Content.Shared.Hands.Components;
+using Content.Shared.Whitelist;
+
+namespace Content.Shared.Hands.EntitySystems;
+
+public abstract partial class SharedHandsSystem
+{
+    private bool CheckWhitelists(Entity<HandsComponent?> ent, string handId, EntityUid toTest)
+    {
+        if (!TryGetHand(ent, handId, out var hand))
+            return false;
+
+        return _entityWhitelist.CheckBoth(toTest, hand.Value.Blacklist, hand.Value.Whitelist);
+    }
+}
index af5e82f41710dc1cdbf96a48ddad2487c1341045..8431e27658bc81fb1304a37fbaccc17feb5a5fc1 100644 (file)
@@ -7,8 +7,10 @@ using Content.Shared.Interaction;
 using Content.Shared.Inventory;
 using Content.Shared.Inventory.VirtualItem;
 using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Whitelist;
 using Robust.Shared.Containers;
 using Robust.Shared.Input.Binding;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Hands.EntitySystems;
@@ -23,6 +25,7 @@ public abstract partial class SharedHandsSystem
     [Dependency] private readonly SharedStorageSystem _storage = default!;
     [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
     [Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!;
+    [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
 
     public event Action<Entity<HandsComponent>, string, HandLocation>? OnPlayerAddHand;
     public event Action<Entity<HandsComponent>, string>? OnPlayerRemoveHand;
@@ -66,9 +69,9 @@ public abstract partial class SharedHandsSystem
     /// <summary>
     /// Adds a hand with the given container id and supplied location to the specified entity.
     /// </summary>
-    public void AddHand(Entity<HandsComponent?> ent, string handName, HandLocation handLocation)
+    public void AddHand(Entity<HandsComponent?> ent, string handName, HandLocation handLocation, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
     {
-        AddHand(ent, handName, new Hand(handLocation));
+        AddHand(ent, handName, new Hand(handLocation, emptyLabel, emptyRepresentative, whitelist, blacklist));
     }
 
     /// <summary>
index e86edb3476998f9866039b541c8721ec37f80b5d..3680a4cbde83c27c44f124b6f96e34cd882b7383 100644 (file)
@@ -1,6 +1,8 @@
-using Robust.Shared.Containers;
+using Content.Shared.Hands.Components;
+using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Silicons.Borgs.Components;
 
@@ -11,40 +13,40 @@ namespace Content.Shared.Silicons.Borgs.Components;
 public sealed partial class ItemBorgModuleComponent : Component
 {
     /// <summary>
-    /// The items that are provided.
+    /// The hands that are provided.
     /// </summary>
     [DataField(required: true)]
-    public List<EntProtoId> Items = new();
+    public List<BorgHand> Hands = new();
 
     /// <summary>
-    /// The entities from <see cref="Items"/> that were spawned.
+    /// The items stored within the hands. Null until the first time items are stored.
     /// </summary>
-    [DataField("providedItems")]
-    public SortedDictionary<string, EntityUid> ProvidedItems = new();
+    [DataField]
+    public Dictionary<string, EntityUid>? StoredItems;
 
     /// <summary>
-    /// A counter that ensures a unique
+    /// An ID for the container where items are stored when not in use.
     /// </summary>
-    [DataField("handCounter")]
-    public int HandCounter;
+    [DataField]
+    public string HoldingContainer = "holding_container";
+}
 
-    /// <summary>
-    /// Whether or not the items have been created and stored in <see cref="ProvidedContainer"/>
-    /// </summary>
-    [DataField("itemsCrated")]
-    public bool ItemsCreated;
+[DataDefinition, Serializable, NetSerializable]
+public partial record struct BorgHand
+{
+    [DataField]
+    public EntProtoId? Item;
 
-    /// <summary>
-    /// A container where provided items are stored when not being used.
-    /// This is helpful as it means that items retain state.
-    /// </summary>
-    [ViewVariables]
-    public Container ProvidedContainer = default!;
+    [DataField]
+    public Hand Hand = new();
 
-    /// <summary>
-    /// An ID for the container where provided items are stored when not used.
-    /// </summary>
-    [DataField("providedContainerId")]
-    public string ProvidedContainerId = "provided_container";
-}
+    [DataField]
+    public bool ForceRemovable = false;
 
+    public BorgHand(EntProtoId? item, Hand hand, bool forceRemovable = false)
+    {
+        Item = item;
+        Hand = hand;
+        ForceRemovable = forceRemovable;
+    }
+}
index 912089379a8b99b11b83bb02c665c4256f23e119..60b93a8da8dc8fc355a82a7c55571e7a1e1cd445 100644 (file)
@@ -352,10 +352,6 @@ namespace Content.Shared.Stacks
 
         private void OnStackStarted(EntityUid uid, StackComponent component, ComponentStartup args)
         {
-            // on client, lingering stacks that start at 0 need to be darkened
-            // on server this does nothing
-            SetCount(uid, component.Count, component);
-
             if (!TryComp(uid, out AppearanceComponent? appearance))
                 return;
 
@@ -366,7 +362,7 @@ namespace Content.Shared.Stacks
 
         private void OnStackGetState(EntityUid uid, StackComponent component, ref ComponentGetState args)
         {
-            args.State = new StackComponentState(component.Count, component.MaxCountOverride, component.Lingering);
+            args.State = new StackComponentState(component.Count, component.MaxCountOverride);
         }
 
         private void OnStackHandleState(EntityUid uid, StackComponent component, ref ComponentHandleState args)
@@ -375,7 +371,6 @@ namespace Content.Shared.Stacks
                 return;
 
             component.MaxCountOverride = cast.MaxCount;
-            component.Lingering = cast.Lingering;
             // This will change the count and call events.
             SetCount(uid, cast.Count, component);
         }
@@ -428,7 +423,7 @@ namespace Content.Shared.Stacks
                 return;
 
             // We haven't eaten the whole stack yet or are unable to eat it completely.
-            if (eaten.Comp.Count > 0 || eaten.Comp.Lingering)
+            if (eaten.Comp.Count > 0)
             {
                 args.Refresh = true;
                 return;
index 356b888606c117e199ba1a4d25bb5b950e70e3d8..453c8a737d63c0759aff54c210a99f3990f4f942 100644 (file)
@@ -34,13 +34,6 @@ namespace Content.Shared.Stacks
         [ViewVariables(VVAccess.ReadOnly)]
         public bool Unlimited { get; set; }
 
-        /// <summary>
-        /// Lingering stacks will remain present even when there are no items.
-        /// Instead, they will become transparent.
-        /// </summary>
-        [DataField("lingering"), ViewVariables(VVAccess.ReadWrite)]
-        public bool Lingering;
-
         [DataField("throwIndividually"), ViewVariables(VVAccess.ReadWrite)]
         public bool ThrowIndividually { get; set; } = false;
 
@@ -93,13 +86,10 @@ namespace Content.Shared.Stacks
         public int Count { get; }
         public int? MaxCount { get; }
 
-        public bool Lingering;
-
-        public StackComponentState(int count, int? maxCount, bool lingering)
+        public StackComponentState(int count, int? maxCount)
         {
             Count = count;
             MaxCount = maxCount;
-            Lingering = lingering;
         }
     }
 
diff --git a/Resources/Locale/en-US/robotics/borg_modules.ftl b/Resources/Locale/en-US/robotics/borg_modules.ftl
new file mode 100644 (file)
index 0000000..b6c5544
--- /dev/null
@@ -0,0 +1,12 @@
+borg-slot-cables-empty = Cables
+borg-slot-construction-empty = Construction materials
+borg-slot-circuitboards-empty = Circuitboards
+borg-slot-flatpacks-empty = Flatpacks
+borg-slot-tiles-empty = Floor tiles
+borg-slot-topicals-empty = Topicals
+borg-slot-small-containers-empty = Small containers
+borg-slot-chemical-containers-empty = Chemical containers
+borg-slot-documents-empty = Books and papers
+borg-slot-soap-empty = Soap
+borg-slot-instruments-empty = Instruments
+borg-slot-beakers-empty = Beakers
index 360fc5ffaa35ee3581eb6f9003eeb9a785b66f91..eb202e634522795f5e70ae24f9682bc6d06bddc3 100644 (file)
@@ -18,4 +18,5 @@
         Glass: 230
       chemicalComposition:
         Silicon: 20
+    - type: Circuitboard
 
index 8f2c7f010302d65eb47df952af73177af080ece8..82a149bf27119746777a85da73b95171c551b301 100644 (file)
@@ -17,6 +17,7 @@
         Glass: 230
       chemicalComposition:
         Silicon: 20
+    - type: Circuitboard
 
 - type: entity
   parent: BaseComputerCircuitboard
index 7848d987e5c3bd97f07ea197c65c30060e23f54b..7d2daaec0db9fab33f2e78f85f7ccffa405c4158 100644 (file)
@@ -17,3 +17,4 @@
       Glass: 200
     chemicalComposition:
       Silicon: 20
+  - type: Circuitboard
index 5676c4932485f07bc181654181fd132c7e93dcc3..532d0b9cca308b78631e574a40a0c754ebcefa43 100644 (file)
@@ -15,6 +15,7 @@
   - type: Tag
     tags:
     - Sheet
+    - ConstructionMaterial
   - type: Material
   - type: Damageable
     damageContainer: Inorganic
     stackType: Glass
     count: 1
 
-- type: entity
-  parent: SheetGlass
-  id: SheetGlassLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   parent: SheetGlassBase
   id: SheetRGlass
           Quantity: 0.5
         canReact: false
 
-- type: entity
-  parent: SheetRGlass
-  id: SheetRGlassLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   parent: SheetGlassBase
   id: SheetPGlass
     stackType: ReinforcedPlasmaGlass
     count: 1
 
-- type: entity
-  parent: SheetRPGlass
-  id: SheetRPGlassLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   parent: SheetGlassBase
   id: SheetUGlass
index bd22eee6911f0fc0ed687e433d46cf55770d60da..1cc4c3a5f539c5dd7a4a3df294769127133e0a1d 100644 (file)
@@ -15,6 +15,7 @@
     tags:
     - Sheet
     - Metal
+    - ConstructionMaterial
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Metallic
     stackType: Steel
     count: 1
 
-- type: entity
-  parent: SheetSteel
-  id: SheetSteelLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   parent: SheetMetalBase
   id: SheetBrass
   - type: Stack
     stackType: Plasteel
     count: 1
-
-- type: entity
-  parent: SheetPlasteel
-  id: SheetPlasteelLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
\ No newline at end of file
index 4a37e18ccf280fd48c1d2d7a5f5e10c42fc2f1aa..b10e071fd9623309f9135fd74a0575096015c2ec 100644 (file)
@@ -12,6 +12,7 @@
   - type: Tag
     tags:
     - Sheet
+    - ConstructionMaterial
   - type: Damageable
     damageContainer: Inorganic
   - type: Destructible
   - type: Tag
     tags:
     - Sheet
+    - ConstructionMaterial
 
 - type: entity
   parent: SheetPlasma
   - type: Stack
     count: 1
 
-- type: entity
-  parent: SheetPlasma
-  id: SheetPlasmaLingering0
-  name: plasma
-  suffix: 0, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   parent: SheetOtherBase
   id: SheetPlastic
     tags:
     - Plastic
     - Sheet
+    - ConstructionMaterial
   - type: Material
   - type: PhysicalComposition
     materialComposition:
index 7e8e6a5b746f5fe13630952004654e685565df70..8d9b8259da4cfd680e09ab89cf144ce71b552b22 100644 (file)
@@ -14,6 +14,7 @@
   - type: Tag
     tags:
     - Ingot
+    - ConstructionMaterial
   - type: Damageable
     damageContainer: Inorganic
     damageModifierSet: Metallic
index 969dc5997af9189fc5d0ead9855f8acc7d65342c..9eeed7af80836f5c4f95f30bb18eac5aaa8f0aab 100644 (file)
       - ClothMade
       - Gauze
       - RawMaterial
+      - ConstructionMaterial
   - type: Construction
     graph: WebObjects # not sure if I should either keep this here or just make another prototype. Will keep it here just in case.
     node: cloth
     tags:
       - ClothMade
       - RawMaterial
+      - ConstructionMaterial
   - type: Item
     heldPrefix: durathread
 
     tags:
     - Wooden
     - RawMaterial
+    - ConstructionMaterial
   - type: Extractable
     grindableSolutionName: wood
   - type: SolutionContainerManager
index 44d72cda12fda71c44bcc4a7f4e89d0f3557aab0..71e2704fce9f07a02881e0d61d6eb6471aa741b9 100644 (file)
@@ -19,6 +19,9 @@
       behaviors:
       - !type:DoActsBehavior
         acts: [ "Destruction" ]
+  - type: Tag
+    tags:
+    - ConstructionMaterial
 
 - type: entity
   parent: PartBase
@@ -81,6 +84,7 @@
   - type: Tag
     tags:
     - RodMetal1
+    - ConstructionMaterial
   - type: Sprite
     state: rods
   - type: Stack
   - type: Tag
     tags:
     - RodMetal1
+    - ConstructionMaterial
   - type: Sprite
     state: rods
   - type: Stack
     count: 1
-
-- type: entity
-  parent: PartRodMetal
-  id: PartRodMetalLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
-- type: entity
-  parent: FloorTileItemSteel
-  id: FloorTileItemSteelLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
-- type: entity
-  parent: FloorTileItemWhite
-  id: FloorTileItemWhiteLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
-- type: entity
-  parent: FloorTileItemDark
-  id: FloorTileItemDarkLingering0
-  suffix: Lingering, 0
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
index 7d944602c5667700b2e4db3fa9e11c019bef48ff..83bc2d1cec5b8e60c43b4811ad0d97e74eed4aa5 100644 (file)
@@ -11,6 +11,9 @@
       size: Tiny
     - type: Stack
       count: 1
+    - type: Tag
+      tags:
+      - ConstructionMaterial
 
 - type: entity
   id: MicroManipulatorStockPart
index 9f33e3c2398885697e93c066eaafee22eb0084b2..318c8d71502a0aa1376e7d0874532f8238897ed4 100644 (file)
   - type: Residue
     residueAdjective: residue-slippery
     residueColor: residue-blue
-
-- type: entity
-  name: soap
-  id: SoapBorg # Intended for borg internals, not slippery or food, not a container for soap reagent
-  parent: BaseItem
-  description: A Nanotrasen brand bar of soap. Smells of plasma and machines.
-  components:
-  - type: Sprite
-    sprite: Objects/Specific/Janitorial/soap.rsi
-    layers:
-    - state: nt-4
-  - type: Appearance
-  - type: Item
-    sprite: Objects/Specific/Janitorial/soap.rsi
-    storedRotation: -90
-  - type: CleansForensics
-  - type: Residue
-    residueAdjective: residue-slippery
-    residueColor: residue-grey
-  - type: Tag
-    tags:
-    - Soap
index 4a0581764627320bbc61569ae7794a483bccd662..0f0d8ae0c283a617c0d9d7b2799eabb7b93abb0e 100644 (file)
     stackType: Ointment
     count: 1
 
-- type: entity
-  id: Ointment10Lingering
-  parent: Ointment
-  suffix: 10, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   name: regenerative mesh
   description: Used to treat even the nastiest burns. Also effective against caustic burns.
     stackType: RegenerativeMesh
     count: 1
 
-- type: entity
-  parent: RegenerativeMesh
-  id: RegenerativeMeshLingering0
-  suffix: 0, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   name: bruise pack
   description: A therapeutic gel pack and bandages designed to treat blunt-force trauma.
     stackType: Brutepack
     count: 1
 
-- type: entity
-  id: Brutepack10Lingering
-  parent: Brutepack
-  suffix: 10, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   name: medicated suture
   description: A suture soaked in medicine, treats blunt-force trauma effectively and closes wounds.
     stackType: MedicatedSuture
     count: 1
 
-- type: entity
-  parent: MedicatedSuture
-  id: MedicatedSutureLingering0
-  suffix: 0, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 0
-
 - type: entity
   name: blood pack
   description: Contains a groundbreaking universal blood replacement created by Nanotrasen's advanced medical science.
     stackType: Bloodpack
     count: 1
 
-- type: entity
-  parent: Bloodpack
-  id: Bloodpack10Lingering
-  suffix: 10, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   parent: BaseHealingItem
   id: Tourniquet
   - type: Stack
     count: 1
 
-- type: entity
-  id: Gauze10Lingering
-  parent: Gauze
-  suffix: 10, Lingering
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   name: aloe cream
   description: A topical cream for burns.
index dee6033a5bd84dbf1e1ef9e657fcaa0c454e34d5..02b1afdbc55e513229794e3f57d681859030b5b5 100644 (file)
@@ -46,7 +46,7 @@
   - type: SelectableBorgModule
   - type: ContainerContainer
     containers:
-      provided_container: !type:Container { }
+      holding_container: !type:Container { }
 
 - type: entity
   parent: BaseAction
     - state: generic
     - state: icon-cables
   - type: ItemBorgModule
-    items:
-    - CableApcStackLingering10
-    - CableMVStackLingering10
-    - CableHVStackLingering10
-    - Wirecutter
-    - Crowbar
-    - trayScanner
+    hands:
+    - item: CableApcStack10
+      hand:
+        emptyRepresentative: CableApcStack10
+        emptyLabel: borg-slot-cables-empty
+        whitelist:
+          tags:
+          - CableCoil
+    - item: CableMVStack10
+      hand:
+        emptyRepresentative: CableMVStack10
+        emptyLabel: borg-slot-cables-empty
+        whitelist:
+          tags:
+          - CableCoil
+    - item: CableHVStack10
+      hand:
+        emptyRepresentative: CableHVStack10
+        emptyLabel: borg-slot-cables-empty
+        whitelist:
+          tags:
+          - CableCoil
+    - item: Wirecutter
+    - item: Crowbar
+    - item: trayScanner
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: wire-module }
 
     - state: generic
     - state: icon-fire-extinguisher
   - type: ItemBorgModule
-    items:
-    - BorgFireExtinguisher
-    - BorgHandheldGPSBasic
-    - HandheldStationMapUnpowered
-    - HandHeldMassScannerBorg
+    hands:
+    - item: BorgFireExtinguisher
+    - item: BorgHandheldGPSBasic
+    - item: HandheldStationMapUnpowered
+    - item: HandHeldMassScannerBorg
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: extinguisher-module }
 
     - state: generic
     - state: icon-tools
   - type: ItemBorgModule
-    items:
-    - Crowbar
-    - Wrench
-    - Screwdriver
-    - Wirecutter
-    - WelderIndustrial
-    - Multitool
+    hands:
+    - item: Crowbar
+    - item: Wrench
+    - item: Screwdriver
+    - item: Wirecutter
+    - item: WelderIndustrial
+    - item: Multitool
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: tool-module }
 
     - state: cargo
     - state: icon-appraisal
   - type: ItemBorgModule
-    items:
-    - AppraisalTool
-    - Pen
-    - HandLabeler
-    - RubberStampApproved
-    - RubberStampDenied
-    - RadioHandheld
+    hands:
+    - item: AppraisalTool
+    - item: Pen
+    - item: HandLabeler
+    - item: RubberStampApproved
+    - item: RubberStampDenied
+    - item: RadioHandheld
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: appraisal-module }
 
     - state: cargo
     - state: icon-mining
   - type: ItemBorgModule
-    items:
-    - MiningDrill
-    - Shovel
-    - MineralScannerUnpowered
-    - BorgOreBag
+    hands:
+    - item: MiningDrill
+    - item: Shovel
+    - item: MineralScannerUnpowered
+    - item: BorgOreBag
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: mining-module }
 
     - state: cargo
     - state: icon-mining-adv
   - type: ItemBorgModule
-    items:
-    - MiningDrillDiamond
-    - Shovel
-    - AdvancedMineralScannerUnpowered
-    - OreBagOfHolding
+    hands:
+    - item: MiningDrillDiamond
+    - item: Shovel
+    - item: AdvancedMineralScannerUnpowered
+    - item: OreBagOfHolding
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-mining-module }
 
     - state: cargo
     - state: icon-grappling-gun
   - type: ItemBorgModule
-    items:
-    - WeaponGrapplingGun
-    - BorgFireExtinguisher
-    - BorgHandheldGPSBasic
-    - HandHeldMassScannerBorg
+    hands:
+    - item: WeaponGrapplingGun
+    - item: BorgFireExtinguisher
+    - item: BorgHandheldGPSBasic
+    - item: HandHeldMassScannerBorg
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: grappling-module }
 
     - state: engineering
     - state: icon-tools-adv
   - type: ItemBorgModule
-    items:
-    - JawsOfLife
-    - PowerDrill
-    - WelderExperimental
-    - Multitool
-    - RemoteSignallerAdvanced
+    hands:
+    - item: JawsOfLife
+    - item: PowerDrill
+    - item: WelderExperimental
+    - item: Multitool
+    - item: RemoteSignallerAdvanced
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-tools-module }
 
+- type: entity
+  id: BorgModuleConstructionMaterialPlaceholder
+  parent: BaseItem
+  components:
+  - type: Sprite
+    sprite: Objects/Materials/Sheets/other.rsi
+    state: generic_materials
+
 - type: entity
   id: BorgModuleConstruction
   parent: [ BaseBorgModuleEngineering, BaseProviderBorgModule ]
     - state: engineering
     - state: icon-construction
   - type: ItemBorgModule
-    items:
-    - SheetSteelLingering0
-    - SheetGlassLingering0
-    - SheetRGlassLingering0
-    - SheetRPGlassLingering0
-    - SheetPlasteelLingering0
-    - PartRodMetalLingering0
-    - FloorTileItemSteelLingering0
-    - FloorTileItemWhiteLingering0
-    - FloorTileItemDarkLingering0
+    hands:
+    - hand:
+        emptyRepresentative: BorgModuleConstructionMaterialPlaceholder
+        emptyLabel: borg-slot-construction-empty
+        whitelist:
+          tags:
+          - ConstructionMaterial
+    - hand:
+        emptyRepresentative: BorgModuleConstructionMaterialPlaceholder
+        emptyLabel: borg-slot-construction-empty
+        whitelist:
+          tags:
+          - ConstructionMaterial
+    - hand:
+        emptyRepresentative: BorgModuleConstructionMaterialPlaceholder
+        emptyLabel: borg-slot-construction-empty
+        whitelist:
+          tags:
+          - ConstructionMaterial
+    - hand:
+        emptyRepresentative: DoorElectronics
+        emptyLabel: borg-slot-circuitboards-empty
+        whitelist:
+          components:
+          - Circuitboard
+    - hand:
+        emptyRepresentative: MicroManipulatorStockPart
+        emptyLabel: borg-slot-construction-empty
+        whitelist:
+          tags:
+          - ConstructionMaterial
+    - hand:
+        emptyRepresentative: SpaceHeaterFlatpack
+        emptyLabel: borg-slot-flatpacks-empty
+        whitelist:
+          components:
+          - Flatpack
+    - hand:
+        emptyRepresentative: FloorTileItemSteel
+        emptyLabel: borg-slot-tiles-empty
+        whitelist:
+          components:
+          - FloorTile
+    - hand:
+        emptyRepresentative: FloorTileItemWhite
+        emptyLabel: borg-slot-tiles-empty
+        whitelist:
+          components:
+          - FloorTile
+    - hand:
+        emptyRepresentative: FloorTileItemDark
+        emptyLabel: borg-slot-tiles-empty
+        whitelist:
+          components:
+          - FloorTile
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: construction-module }
 
     - state: engineering
     - state: icon-rcd
   - type: ItemBorgModule
-    items:
-    - RCDRecharging
-    - BorgFireExtinguisher
-    - BorgHandheldGPSBasic
-    - GasAnalyzer
-    - HolofanProjectorBorg
-    - GeigerCounter
+    hands:
+    - item: RCDRecharging
+    - item: BorgFireExtinguisher
+    - item: BorgHandheldGPSBasic
+    - item: GasAnalyzer
+    - item: HolofanProjectorBorg
+    - item: GeigerCounter
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: rcd-module }
 
     - state: janitor
     - state: icon-light-replacer
   - type: ItemBorgModule
-    items:
-    - LightReplacer
-    - BorgTrashBag
-    - Plunger
-    - SoapBorg
+    hands:
+    - item: LightReplacer
+    - item: BorgTrashBag
+    - item: Plunger
+    - item: SoapNT
+      hand:
+        emptyLabel: borg-slot-soap-empty
+        emptyRepresentative: SoapNT
+        whitelist:
+          tags:
+          - Soap
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: light-replacer-module }
 
     - state: janitor
     - state: icon-mop
   - type: ItemBorgModule
-    items:
-    - MopItem
-    - BorgBucket
-    - BorgSprayBottle
-    - HoloprojectorBorg
+    hands:
+    - item: MopItem
+    - item: BorgBucket
+    - item: BorgSprayBottle
+    - item: HoloprojectorBorg
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: cleaning-module }
 
     - state: janitor
     - state: icon-mop-adv
   - type: ItemBorgModule
-    items:
-    - AdvMopItem
-    - BorgMegaSprayBottle
-    - HoloprojectorBorg
-    - BorgDropper
-    - BorgBeaker
+    hands:
+    - item: AdvMopItem
+    - item: BorgMegaSprayBottle
+    - item: HoloprojectorBorg
+    - item: BorgDropper
+    - item: Beaker
+      hand:
+        emptyLabel: borg-slot-beakers-empty
+        emptyRepresentative: Beaker
+        whitelist:
+          tags:
+          - GlassBeaker
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-cleaning-module }
 
     - state: medical
     - state: icon-diagnosis
   - type: ItemBorgModule
-    items:
-    - HandheldHealthAnalyzerUnpowered
-    - ClothingNeckStethoscope
+    hands:
+    - item: HandheldHealthAnalyzerUnpowered
+    - item: ClothingNeckStethoscope
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: diagnosis-module }
 
     - state: medical
     - state: icon-treatment
   - type: ItemBorgModule
-    items:
-    - HandheldHealthAnalyzerUnpowered
-    - Gauze10Lingering
-    - Brutepack10Lingering
-    - Ointment10Lingering
-    - Bloodpack10Lingering
-    - RegenerativeMeshLingering0
-    - MedicatedSutureLingering0
+    hands:
+    - item: HandheldHealthAnalyzerUnpowered
+    - item: Gauze
+      hand:
+        emptyLabel: borg-slot-topicals-empty
+        emptyRepresentative: Gauze
+        whitelist:
+          components:
+          - Healing
+    - item: Brutepack
+      hand:
+        emptyLabel: borg-slot-topicals-empty
+        emptyRepresentative: Brutepack
+        whitelist:
+          components:
+          - Healing
+    - item: Ointment
+      hand:
+        emptyLabel: borg-slot-topicals-empty
+        emptyRepresentative: Ointment
+        whitelist:
+          components:
+          - Healing
+    - item: Bloodpack
+      hand:
+        emptyLabel: borg-slot-topicals-empty
+        emptyRepresentative: Bloodpack
+        whitelist:
+          components:
+          - Healing
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: treatment-module }
 
     - state: medical
     - state: icon-defib
   - type: ItemBorgModule
-    items:
-    - HandheldHealthAnalyzerUnpowered
-    - DefibrillatorOneHandedUnpowered
-    - BorgFireExtinguisher
-    - BorgHandheldGPSBasic
-    - HandLabeler
+    hands:
+    - item: HandheldHealthAnalyzerUnpowered
+    - item: DefibrillatorOneHandedUnpowered
+    - item: BorgFireExtinguisher
+    - item: BorgHandheldGPSBasic
+    - item: HandLabeler
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: defib-module }
 
     - state: medical
     - state: icon-chem
   - type: ItemBorgModule
-    items:
-    - HandheldHealthAnalyzerUnpowered
-    - Syringe
-    - BorgDropper
-    - BorgVial
-    - BorgVial
-    - BorgVial
+    hands:
+    - item: HandheldHealthAnalyzerUnpowered
+    - item: Syringe
+    - item: BorgDropper
+    - item: BaseChemistryEmptyVial
+      hand:
+        emptyLabel: borg-slot-small-containers-empty
+        emptyRepresentative: BaseChemistryEmptyVial
+        whitelist:
+          components:
+          - FitsInDispenser
+    - item: BaseChemistryEmptyVial
+      hand:
+        emptyLabel: borg-slot-small-containers-empty
+        emptyRepresentative: BaseChemistryEmptyVial
+        whitelist:
+          components:
+          - FitsInDispenser
+    - item: BaseChemistryEmptyVial
+      hand:
+        emptyLabel: borg-slot-small-containers-empty
+        emptyRepresentative: BaseChemistryEmptyVial
+        whitelist:
+          components:
+          - FitsInDispenser
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: chem-module }
 
     - state: medical
     - state: icon-chemist
   - type: ItemBorgModule
-    items:
-    - HandheldHealthAnalyzerUnpowered
-    - BorgHypo
-    - Syringe
-    - BorgDropper
-    - BorgBeaker
-    - BorgBeaker
+    hands:
+    - item: HandheldHealthAnalyzerUnpowered
+    - item: BorgHypo
+    - item: Syringe
+    - item: BorgDropper
+    - item: Beaker
+      hand:
+        emptyLabel: borg-slot-chemical-containers-empty
+        emptyRepresentative: Beaker
+        whitelist:
+          components:
+          - FitsInDispenser
+          tags:
+          - ChemDispensable
+    - item: Beaker
+      hand:
+        emptyLabel: borg-slot-chemical-containers-empty
+        emptyRepresentative: Beaker
+        whitelist:
+          components:
+          - FitsInDispenser
+          tags:
+          - ChemDispensable
+    - item: Beaker
+      hand:
+        emptyLabel: borg-slot-chemical-containers-empty
+        emptyRepresentative: Beaker
+        whitelist:
+          components:
+          - FitsInDispenser
+          tags:
+          - ChemDispensable
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-chem-module }
 
     - state: science
     - state: icon-artifacts
   - type: ItemBorgModule
-    items:
-    - NodeScanner
-    - SprayBottle
-    - GasAnalyzer
-    - BorgDropper
-    - BorgVial
-    - GeigerCounter
+    hands:
+    - item: NodeScanner
+    - item: SprayBottle
+    - item: GasAnalyzer
+    - item: BorgDropper
+    - item: BaseChemistryEmptyVial
+      hand:
+        emptyLabel: borg-slot-small-containers-empty
+        emptyRepresentative: BaseChemistryEmptyVial
+        whitelist:
+          components:
+          - FitsInDispenser
+    - item: GeigerCounter
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: node-scanner-module }
 
     - state: science
     - state: icon-anomalies
   - type: ItemBorgModule
-    items:
-    - AnomalyScanner
-    - AnomalyLocatorUnpowered
-    - AnomalyLocatorWideUnpowered
-    - HandLabeler
-    - SheetRGlassLingering0
-    - SheetRPGlassLingering0
+    hands:
+    - item: AnomalyScanner
+    - item: AnomalyLocatorUnpowered
+    - item: AnomalyLocatorWideUnpowered
+    - item: HandLabeler
+    - hand:
+        emptyRepresentative: SheetRGlass
+        emptyLabel: borg-slot-construction-empty
+        whitelist:
+          tags:
+          - ConstructionMaterial
+    - hand:
+        emptyRepresentative: SheetRPGlass
+        emptyLabel: borg-slot-construction-empty
+        whitelist:
+          tags:
+          - ConstructionMaterial
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: anomaly-module }
 
     - state: service
     - state: icon-pen
   - type: ItemBorgModule
-    items:
-    - Pen
-    #- BooksBag (Add back when hand whitelisting exists, at the moment they can only use it like an orebag.)
-    - HandLabeler
-    - RubberStampApproved
-    - RubberStampDenied
-    - BorgDropper
-    - BorgVial
+    hands:
+    - item: Pen
+    - item: BooksBag
+    - hand:
+        emptyLabel: borg-slot-documents-empty
+        emptyRepresentative: BookBase
+        whitelist:
+          tags:
+          - Book
+          - Dice
+          - Document
+          - Figurine
+          - TabletopBoard
+          - Write
+    - hand:
+        emptyLabel: borg-slot-documents-empty
+        emptyRepresentative: Paper
+        whitelist:
+          tags:
+          - Book
+          - Dice
+          - Document
+          - Figurine
+          - TabletopBoard
+          - Write
+    - item: HandLabeler
+    - item: RubberStampApproved
+    - item: RubberStampDenied
+    - item: BorgDropper
+    - item: DrinkShaker
+      hand:
+        emptyLabel: borg-slot-small-containers-empty
+        emptyRepresentative: DrinkShaker
+        whitelist:
+          components:
+          - FitsInDispenser
+    - item: BarSpoon
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: service-module }
 
     - state: service
     - state: icon-musique
   - type: ItemBorgModule
-    items:
-    - SynthesizerInstrument
-    - ElectricGuitarInstrument
-    - SaxophoneInstrument
+    hands:
+    - item: SynthesizerInstrument
+      hand:
+        emptyLabel: borg-slot-instruments-empty
+        emptyRepresentative: SynthesizerInstrument
+        whitelist:
+          components:
+          - Instrument
+    - item: ElectricGuitarInstrument
+      hand:
+        emptyLabel: borg-slot-instruments-empty
+        emptyRepresentative: ElectricGuitarInstrument
+        whitelist:
+          components:
+          - Instrument
+    - item: SaxophoneInstrument
+      hand:
+        emptyLabel: borg-slot-instruments-empty
+        emptyRepresentative: SaxophoneInstrument
+        whitelist:
+          components:
+          - Instrument
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: musical-module }
 
     - state: service
     - state: icon-gardening
   - type: ItemBorgModule
-    items:
-    - HydroponicsToolMiniHoe
-    - HydroponicsToolSpade
-    - HydroponicsToolClippers
-    - Bucket
+    hands:
+    - item: HydroponicsToolMiniHoe
+    - item: HydroponicsToolSpade
+    - item: HydroponicsToolClippers
+    - item: Bucket
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: gardening-module }
 
     - state: service
     - state: icon-harvesting
   - type: ItemBorgModule
-    items:
-    - HydroponicsToolScythe
-    - HydroponicsToolHatchet
-    - PlantBag
+    hands:
+    - item: HydroponicsToolScythe
+    - item: HydroponicsToolHatchet
+    - item: PlantBag
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: harvesting-module }
 
     - state: service
     - state: icon-clown
   - type: ItemBorgModule
-    items:
-    - BikeHorn
-    - ClownRecorder
-    - BikeHornInstrument
+    hands:
+    - item: BikeHorn
+    - item: ClownRecorder
+    - item: BikeHornInstrument
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: clowning-module }
 
     - state: service
     - state: icon-clown-adv
   - type: ItemBorgModule
-    items:
-    - HoloprojectorClownBorg
-    - BorgLauncherCreamPie
-    - ClownRecorder
-    - PushHorn
-    - BikeHornInstrument
+    hands:
+    - item: HoloprojectorClownBorg
+    - item: BorgLauncherCreamPie
+    - item: ClownRecorder
+    - item: PushHorn
+    - item: BikeHornInstrument
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-clowning-module }
 
     - state: syndicate
     - state: icon-syndicate
   - type: ItemBorgModule
-    items:
-    - WeaponPistolEchis
-    - EnergyDaggerLoud
+    hands:
+    - item: WeaponPistolEchis
+    - item: EnergyDaggerLoud
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-weapon-module }
   - type: Item
       - state: syndicate
       - state: icon-syndicate
     - type: ItemBorgModule
-      items:
-      - Crowbar
-      - Emag
-      - AccessBreaker
-      - PinpointerSyndicateNuclear
+      hands:
+      - item: Crowbar
+      - item: Emag
+      - item: AccessBreaker
+      - item: PinpointerSyndicateNuclear
     - type: BorgModuleIcon
       icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-operative-module }
 
       - state: syndicate
       - state: icon-syndicate
     - type: ItemBorgModule
-      items:
-      - CyborgEnergySwordDouble
-      - PinpointerSyndicateNuclear
+      hands:
+      - item: CyborgEnergySwordDouble
+      - item: PinpointerSyndicateNuclear
     - type: BorgModuleIcon
       icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-esword-module }
 
       - state: syndicate
       - state: icon-syndicate
     - type: ItemBorgModule
-      items:
-        - WeaponLightMachineGunL6C
-        - PinpointerSyndicateNuclear
+      hands:
+      - item: WeaponLightMachineGunL6C
+      - item: PinpointerSyndicateNuclear
     - type: BorgModuleIcon
       icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-l6c-module }
 
         - state: syndicateborgbomb
         - state: icon-bomb
     - type: ItemBorgModule
-      items:
-        - SelfDestructSeq
+      hands:
+      - item: SelfDestructSeq
     - type: BorgModuleIcon
       icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-martyr-module }
     - type: Item
     - state: xenoborg_generic
     - state: icon-xenoborg-basic
   - type: ItemBorgModule
-    items:
-    - MaterialBag
-    - PinpointerMothership
-    - HandheldGPSBasic
+    hands:
+    - item: MaterialBag
+    - item: PinpointerMothership
+    - item: HandheldGPSBasic
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-basic-module }
 
     - state: xenoborg_generic
     - state: icon-xenoborg-tools
   - type: ItemBorgModule
-    items:
-    - Crowbar
-    - Wrench
-    - Screwdriver
-    - Wirecutter
-    - Multitool
-    - RefuelingWelder
+    hands:
+    - item: Crowbar
+    - item: Wrench
+    - item: Screwdriver
+    - item: Wirecutter
+    - item: Multitool
+    - item: RefuelingWelder
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-tool-module }
 
     - state: xenoborg_engi
     - state: icon-xenoborg-access-breaker
   - type: ItemBorgModule
-    items:
-    - AccessBreaker
+    hands:
+    - item: AccessBreaker
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-access-breaker-module }
 
     - state: xenoborg_engi
     - state: icon-xenoborg-fire-extinguisher
   - type: ItemBorgModule
-    items:
-    - SelfRechargingFireExtinguisher
+    hands:
+    - item: SelfRechargingFireExtinguisher
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-extinguisher-module }
 
     - state: xenoborg_heavy
     - state: icon-xenoborg-jammer
   - type: ItemBorgModule
-    items:
-    - XenoborgRadioJammer
+    hands:
+    - item: XenoborgRadioJammer
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-jammer-module }
 
     - state: xenoborg_heavy
     - state: icon-xenoborg-laser
   - type: ItemBorgModule
-    items:
-    - XenoborgLaserGun
+    hands:
+    - item: XenoborgLaserGun
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-laser-module }
 
     - state: xenoborg_heavy
     - state: icon-xenoborg-laser2
   - type: ItemBorgModule
-    items:
-    - XenoborgHeavyLaserGun
+    hands:
+    - item: XenoborgHeavyLaserGun
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-laser2-module }
 
     - state: xenoborg_scout
     - state: icon-xenoborg-space-movement
   - type: ItemBorgModule
-    items:
-    - HandheldGPSBasic
-    - HandHeldMassScannerBorg
-    - HandheldStationMapUnpowered
-    - WeaponGrapplingGun
-    - JetpackXenoborg
+    hands:
+    - item: HandheldGPSBasic
+    - item: HandHeldMassScannerBorg
+    - item: HandheldStationMapUnpowered
+    - item: WeaponGrapplingGun
+    - item: JetpackXenoborg
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-space-movement-module }
 
     - state: xenoborg_scout
     - state: icon-xenoborg-sword
   - type: ItemBorgModule
-    items:
-    - KukriKnife
-    - JetpackXenoborg
+    hands:
+    - item: KukriKnife
+    - item: JetpackXenoborg
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword-module }
 
     - state: xenoborg_scout
     - state: icon-xenoborg-sword2
   - type: ItemBorgModule
-    items:
-    - EnergyDaggerLoudBlue
-    - JetpackXenoborg
+    hands:
+    - item: EnergyDaggerLoudBlue
+    - item: JetpackXenoborg
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-sword2-module }
 
     - state: xenoborg_stealth
     - state: icon-xenoborg-hypo
   - type: ItemBorgModule
-    items:
-    - NocturineHypo
+    hands:
+    - item: NocturineHypo
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-hypo-module }
 
     - state: xenoborg_stealth
     - state: icon-xenoborg-projector
   - type: ItemBorgModule
-    items:
-    - ChameleonProjector
+    hands:
+    - item: ChameleonProjector
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-projector-module }
 
     - state: xenoborg_stealth
     - state: icon-xenoborg-cloak
   - type: ItemBorgModule
-    items:
-    - CloakingDevice
+    hands:
+    - item: CloakingDevice
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-eye-module }
 
     - state: xenoborg_stealth
     - state: icon-xenoborg-cloak2
   - type: ItemBorgModule
-    items:
-    - SuperCloakingDevice
+    hands:
+    - item: SuperCloakingDevice
   - type: BorgModuleIcon
     icon: { sprite: Interface/Actions/actions_borg.rsi, state: xenoborg-eye2-module }
index bc8207b673bc1401ab648cb5e1117ebdaab9b226..21b3742d0244c3aa2a68af7bd42efe936b5fd0b3 100644 (file)
     solution: beaker
   - type: DnaSubstanceTrace
 
-- type: entity
-  parent: BaseItem
-  id: BorgVial
-  name: integrated vial
-  description: An internal compartment installed into a cyborg. Rated for 30 units of any liquid.
-  components:
-  # All this shit is here to avoid inheriting breakable, since borgs can't replace broken vials.
-  - type: Sprite
-    sprite: Objects/Specific/Chemistry/vial.rsi
-    layers:
-      - state: vial-1
-      - state: vial-1-1
-        map: ["enum.SolutionContainerLayers.Fill"]
-        visible: false
-  - type: Appearance
-  - type: SolutionContainerVisuals
-    maxFillLevels: 6
-    fillBaseName: vial-1-
-    inHandsMaxFillLevels: 4
-    inHandsFillBaseName: -fill-
-  - type: Drink
-    solution: beaker
-  - type: SolutionContainerManager
-    solutions:
-      beaker:
-        maxVol: 30
-  - type: MixableSolution
-    solution: beaker
-  - type: RefillableSolution
-    solution: beaker
-  - type: DrainableSolution
-    solution: beaker
-  - type: ExaminableSolution
-    solution: beaker
-    exactVolume: true
-  - type: DrawableSolution
-    solution: beaker
-  - type: SolutionTransfer
-    maxTransferAmount: 30
-    canChangeTransferAmount: true
-  - type: SolutionItemStatus
-    solution: beaker
-  - type: UserInterface
-    interfaces:
-      enum.TransferAmountUiKey.Key:
-        type: TransferAmountBoundUserInterface
-  - type: Item
-    size: Tiny
-    sprite: Objects/Specific/Chemistry/vial.rsi
-    shape:
-    - 0,0,0,0
-  - type: MeleeWeapon
-    soundNoDamage:
-      path: "/Audio/Effects/Fluids/splat.ogg"
-    damage:
-      types:
-        Blunt: 0
-
 - type: entity
   id: VestineChemistryVial
   parent: BaseChemistryEmptyVial
index 70ea8080ae5d4fc637a12235e38df6a7d81c4928..62c533bff1ff2caab033913a0a4c68f225a6459a 100644 (file)
     price: 30
   - type: DnaSubstanceTrace
 
-- type: entity
-  parent: BaseItem
-  id: BorgBeaker
-  name: integrated beaker
-  description: An internal compartment installed into a cyborg. Rated for 50 units of any liquid.
-  components:
-  # 3 morbillion components are to avoid inheriting breakable since borgs can't replace beakers.
-  - type: Tag
-    tags:
-    - GlassBeaker
-  - type: Sprite
-    sprite: Objects/Specific/Chemistry/beaker.rsi
-    layers:
-      - state: beaker
-      - state: beaker1
-        map: ["enum.SolutionContainerLayers.Fill"]
-        visible: false
-  - type: Item
-    sprite: Objects/Specific/Chemistry/beaker.rsi
-  - type: MeleeWeapon
-    soundNoDamage:
-      path: "/Audio/Effects/Fluids/splat.ogg"
-    damage:
-      types:
-        Blunt: 0
-  - type: SolutionContainerManager
-    solutions:
-      beaker:
-        maxVol: 50
-  - type: MixableSolution
-    solution: beaker
-  - type: FitsInDispenser
-    solution: beaker
-  - type: RefillableSolution
-    solution: beaker
-  - type: DrainableSolution
-    solution: beaker
-  - type: ExaminableSolution
-    solution: beaker
-    exactVolume: true
-  - type: DrawableSolution
-    solution: beaker
-  - type: InjectableSolution
-    solution: beaker
-  - type: SolutionTransfer
-    canChangeTransferAmount: true
-  - type: SolutionItemStatus
-    solution: beaker
-  - type: UserInterface
-    interfaces:
-      enum.TransferAmountUiKey.Key:
-        type: TransferAmountBoundUserInterface
-  - type: Drink
-    solution: beaker
-  - type: Appearance
-  - type: SolutionContainerVisuals
-    maxFillLevels: 6
-    fillBaseName: beaker
-
 - type: entity
   parent: BaseItem
   id: BaseBeakerMetallic
index 4c24117caab8a291985e87a8ab186b9ed59a046b..d8b05addca3640ac1b000a0f1adf30ad09a7e950 100644 (file)
   - type: Stack
     count: 10
 
-- type: entity
-  parent: CableHVStack10
-  id: CableHVStackLingering10
-  suffix: Lingering, 10
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   parent: CableHVStack
   id: CableHVStack1
   - type: Stack
     count: 10
 
-- type: entity
-  parent: CableMVStack10
-  id: CableMVStackLingering10
-  suffix: Lingering, 10
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   parent: CableMVStack
   id: CableMVStack1
     - type: Stack
       count: 10
 
-- type: entity
-  parent: CableApcStack10
-  id: CableApcStackLingering10
-  suffix: Lingering, 10
-  components:
-  - type: Stack
-    lingering: true
-    count: 10
-
 - type: entity
   parent: CableApcStack
   id: CableApcStack1
index 5d34f0f30cb5a4c1f93f0e25a4a73e986fb20b7f..95b261466f90970a471d529eca56e9b9889ee87a 100644 (file)
 - type: Tag
   id: ComputerTelevisionCircuitboard
 
+- type: Tag
+  id: ConstructionMaterial
+
 - type: Tag
   id: ConveyorAssembly