Borg hands & hand whitelisting (#38668)

* 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
This commit is contained in:
pathetic meowmeow 2025-08-12 18:21:42 -04:00 committed by Vanessa
parent 7ba94f741b
commit 481246d170
34 changed files with 745 additions and 955 deletions

View 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;

View 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,12 +219,35 @@ 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()
{
var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath);

View 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;
using Content.Shared._NF.Interaction.Components;
@ -74,7 +75,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)
@ -140,7 +142,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))
@ -157,11 +159,25 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
handButton.Blocked = true;
}
// End Frontier - borg hand placeholder
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)
@ -169,6 +185,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))
@ -220,7 +241,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)
@ -229,8 +255,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()
@ -293,13 +330,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);
@ -312,9 +349,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;
@ -330,10 +367,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;
@ -497,12 +540,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);
}
}

View 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)

View 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;
}

View 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.

View File

@ -53,7 +53,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)
@ -188,43 +188,43 @@ public sealed partial class BorgSystem
if (!TryComp<HandsComponent>(chassis, out var hands))
return;
var xform = Transform(chassis);
foreach (var itemProto in component.Items)
{
EntityUid item;
if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
return;
if (!component.ItemsCreated)
var xform = Transform(chassis);
for (var i = 0; i < component.Hands.Count; i++)
{
var hand = component.Hands[i];
var handId = $"{uid}-hand-{i}";
_hands.AddHand((chassis, hands), handId, hand.Hand);
EntityUid? item = null;
if (component.StoredItems is not null)
{
if (component.StoredItems.TryGetValue(handId, out var storedItem))
{
item = storedItem;
_container.Remove(storedItem, container, force: true);
}
}
else if (hand.Item is { } itemProto)
{
item = Spawn(itemProto, xform.Coordinates);
}
else
if (item is { } pickUp)
{
item = component.ProvidedContainer.ContainedEntities
.FirstOrDefault(ent => Prototype(ent)?.ID == itemProto.Id);
if (!item.IsValid())
_hands.DoPickup(chassis, handId, pickUp, hands);
if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null)
{
Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}");
continue;
EnsureComp<UnremoveableComponent>(pickUp);
}
_container.Remove(item, component.ProvidedContainer, force: true);
}
if (!item.IsValid())
{
Log.Debug("no valid item");
continue;
}
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);
}
component.ItemsCreated = true;
Dirty(uid, component);
}
private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
@ -235,27 +235,33 @@ public sealed partial class BorgSystem
if (!TryComp<HandsComponent>(chassis, out var hands))
return;
if (TerminatingOrDeleted(uid))
{
foreach (var (hand, item) in component.ProvidedItems)
{
QueueDel(item);
_hands.RemoveHand(chassis, hand);
}
component.ProvidedItems.Clear();
if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
return;
}
foreach (var (handId, item) in component.ProvidedItems)
if (TerminatingOrDeleted(uid))
return;
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>
@ -281,10 +287,10 @@ public sealed partial class BorgSystem
}
// Frontier - event for DroppableBorgModule to use
var ev = new BorgCanInsertModuleEvent((uid, component), user);
RaiseLocalEvent(module, ref ev);
if (ev.Cancelled)
return false;
//var ev = new BorgCanInsertModuleEvent((uid, component), user);
//RaiseLocalEvent(module, ref ev);
//if (ev.Cancelled)
// return false;
// End Frontier
if (TryComp<ItemBorgModuleComponent>(module, out var itemModuleComp))
@ -294,9 +300,8 @@ public sealed partial class BorgSystem
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
continue;
// if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count && // Frontier: no item check
// containedItemModuleComp.Items.All(itemModuleComp.Items.Contains)) // Frontier
if (containedItemModuleComp.ModuleId == itemModuleComp.ModuleId) // Frontier: ID comparison
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);

View 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);
}

View 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
{

View 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;
}
}

View 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))

View File

@ -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);
}
}

View 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>

View 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,46 +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;
/// <summary>
/// Whether or not the items have been created and stored in <see cref="ProvidedContainer"/>
/// </summary>
[DataField("itemsCrated")]
public bool ItemsCreated;
/// <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!;
/// <summary>
/// An ID for the container where provided items are stored when not used.
/// </summary>
[DataField("providedContainerId")]
public string ProvidedContainerId = "provided_container";
/// <summary>
/// Frontier: a module ID to check for equivalence
/// </summary>
[DataField(required: true)]
public string ModuleId = default!;
[DataField]
public string HoldingContainer = "holding_container";
}
[DataDefinition, Serializable, NetSerializable]
public partial record struct BorgHand
{
[DataField]
public EntProtoId? Item;
[DataField]
public Hand Hand = new();
[DataField]
public bool ForceRemovable = false;
public BorgHand(EntProtoId? item, Hand hand, bool forceRemovable = false)
{
Item = item;
Hand = hand;
ForceRemovable = forceRemovable;
}
}

View File

@ -362,10 +362,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;
@ -376,7 +372,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)
@ -385,7 +381,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);
}
@ -438,7 +433,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;

View 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;
}
}

View File

@ -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

View File

@ -18,4 +18,5 @@
Glass: 230
chemicalComposition:
Silicon: 20
- type: Circuitboard

View File

@ -17,6 +17,7 @@
Glass: 230
chemicalComposition:
Silicon: 20
- type: Circuitboard
- type: entity
parent: BaseComputerCircuitboard

View File

@ -17,4 +17,4 @@
Glass: 200
chemicalComposition:
Silicon: 20
- type: Electronics # DeltaV
- type: Circuitboard

View File

@ -15,7 +15,7 @@
- type: Tag
tags:
- Sheet
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Material
- type: Damageable
damageContainer: Inorganic
@ -116,15 +116,6 @@
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
@ -205,15 +196,6 @@
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
@ -344,15 +326,6 @@
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

View File

@ -15,7 +15,7 @@
tags:
- Sheet
- Metal
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
@ -103,15 +103,6 @@
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
@ -239,12 +230,3 @@
- type: Stack
stackType: Plasteel
count: 1
- type: entity
parent: SheetPlasteel
id: SheetPlasteelLingering0
suffix: Lingering, 0
components:
- type: Stack
lingering: true
count: 0

View File

@ -12,7 +12,7 @@
- type: Tag
tags:
- Sheet
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Damageable
damageContainer: Inorganic
- type: Destructible
@ -118,7 +118,7 @@
- type: Tag
tags:
- Sheet
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: entity
parent: SheetPlasma
@ -141,16 +141,6 @@
- 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
@ -161,7 +151,7 @@
tags:
- Plastic
- Sheet
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Material
- type: PhysicalComposition
materialComposition:

View File

@ -14,7 +14,7 @@
- type: Tag
tags:
- Ingot
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic

View File

@ -147,7 +147,7 @@
- ClothMade
- Gauze
- RawMaterial
- BorgMaterial # DeltaV
- 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
@ -213,7 +213,7 @@
tags:
- ClothMade
- RawMaterial
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Item
heldPrefix: durathread
@ -263,7 +263,7 @@
tags:
- Wooden
- RawMaterial
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Extractable
grindableSolutionName: wood
- type: SolutionContainerManager

View File

@ -19,6 +19,9 @@
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: Tag
tags:
- ConstructionMaterial
- type: entity
parent: PartBase
@ -85,7 +88,7 @@
- type: Tag
tags:
- RodMetal1
- BorgMaterial # DeltaV
- ConstructionMaterial
- type: Sprite
state: rods
- type: Stack
@ -100,44 +103,8 @@
- type: Tag
tags:
- RodMetal1
- BorgMaterial # DeltaV
- 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

View File

@ -11,6 +11,9 @@
size: Tiny
- type: Stack
count: 1
- type: Tag
tags:
- ConstructionMaterial
- type: entity
id: MicroManipulatorStockPart

View File

@ -244,25 +244,3 @@
- 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

View File

@ -62,15 +62,6 @@
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.
@ -120,15 +111,6 @@
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.
@ -175,15 +157,6 @@
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.
@ -232,15 +205,6 @@
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.
@ -281,12 +245,12 @@
- type: entity
parent: Bloodpack
id: Bloodpack10Lingering
suffix: 10, Lingering
id: Bloodpack1
suffix: Single
components:
- type: Stack
lingering: true
count: 10
stackType: Bloodpack
count: 1
- type: entity
parent: BaseHealingItem
@ -373,15 +337,6 @@
- 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.

View File

@ -105,64 +105,6 @@
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

View File

@ -95,65 +95,6 @@
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

View File

@ -107,15 +107,6 @@
- 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
@ -172,15 +163,6 @@
- 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
@ -236,15 +218,6 @@
- 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

View File

@ -347,6 +347,9 @@
- type: Tag
id: ComputerTelevisionCircuitboard
- type: Tag
id: ConstructionMaterial
- type: Tag
id: ConveyorAssembly