rewrite borg hand code (#3126)

* fully rework borg hands

update modules

* not real

* fix plant bag

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas 2025-03-06 01:45:33 +00:00 committed by GitHub
parent ff692ed87e
commit 0201167941
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 466 additions and 283 deletions

View File

@ -4,7 +4,7 @@ using Content.Shared.Interaction.Components;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Content.Shared._NF.Interaction.Components; // Frontier
using Content.Shared._NF.Silicons.Borgs; // Frontier
namespace Content.Server.Silicons.Borgs;
@ -198,7 +198,6 @@ public sealed partial class BorgSystem
if (!component.ItemsCreated)
{
item = Spawn(itemProto, xform.Coordinates);
_interaction.DoContactInteraction(chassis, item); // DeltaV - give items fibers before they might be dropped
}
else
{
@ -227,63 +226,6 @@ public sealed partial class BorgSystem
component.ProvidedItems.Add(handId, item);
}
// Frontier: droppable cyborg items
foreach (var itemProto in component.DroppableItems)
{
EntityUid item;
if (!component.ItemsCreated)
{
item = Spawn(itemProto.ID, xform.Coordinates);
var placeComp = EnsureComp<HandPlaceholderRemoveableComponent>(item);
placeComp.Whitelist = itemProto.Whitelist;
placeComp.Prototype = itemProto.ID;
Dirty(item, placeComp);
}
else
{
item = component.ProvidedContainer.ContainedEntities
.FirstOrDefault(ent => _whitelistSystem.IsWhitelistPassOrNull(itemProto.Whitelist, ent) || TryComp<HandPlaceholderComponent>(ent, out var placeholder));
if (!item.IsValid())
{
Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}");
continue;
}
// Just in case, make sure the borg can't drop the placeholder.
if (!HasComp<HandPlaceholderComponent>(item))
{
var placeComp = EnsureComp<HandPlaceholderRemoveableComponent>(item);
placeComp.Whitelist = itemProto.Whitelist;
placeComp.Prototype = itemProto.ID;
Dirty(item, placeComp);
}
}
if (!item.IsValid())
{
Log.Debug("no valid item");
continue;
}
var handId = $"{uid}-item{component.HandCounter}";
component.HandCounter++;
_hands.AddHand(chassis, handId, HandLocation.Middle, hands);
_hands.DoPickup(chassis, hands.Hands[handId], item, hands);
if (hands.Hands[handId].HeldEntity != item)
{
// If we didn't pick up our expected item, delete the hand. No free hands!
_hands.RemoveHand(chassis, handId);
}
else if (HasComp<HandPlaceholderComponent>(item))
{
// Placeholders can't be put down, must be changed after picked up (otherwise it'll fail to pick up)
EnsureComp<UnremoveableComponent>(item);
}
component.DroppableProvidedItems.Add(handId, (item, itemProto));
}
// End Frontier: droppable cyborg items
component.ItemsCreated = true;
}
@ -303,14 +245,6 @@ public sealed partial class BorgSystem
_hands.RemoveHand(chassis, hand, hands);
}
component.ProvidedItems.Clear();
// Frontier: droppable items
foreach (var (hand, item) in component.DroppableProvidedItems)
{
QueueDel(item.Item1);
_hands.RemoveHand(chassis, hand, hands);
}
component.DroppableProvidedItems.Clear();
// End Frontier: droppable items
return;
}
@ -324,20 +258,6 @@ public sealed partial class BorgSystem
_hands.RemoveHand(chassis, handId, hands);
}
component.ProvidedItems.Clear();
// Frontier: remove all items from borg hands directly, not from the provided items set
foreach (var (handId, _) in component.DroppableProvidedItems)
{
_hands.TryGetHand(chassis, handId, out var hand, hands);
if (hand?.HeldEntity != null)
{
RemComp<UnremoveableComponent>(hand.HeldEntity.Value);
_container.Insert(hand.HeldEntity.Value, component.ProvidedContainer);
}
_hands.RemoveHand(chassis, handId, hands);
}
component.DroppableProvidedItems.Clear();
// End Frontier
}
/// <summary>
@ -362,18 +282,22 @@ public sealed partial class BorgSystem
return false;
}
// Frontier - event for DroppableBorgModule to use
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))
{
var droppableComparer = new DroppableBorgItemComparer(); // Frontier: cached comparer
foreach (var containedModuleUid in component.ModuleContainer.ContainedEntities)
{
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
continue;
if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count &&
containedItemModuleComp.DroppableItems.Count == itemModuleComp.DroppableItems.Count && // Frontier
containedItemModuleComp.Items.All(itemModuleComp.Items.Contains) &&
containedItemModuleComp.DroppableItems.All(x => itemModuleComp.DroppableItems.Contains(x, droppableComparer))) // Frontier
containedItemModuleComp.Items.All(itemModuleComp.Items.Contains))
{
if (user != null)
Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value);
@ -385,30 +309,6 @@ public sealed partial class BorgSystem
return true;
}
// Frontier: droppable borg item comparator
private sealed class DroppableBorgItemComparer : IEqualityComparer<DroppableBorgItem>
{
public bool Equals(DroppableBorgItem? x, DroppableBorgItem? y)
{
// Same object (or both null)
if (ReferenceEquals(x, y))
return true;
// One-side null
if (x == null || y == null)
return false;
// Otherwise, use EntProtoId of item
return x.ID == y.ID;
}
public int GetHashCode(DroppableBorgItem obj)
{
if (obj is null)
return 0;
return obj.ID.GetHashCode();
}
}
// End Frontier
/// <summary>
/// Check if a module can be removed from a borg.
/// </summary>

View File

@ -1,5 +1,4 @@
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
@ -15,27 +14,15 @@ public sealed partial class ItemBorgModuleComponent : Component
/// <summary>
/// The items that are provided.
/// </summary>
[DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))] // Frontier: removed
[DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>), required: true)]
public List<string> Items = new();
/// <summary>
/// Frontier: The droppable items that are provided.
/// </summary>
[DataField]
public List<DroppableBorgItem> DroppableItems = new();
/// <summary>
/// The entities from <see cref="Items"/> that were spawned.
/// </summary>
[DataField("providedItems")]
public SortedDictionary<string, EntityUid> ProvidedItems = new();
/// <summary>
/// The entities from <see cref="Items"/> that were spawned.
/// </summary>
[DataField("droppableProvidedItems")]
public SortedDictionary<string, (EntityUid, DroppableBorgItem)> DroppableProvidedItems = new();
/// <summary>
/// A counter that ensures a unique
/// </summary>
@ -62,13 +49,3 @@ public sealed partial class ItemBorgModuleComponent : Component
public string ProvidedContainerId = "provided_container";
}
// Frontier: droppable borg item data definitions
[DataDefinition]
public sealed partial class DroppableBorgItem
{
[IdDataField]
public EntProtoId ID;
[DataField]
public EntityWhitelist Whitelist;
}

View File

@ -1,22 +1,45 @@
using Content.Shared._NF.Interaction.Systems;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._NF.Interaction.Components;
[RegisterComponent]
[NetworkedComponent]
/// <summary>
/// Lets this placeholder entity "pick up" items by clicking on them.
/// The picked up item will then have <see cref="HandPlaceholderRemoveableComponent"/> added, referencing this placeholder.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(HandPlaceholderSystem))]
[AutoGenerateComponentState(true)]
// When an entity with this is removed from a hand, it is replaced with a placeholder entity that blocks the hand's use until re-equipped with the same prototype.
public sealed partial class HandPlaceholderComponent : Component
{
/// <summary>
/// A whitelist to match entities that this should accept.
/// </summary>
[ViewVariables, AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;
[ViewVariables, AutoNetworkedField]
/// <summary>
/// The prototype to use for placeholder icon visuals.
/// </summary>
[DataField, AutoNetworkedField]
public EntProtoId? Prototype;
}
/// <summary>
/// The source entity that this placeholder gets stored in when an item is picked up.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Source;
/// <summary>
/// The container on <see cref="Source"/> to insert this placeholder into.
/// </summary>
[DataField, AutoNetworkedField]
public string ContainerId = string.Empty;
/// <summary>
/// Controls preventing removal from containers.
/// </summary>
[DataField, AutoNetworkedField]
public bool Enabled;
}

View File

@ -1,17 +1,22 @@
using Content.Shared.Whitelist;
using Content.Shared._NF.Interaction.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._NF.Interaction.Components;
[RegisterComponent]
[NetworkedComponent]
// When an entity with this is removed from a hand, it is replaced with a placeholder entity that blocks the hand's use until re-equipped with the same prototype.
/// <summary>
/// When an entity with this is removed from a hand, it is replaced with an existing placeholder entity.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(HandPlaceholderSystem))]
[AutoGenerateComponentState]
public sealed partial class HandPlaceholderRemoveableComponent : Component
{
[DataField]
public EntityWhitelist? Whitelist;
[DataField, AutoNetworkedField]
public EntityUid Placeholder;
[DataField]
public EntProtoId? Prototype;
/// <summary>
/// Used to prevent it incorrectly replacing with the placeholder,
/// when selecting and deselecting a module.
/// </summary>
[DataField, AutoNetworkedField]
public bool Enabled;
}

View File

@ -0,0 +1,154 @@
using Content.Shared._NF.Interaction.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._NF.Interaction.Systems;
/// <summary>
/// Handles interactions with items that swap with HandPlaceholder items.
/// </summary>
public sealed partial class HandPlaceholderSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public readonly EntProtoId<HandPlaceholderComponent> Placeholder = "HandPlaceholder";
public override void Initialize()
{
SubscribeLocalEvent<HandPlaceholderRemoveableComponent, EntGotRemovedFromContainerMessage>(OnEntityRemovedFromContainer);
SubscribeLocalEvent<HandPlaceholderComponent, AfterInteractEvent>(AfterInteract);
SubscribeLocalEvent<HandPlaceholderComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
}
/// <summary>
/// Spawns a new placeholder and ties it to an item.
/// When dropped the item will replace itself with the placeholder in its container.
/// </summary>
public void SpawnPlaceholder(BaseContainer container, EntityUid item, EntProtoId id, EntityWhitelist whitelist)
{
var placeholder = Spawn(Placeholder);
var comp = Comp<HandPlaceholderComponent>(placeholder);
comp.Prototype = id;
comp.Whitelist = whitelist;
comp.Source = container.Owner;
comp.ContainerId = container.ID;
Dirty(placeholder, comp);
var name = _proto.Index(id).Name;
_metadata.SetEntityName(placeholder, name);
SetPlaceholder(item, placeholder);
var succeeded = _container.Insert(placeholder, container, force: true);
DebugTools.Assert(succeeded, $"Failed to insert placeholder {ToPrettyString(placeholder)} into {ToPrettyString(comp.Source)}");
}
/// <summary>
/// Sets the placeholder entity for an item.
/// </summary>
public void SetPlaceholder(EntityUid item, EntityUid placeholder)
{
var comp = EnsureComp<HandPlaceholderRemoveableComponent>(item);
comp.Placeholder = placeholder;
Dirty(item, comp);
}
public void SetEnabled(EntityUid item, bool enabled)
{
if (TryComp<HandPlaceholderRemoveableComponent>(item, out var comp))
{
comp.Enabled = enabled;
Dirty(item, comp);
}
else if (TryComp<HandPlaceholderComponent>(item, out var placeholder))
{
placeholder.Enabled = enabled;
Dirty(item, placeholder);
}
}
private void SwapPlaceholder(Entity<HandPlaceholderRemoveableComponent> ent, BaseContainer container)
{
// trying to insert when deleted is an error, and only handle when it is being actually dropped
var owner = container.Owner;
if (!ent.Comp.Enabled || TerminatingOrDeleted(owner))
return;
var placeholder = ent.Comp.Placeholder;
ent.Comp.Enabled = false;
RemCompDeferred<HandPlaceholderRemoveableComponent>(ent);
SetEnabled(placeholder, false);
var succeeded = _container.Insert(placeholder, container, force: true);
DebugTools.Assert(succeeded, $"Failed to insert placeholder {ToPrettyString(placeholder)} of {ToPrettyString(ent)} into container of {ToPrettyString(owner)}");
SetEnabled(placeholder, true); // prevent dropping it now that it's in hand
}
private void OnEntityRemovedFromContainer(Entity<HandPlaceholderRemoveableComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
SwapPlaceholder(ent, args.Container);
}
private void AfterInteract(Entity<HandPlaceholderComponent> ent, ref AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || args.Target is not {} target)
return;
args.Handled = true;
TryToPickUpTarget(ent, target, args.User);
}
private void OnRemoveAttempt(Entity<HandPlaceholderComponent> ent, ref ContainerGettingRemovedAttemptEvent args)
{
if (ent.Comp.Enabled)
args.Cancel();
}
private void TryToPickUpTarget(Entity<HandPlaceholderComponent> ent, EntityUid target, EntityUid user)
{
// require items regardless of the whitelist
if (!HasComp<ItemComponent>(target) || _whitelist.IsWhitelistFail(ent.Comp.Whitelist, target))
return;
if (!TryComp<HandsComponent>(user, out var hands))
return;
// Can't get the hand we're holding this with? Something's wrong, abort. No empty hands.
if (!_hands.IsHolding(user, ent, out var hand, hands))
return;
SetPlaceholder(target, ent);
SetEnabled(target, true);
SetEnabled(ent, false); // allow inserting into the source container
if (ent.Comp.Source is {} source)
{
var container = _container.GetContainer(source, ent.Comp.ContainerId);
var succeeded = _container.Insert(ent.Owner, container, force: true);
DebugTools.Assert(succeeded, $"Failed to insert {ToPrettyString(ent)} into {container.ID} of {ToPrettyString(source)}");
}
else
{
Log.Error($"Placeholder {ToPrettyString(ent)} had no source set");
}
_hands.DoPickup(user, hand, target, hands); // Force pickup - empty hands are not okay
_interaction.DoContactInteraction(user, target); // allow for forensics and other systems to work (why does hands system not do this???)
}
}

View File

@ -1,106 +0,0 @@
using Content.Shared._NF.Interaction.Components;
using Content.Shared.Hands;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Whitelist;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._NF.Interaction.Systems;
/// <summary>
/// Handles interactions with items that spawn HandPlaceholder items.
/// </summary>
[UsedImplicitly]
public sealed partial class HandPlaceholderSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!; // DeltaV
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
{
SubscribeLocalEvent<HandPlaceholderRemoveableComponent, EntGotRemovedFromContainerMessage>(OnEntityRemovedFromContainer);
SubscribeLocalEvent<HandPlaceholderComponent, AfterInteractEvent>(AfterInteract);
SubscribeLocalEvent<HandPlaceholderComponent, BeforeRangedInteractEvent>(BeforeRangedInteract);
}
private void OnEntityRemovedFromContainer(Entity<HandPlaceholderRemoveableComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
if (!TerminatingOrDeleted(args.Container.Owner))
SpawnAndPickUpPlaceholder(ent, args.Container);
RemCompDeferred<HandPlaceholderRemoveableComponent>(ent);
}
private void SpawnAndPickUpPlaceholder(Entity<HandPlaceholderRemoveableComponent> ent, BaseContainer container)
{
if (_net.IsServer)
{
var placeholder = Spawn("HandPlaceholder");
if (TryComp<HandPlaceholderComponent>(placeholder, out var placeComp))
{
placeComp.Whitelist = ent.Comp.Whitelist;
placeComp.Prototype = ent.Comp.Prototype;
Dirty(placeholder, placeComp);
}
if (_proto.TryIndex(ent.Comp.Prototype, out var itemProto))
_metadata.SetEntityName(placeholder, itemProto.Name);
if (!_container.Insert(placeholder, container, force: true))
QueueDel(placeholder);
}
}
private void BeforeRangedInteract(Entity<HandPlaceholderComponent> ent, ref BeforeRangedInteractEvent args)
{
if (args.Target == null || args.Handled)
return;
args.Handled = true;
TryToPickUpTarget(ent, args.Target.Value, args.User);
}
private void AfterInteract(Entity<HandPlaceholderComponent> ent, ref AfterInteractEvent args)
{
if (args.Target == null || args.Handled)
return;
args.Handled = true;
TryToPickUpTarget(ent, args.Target.Value, args.User);
}
private void TryToPickUpTarget(Entity<HandPlaceholderComponent> ent, EntityUid target, EntityUid user)
{
if (_whitelist.IsWhitelistFail(ent.Comp.Whitelist, target))
return;
// Can't get the hand we're holding this with? Something's wrong, abort. No empty hands.
if (!_hands.IsHolding(user, ent, out var hand))
return;
// Cache the whitelist/prototype, entity might be deleted.
var whitelist = ent.Comp.Whitelist;
var prototype = ent.Comp.Prototype;
if (_net.IsServer)
Del(ent);
_hands.DoPickup(user, hand, target); // Force pickup - empty hands are not okay
var placeComp = EnsureComp<HandPlaceholderRemoveableComponent>(target);
placeComp.Whitelist = whitelist;
placeComp.Prototype = prototype;
Dirty(target, placeComp);
_interaction.DoContactInteraction(user, target); // DeltaV - borgs picking up items leaves fibers
}
}

View File

@ -0,0 +1,49 @@
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._NF.Silicons.Borgs;
/// <summary>
/// Uses placeholder entities to give borgs "hands" that are whitelisted for a certain kind of item.
/// The items in it can be dropped and picked up if they match its whitelist.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(DroppableBorgModuleSystem))]
public sealed partial class DroppableBorgModuleComponent : Component
{
/// <summary>
/// The items to spawn in borg hands.
/// </summary>
[DataField(required: true)]
public List<DroppableBorgItem> Items = new();
/// <summary>
/// The ID of the container to add that stores items when not in hands.
/// </summary>
[DataField]
public string ContainerId = "nf-droppable-items";
/// <summary>
/// The ID of the container to add that stores item placeholders when not in hands.
/// </summary>
[DataField]
public string PlaceholderContainerId = "nf-item-placeholders";
}
[DataDefinition]
public sealed partial class DroppableBorgItem
{
/// <summary>
/// The entity to spawn and use for the placeholder sprite.
/// </summary>
[DataField(required: true)]
public EntProtoId Id;
/// <summary>
/// A whitelist that items must match to be picked up by the placeholder.
/// Regardless of this whitelist entities must have <c>ItemComponent</c> to be picked up.
/// </summary>
[DataField(required: true)]
public EntityWhitelist Whitelist;
}

View File

@ -0,0 +1,173 @@
using Content.Shared._NF.Interaction.Components;
using Content.Shared._NF.Interaction.Systems;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Popups;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Shared._NF.Silicons.Borgs;
public sealed class DroppableBorgModuleSystem : EntitySystem
{
[Dependency] private readonly HandPlaceholderSystem _placeholder = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DroppableBorgModuleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DroppableBorgModuleComponent, BorgCanInsertModuleEvent>(OnCanInsertModule);
SubscribeLocalEvent<DroppableBorgModuleComponent, BorgModuleSelectedEvent>(OnModuleSelected);
SubscribeLocalEvent<DroppableBorgModuleComponent, BorgModuleUnselectedEvent>(OnModuleUnselected);
}
private void OnMapInit(Entity<DroppableBorgModuleComponent> ent, ref MapInitEvent args)
{
_container.EnsureContainer<Container>(ent, ent.Comp.ContainerId);
var placeholders = _container.EnsureContainer<Container>(ent, ent.Comp.PlaceholderContainerId);
foreach (var slot in ent.Comp.Items)
{
// only the server runs mapinit, this wont make clientside entities
var successful = TrySpawnInContainer(slot.Id, ent, ent.Comp.ContainerId, out var item);
// this would only fail if the current entity is being terminated, which is impossible for mapinit
DebugTools.Assert(successful, $"Somehow failed to insert {ToPrettyString(item)} into {ToPrettyString(ent)}");
_placeholder.SpawnPlaceholder(placeholders, item!.Value, slot.Id, slot.Whitelist);
}
Dirty(ent);
}
private void OnCanInsertModule(Entity<DroppableBorgModuleComponent> ent, ref BorgCanInsertModuleEvent args)
{
if (args.Cancelled)
return;
foreach (var module in args.Chassis.Comp.ModuleContainer.ContainedEntities)
{
if (!TryComp<DroppableBorgModuleComponent>(module, out var comp))
continue;
if (!SameItems(comp.Items, ent.Comp.Items))
continue;
if (args.User is {} user)
_popup.PopupEntity(Loc.GetString("borg-module-duplicate"), args.Chassis, user); // event is only raised by server so not using PopupClient
args.Cancelled = true;
return;
}
}
private void OnModuleSelected(Entity<DroppableBorgModuleComponent> ent, ref BorgModuleSelectedEvent args)
{
var chassis = args.Chassis;
if (!TryComp<HandsComponent>(chassis, out var hands))
return;
var container = _container.GetContainer(ent, ent.Comp.ContainerId);
var items = container.ContainedEntities;
for (int i = 0; i < ent.Comp.Items.Count; i++)
{
var item = items[0]; // the contained items will gradually go to 0
var handId = HandId(ent, i);
_hands.AddHand(chassis, handId, HandLocation.Middle, hands);
var hand = hands.Hands[handId];
_hands.DoPickup(chassis, hand, item, hands);
if (hand.HeldEntity != item)
{
Log.Error($"Failed to pick up {ToPrettyString(item)} into hand {handId} of {ToPrettyString(chassis)}, it holds {ToPrettyString(hand.HeldEntity)}");
// If we didn't pick up our expected item, delete the hand. No free hands!
_hands.RemoveHand(chassis, handId, hands);
}
else
{
_interaction.DoContactInteraction(chassis, item); // for potential forensics or other systems (why does hands system not do this)
_placeholder.SetEnabled(item, true);
}
}
}
private void OnModuleUnselected(Entity<DroppableBorgModuleComponent> ent, ref BorgModuleUnselectedEvent args)
{
var chassis = args.Chassis;
if (!TryComp<HandsComponent>(chassis, out var hands))
return;
if (TerminatingOrDeleted(ent))
{
for (int i = 0; i < ent.Comp.Items.Count; i++)
{
var handId = HandId(ent, i);
_hands.TryGetHand(chassis, handId, out var hand, hands);
if (hand?.HeldEntity is {} item)
QueueDel(item);
else
Log.Error($"Borg {ToPrettyString(chassis)} terminated with empty hand {i} in {ToPrettyString(ent)}");
_hands.RemoveHand(chassis, handId, hands);
}
return;
}
var container = _container.GetContainer(ent, ent.Comp.ContainerId);
for (int i = 0; i < ent.Comp.Items.Count; i++)
{
var handId = HandId(ent, i);
_hands.TryGetHand(chassis, handId, out var hand, hands);
if (hand?.HeldEntity is {} item)
{
_placeholder.SetEnabled(item, false);
_container.Insert(item, container, force: true);
}
else
{
Log.Error($"Borg {ToPrettyString(chassis)} had an empty hand in the slot for {ent.Comp.Items[i].Id}");
}
_hands.RemoveHand(chassis, handId, hands);
}
}
/// <summary>
/// Format the hand ID for a given module and item number.
/// </summary>
private static string HandId(EntityUid uid, int i)
{
return $"nf-{uid}-item-{i}";
}
/// <summary>
/// Checks that 2 droppable modules have the same starting items.
/// Used for duplicate module check.
/// </summary>
private static bool SameItems(List<DroppableBorgItem> a, List<DroppableBorgItem> b)
{
if (a.Count != b.Count)
return false;
for (int i = 0; i < a.Count; i++)
{
if (a[i].Id != b[i].Id)
return false;
}
return true;
}
}
/// <summary>
/// Event raised on a module to check if it can be installed.
/// This should exist upstream but doesn't.
/// </summary>
[ByRefEvent]
public record struct BorgCanInsertModuleEvent(Entity<BorgChassisComponent> Chassis, EntityUid? User, bool Cancelled = false);

View File

@ -1,4 +1,4 @@
namespace Content.Server._NF.Whitelist.Components;
namespace Content.Shared._NF.Whitelist.Components;
/// <summary>
/// Whitelist component for book bags to avoid tag redefinition and collisions

View File

@ -1,4 +1,4 @@
namespace Content.Server._NF.Whitelist.Components;
namespace Content.Shared._NF.Whitelist.Components;
/// <summary>
/// Whitelist component for lighters to avoid tag redefinition and collisions

View File

@ -1,4 +1,4 @@
namespace Content.Server._NF.Whitelist.Components;
namespace Content.Shared._NF.Whitelist.Components;
/// <summary>
/// Whitelist component for ore bags to avoid tag redefinition and collisions

View File

@ -1,4 +1,4 @@
namespace Content.Server._NF.Whitelist.Components;
namespace Content.Shared._NF.Whitelist.Components;
/// <summary>
/// Whitelist component for plant bags to avoid tag redefinition and collisions

View File

@ -1,4 +1,4 @@
namespace Content.Server._NF.Whitelist.Components;
namespace Content.Shared._NF.Whitelist.Components;
/// <summary>
/// Whitelist component for shakers to avoid tag redefinition and collisions

View File

@ -133,14 +133,17 @@
layers:
- state: generic
- state: icon-fire-extinguisher
- type: ItemBorgModule
# Frontier: droppable borg items
droppableItems:
# Frontier: droppable borg items
#- type: ItemBorgModule
# items:
# - FireExtinguisher
- type: DroppableBorgModule
items:
- id: FireExtinguisher
whitelist:
tags:
- FireExtinguisher
# End Frontier
# End Frontier
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: extinguisher-module }
@ -229,13 +232,14 @@
# - OreBag # Frontier
- Crowbar
- RadioHandheld
# Frontier: droppable borg items
droppableItems:
# Frontier: droppable borg items
- type: DroppableBorgModule
items:
- id: OreBag
whitelist:
components:
- NFOreBag
# End Frontier: droppable borg items
# End Frontier: droppable borg items
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: mining-module }
@ -359,13 +363,14 @@
- MopItem
# - Bucket # Frontier
- TrashBag
# Frontier: droppable items
droppableItems:
# Frontier: droppable items
- type: DroppableBorgModule
items:
- id: Bucket
whitelist:
tags:
- Bucket
# End Frontier
# End Frontier
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: cleaning-module }
@ -456,8 +461,9 @@
# - Beaker # Frontier
- BorgDropper
- BorgHypo
# Frontier: droppable borg items
droppableItems:
# Frontier: droppable borg items
- type: DroppableBorgModule
items:
- id: Beaker
whitelist:
tags:
@ -466,7 +472,7 @@
whitelist:
tags:
- GlassBeaker
# End Frontier: droppable borg items
# End Frontier: droppable borg items
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-diagnosis-module }
@ -524,8 +530,9 @@
# - Lighter # Frontier
# - DrinkShaker # Frontier
- BorgDropper
# Frontier: droppable
droppableItems:
# Frontier: droppable items
- type: DroppableBorgModule
items:
- id: BooksBag
whitelist:
components:
@ -538,7 +545,7 @@
whitelist:
components:
- NFShaker
# End Frontier
# End Frontier
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: service-module }
@ -574,13 +581,14 @@
- HydroponicsToolSpade
- HydroponicsToolClippers
# - Bucket # Frontier
# Frontier: droppable borg items
droppableItems:
# Frontier: droppable borg items
- type: DroppableBorgModule
items:
- id: Bucket
whitelist:
tags:
- Bucket
# End Frontier
# End Frontier
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: gardening-module }
@ -598,13 +606,14 @@
- HydroponicsToolScythe
- HydroponicsToolHatchet
# - PlantBag # Frontier
# Frontier: droppable borg items
droppableItems:
# Frontier: droppable borg items
- type: DroppableBorgModule
items:
- id: PlantBag
whitelist:
components:
- NFPlantBag
# End Frontier
# End Frontier
- type: BorgModuleIcon
icon: { sprite: Interface/Actions/actions_borg.rsi, state: harvesting-module }

View File

@ -5,6 +5,5 @@
components:
- type: Item
size: Ginormous # no storage insertion visuals
- type: Unremoveable
- type: HandPlaceholder
- type: HandPlaceholderVisuals