From 0201167941d425b181b4ebc3d0c1a855f9b35bc2 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Thu, 6 Mar 2025 01:45:33 +0000 Subject: [PATCH] rewrite borg hand code (#3126) * fully rework borg hands update modules * not real * fix plant bag --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Silicons/Borgs/BorgSystem.Modules.cs | 118 +----------- .../Components/ItemBorgModuleComponent.cs | 27 +-- .../Components/HandPlaceholderComponent.cs | 35 +++- .../HandPlaceholderRemoveableComponent.cs | 23 ++- .../Systems/HandPlaceholderSystem.cs | 154 ++++++++++++++++ .../Systems/SharedHandPlaceholderSystem.cs | 106 ----------- .../Borgs/DroppableBorgModuleComponent.cs | 49 +++++ .../Borgs/DroppableBorgModuleSystem.cs | 173 ++++++++++++++++++ .../Components/NFBookBagComponent.cs | 2 +- .../Components/NFLighterComponent.cs | 2 +- .../Whitelist/Components/NFOreBagComponent.cs | 2 +- .../Components/NFPlantBagComponent.cs | 2 +- .../Whitelist/Components/NFShakerComponent.cs | 2 +- .../Specific/Robotics/borg_modules.yml | 53 +++--- .../Robotics/borg_hand_placeholder.yml | 1 - 15 files changed, 466 insertions(+), 283 deletions(-) create mode 100644 Content.Shared/_NF/Interaction/Systems/HandPlaceholderSystem.cs delete mode 100644 Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs create mode 100644 Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleComponent.cs create mode 100644 Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleSystem.cs rename {Content.Server => Content.Shared}/_NF/Whitelist/Components/NFBookBagComponent.cs (78%) rename {Content.Server => Content.Shared}/_NF/Whitelist/Components/NFLighterComponent.cs (78%) rename {Content.Server => Content.Shared}/_NF/Whitelist/Components/NFOreBagComponent.cs (78%) rename {Content.Server => Content.Shared}/_NF/Whitelist/Components/NFPlantBagComponent.cs (78%) rename {Content.Server => Content.Shared}/_NF/Whitelist/Components/NFShakerComponent.cs (78%) diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index f6155bf890..a42926e307 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -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(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(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(item)) - { - var placeComp = EnsureComp(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(item)) - { - // Placeholders can't be put down, must be changed after picked up (otherwise it'll fail to pick up) - EnsureComp(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(hand.HeldEntity.Value); - _container.Insert(hand.HeldEntity.Value, component.ProvidedContainer); - } - - _hands.RemoveHand(chassis, handId, hands); - } - component.DroppableProvidedItems.Clear(); - // End Frontier } /// @@ -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(module, out var itemModuleComp)) { - var droppableComparer = new DroppableBorgItemComparer(); // Frontier: cached comparer foreach (var containedModuleUid in component.ModuleContainer.ContainedEntities) { if (!TryComp(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 - { - 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 - /// /// Check if a module can be removed from a borg. /// diff --git a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs index ead55d9727..75835d0cf0 100644 --- a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs +++ b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs @@ -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 /// /// The items that are provided. /// - [DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer))] // Frontier: removed + [DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] public List Items = new(); - /// - /// Frontier: The droppable items that are provided. - /// - [DataField] - public List DroppableItems = new(); - /// /// The entities from that were spawned. /// [DataField("providedItems")] public SortedDictionary ProvidedItems = new(); - /// - /// The entities from that were spawned. - /// - [DataField("droppableProvidedItems")] - public SortedDictionary DroppableProvidedItems = new(); - /// /// A counter that ensures a unique /// @@ -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; -} diff --git a/Content.Shared/_NF/Interaction/Components/HandPlaceholderComponent.cs b/Content.Shared/_NF/Interaction/Components/HandPlaceholderComponent.cs index 51611889e4..7cff040897 100644 --- a/Content.Shared/_NF/Interaction/Components/HandPlaceholderComponent.cs +++ b/Content.Shared/_NF/Interaction/Components/HandPlaceholderComponent.cs @@ -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] +/// +/// Lets this placeholder entity "pick up" items by clicking on them. +/// The picked up item will then have added, referencing this placeholder. +/// +[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 { /// /// A whitelist to match entities that this should accept. /// - [ViewVariables, AutoNetworkedField] + [DataField, AutoNetworkedField] public EntityWhitelist? Whitelist; - [ViewVariables, AutoNetworkedField] + /// + /// The prototype to use for placeholder icon visuals. + /// + [DataField, AutoNetworkedField] public EntProtoId? Prototype; -} + /// + /// The source entity that this placeholder gets stored in when an item is picked up. + /// + [DataField, AutoNetworkedField] + public EntityUid? Source; + + /// + /// The container on to insert this placeholder into. + /// + [DataField, AutoNetworkedField] + public string ContainerId = string.Empty; + + /// + /// Controls preventing removal from containers. + /// + [DataField, AutoNetworkedField] + public bool Enabled; +} diff --git a/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs b/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs index 91062f3595..04f68c8ad3 100644 --- a/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs +++ b/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs @@ -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. +/// +/// When an entity with this is removed from a hand, it is replaced with an existing placeholder entity. +/// +[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; + /// + /// Used to prevent it incorrectly replacing with the placeholder, + /// when selecting and deselecting a module. + /// + [DataField, AutoNetworkedField] + public bool Enabled; } diff --git a/Content.Shared/_NF/Interaction/Systems/HandPlaceholderSystem.cs b/Content.Shared/_NF/Interaction/Systems/HandPlaceholderSystem.cs new file mode 100644 index 0000000000..58354d20d6 --- /dev/null +++ b/Content.Shared/_NF/Interaction/Systems/HandPlaceholderSystem.cs @@ -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; + +/// +/// Handles interactions with items that swap with HandPlaceholder items. +/// +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 Placeholder = "HandPlaceholder"; + + public override void Initialize() + { + SubscribeLocalEvent(OnEntityRemovedFromContainer); + + SubscribeLocalEvent(AfterInteract); + SubscribeLocalEvent(OnRemoveAttempt); + } + + /// + /// Spawns a new placeholder and ties it to an item. + /// When dropped the item will replace itself with the placeholder in its container. + /// + public void SpawnPlaceholder(BaseContainer container, EntityUid item, EntProtoId id, EntityWhitelist whitelist) + { + var placeholder = Spawn(Placeholder); + var comp = Comp(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)}"); + } + + /// + /// Sets the placeholder entity for an item. + /// + public void SetPlaceholder(EntityUid item, EntityUid placeholder) + { + var comp = EnsureComp(item); + comp.Placeholder = placeholder; + Dirty(item, comp); + } + + public void SetEnabled(EntityUid item, bool enabled) + { + if (TryComp(item, out var comp)) + { + comp.Enabled = enabled; + Dirty(item, comp); + } + else if (TryComp(item, out var placeholder)) + { + placeholder.Enabled = enabled; + Dirty(item, placeholder); + } + } + + private void SwapPlaceholder(Entity 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(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 ent, ref EntGotRemovedFromContainerMessage args) + { + SwapPlaceholder(ent, args.Container); + } + + private void AfterInteract(Entity 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 ent, ref ContainerGettingRemovedAttemptEvent args) + { + if (ent.Comp.Enabled) + args.Cancel(); + } + + private void TryToPickUpTarget(Entity ent, EntityUid target, EntityUid user) + { + // require items regardless of the whitelist + if (!HasComp(target) || _whitelist.IsWhitelistFail(ent.Comp.Whitelist, target)) + return; + + if (!TryComp(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???) + } +} diff --git a/Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs b/Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs deleted file mode 100644 index 564e37231d..0000000000 --- a/Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs +++ /dev/null @@ -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; - -/// -/// Handles interactions with items that spawn HandPlaceholder items. -/// -[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(OnEntityRemovedFromContainer); - - SubscribeLocalEvent(AfterInteract); - SubscribeLocalEvent(BeforeRangedInteract); - } - - private void OnEntityRemovedFromContainer(Entity ent, ref EntGotRemovedFromContainerMessage args) - { - if (!TerminatingOrDeleted(args.Container.Owner)) - SpawnAndPickUpPlaceholder(ent, args.Container); - RemCompDeferred(ent); - } - - private void SpawnAndPickUpPlaceholder(Entity ent, BaseContainer container) - { - if (_net.IsServer) - { - var placeholder = Spawn("HandPlaceholder"); - if (TryComp(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 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 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 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(target); - placeComp.Whitelist = whitelist; - placeComp.Prototype = prototype; - Dirty(target, placeComp); - _interaction.DoContactInteraction(user, target); // DeltaV - borgs picking up items leaves fibers - } -} diff --git a/Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleComponent.cs b/Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleComponent.cs new file mode 100644 index 0000000000..961b58b2dd --- /dev/null +++ b/Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleComponent.cs @@ -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; + +/// +/// 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. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(DroppableBorgModuleSystem))] +public sealed partial class DroppableBorgModuleComponent : Component +{ + /// + /// The items to spawn in borg hands. + /// + [DataField(required: true)] + public List Items = new(); + + /// + /// The ID of the container to add that stores items when not in hands. + /// + [DataField] + public string ContainerId = "nf-droppable-items"; + + /// + /// The ID of the container to add that stores item placeholders when not in hands. + /// + [DataField] + public string PlaceholderContainerId = "nf-item-placeholders"; +} + +[DataDefinition] +public sealed partial class DroppableBorgItem +{ + /// + /// The entity to spawn and use for the placeholder sprite. + /// + [DataField(required: true)] + public EntProtoId Id; + + /// + /// A whitelist that items must match to be picked up by the placeholder. + /// Regardless of this whitelist entities must have ItemComponent to be picked up. + /// + [DataField(required: true)] + public EntityWhitelist Whitelist; +} diff --git a/Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleSystem.cs b/Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleSystem.cs new file mode 100644 index 0000000000..9e4af26958 --- /dev/null +++ b/Content.Shared/_NF/Silicons/Borgs/DroppableBorgModuleSystem.cs @@ -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(OnMapInit); + SubscribeLocalEvent(OnCanInsertModule); + SubscribeLocalEvent(OnModuleSelected); + SubscribeLocalEvent(OnModuleUnselected); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + _container.EnsureContainer(ent, ent.Comp.ContainerId); + var placeholders = _container.EnsureContainer(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 ent, ref BorgCanInsertModuleEvent args) + { + if (args.Cancelled) + return; + + foreach (var module in args.Chassis.Comp.ModuleContainer.ContainedEntities) + { + if (!TryComp(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 ent, ref BorgModuleSelectedEvent args) + { + var chassis = args.Chassis; + if (!TryComp(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 ent, ref BorgModuleUnselectedEvent args) + { + var chassis = args.Chassis; + if (!TryComp(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); + } + } + + /// + /// Format the hand ID for a given module and item number. + /// + private static string HandId(EntityUid uid, int i) + { + return $"nf-{uid}-item-{i}"; + } + + /// + /// Checks that 2 droppable modules have the same starting items. + /// Used for duplicate module check. + /// + private static bool SameItems(List a, List 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; + } +} + +/// +/// Event raised on a module to check if it can be installed. +/// This should exist upstream but doesn't. +/// +[ByRefEvent] +public record struct BorgCanInsertModuleEvent(Entity Chassis, EntityUid? User, bool Cancelled = false); diff --git a/Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs b/Content.Shared/_NF/Whitelist/Components/NFBookBagComponent.cs similarity index 78% rename from Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs rename to Content.Shared/_NF/Whitelist/Components/NFBookBagComponent.cs index 934fc2a971..a28f4c2eeb 100644 --- a/Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs +++ b/Content.Shared/_NF/Whitelist/Components/NFBookBagComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server._NF.Whitelist.Components; +namespace Content.Shared._NF.Whitelist.Components; /// /// Whitelist component for book bags to avoid tag redefinition and collisions diff --git a/Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs b/Content.Shared/_NF/Whitelist/Components/NFLighterComponent.cs similarity index 78% rename from Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs rename to Content.Shared/_NF/Whitelist/Components/NFLighterComponent.cs index acc0414937..f4aa50da33 100644 --- a/Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs +++ b/Content.Shared/_NF/Whitelist/Components/NFLighterComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server._NF.Whitelist.Components; +namespace Content.Shared._NF.Whitelist.Components; /// /// Whitelist component for lighters to avoid tag redefinition and collisions diff --git a/Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs b/Content.Shared/_NF/Whitelist/Components/NFOreBagComponent.cs similarity index 78% rename from Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs rename to Content.Shared/_NF/Whitelist/Components/NFOreBagComponent.cs index 2072de58eb..35bf05bbdd 100644 --- a/Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs +++ b/Content.Shared/_NF/Whitelist/Components/NFOreBagComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server._NF.Whitelist.Components; +namespace Content.Shared._NF.Whitelist.Components; /// /// Whitelist component for ore bags to avoid tag redefinition and collisions diff --git a/Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs b/Content.Shared/_NF/Whitelist/Components/NFPlantBagComponent.cs similarity index 78% rename from Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs rename to Content.Shared/_NF/Whitelist/Components/NFPlantBagComponent.cs index f3ec9a6935..4629d0a38f 100644 --- a/Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs +++ b/Content.Shared/_NF/Whitelist/Components/NFPlantBagComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server._NF.Whitelist.Components; +namespace Content.Shared._NF.Whitelist.Components; /// /// Whitelist component for plant bags to avoid tag redefinition and collisions diff --git a/Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs b/Content.Shared/_NF/Whitelist/Components/NFShakerComponent.cs similarity index 78% rename from Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs rename to Content.Shared/_NF/Whitelist/Components/NFShakerComponent.cs index 78831efabc..c0277eb83c 100644 --- a/Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs +++ b/Content.Shared/_NF/Whitelist/Components/NFShakerComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server._NF.Whitelist.Components; +namespace Content.Shared._NF.Whitelist.Components; /// /// Whitelist component for shakers to avoid tag redefinition and collisions diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index a0a9f1e7c0..77e4ac38e5 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -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 } diff --git a/Resources/Prototypes/_NF/Entities/Objects/Specific/Robotics/borg_hand_placeholder.yml b/Resources/Prototypes/_NF/Entities/Objects/Specific/Robotics/borg_hand_placeholder.yml index 1c84c2b08e..de80c492aa 100644 --- a/Resources/Prototypes/_NF/Entities/Objects/Specific/Robotics/borg_hand_placeholder.yml +++ b/Resources/Prototypes/_NF/Entities/Objects/Specific/Robotics/borg_hand_placeholder.yml @@ -5,6 +5,5 @@ components: - type: Item size: Ginormous # no storage insertion visuals - - type: Unremoveable - type: HandPlaceholder - type: HandPlaceholderVisuals