From 0f3edc0b3948c21e8c4dd76b450e8f3b2b73c996 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:10:32 +0000 Subject: [PATCH] port better borgs from frontier (#3110) * BetterBorgs: droppable, swappable cyborg item interactions (#2766) * WIP: droppable, swappable, insertable cyborg items * Half-baked borg HandPlaceholderComponent * cyborg: sprite representation for empty slots * nullable prototype --------- Co-authored-by: Dvir <39403717+dvir001@users.noreply.github.com> * BorgSystem: check droppable items for duped mods (#2887) * BorgSystem: check droppable items for duped mods * Cache item comparer * BorgSystem: Unremoveable after equip (#2854) * raise interaction events to add fibers to things --------- Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com> Co-authored-by: Dvir <39403717+dvir001@users.noreply.github.com> Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Systems/Hands/HandsUIController.cs | 15 +++ .../_NF/Hands/UI/HandPlaceholderStatus.cs | 13 ++ .../_NF/Hands/UI/HandPlaceholderStatus.xaml | 3 + .../HandPlaceholderVisualsComponent.cs | 10 ++ .../Systems/HandPlaceholderVisualsSystem.cs | 47 +++++++ .../Tests/Sprite/ItemSpriteTest.cs | 3 +- .../Silicons/Borgs/BorgSystem.Modules.cs | 110 +++++++++++++++- Content.Server/Silicons/Borgs/BorgSystem.cs | 1 + .../Components/NFBookBagComponent.cs | 7 + .../Components/NFLighterComponent.cs | 7 + .../Whitelist/Components/NFOreBagComponent.cs | 7 + .../Components/NFPlantBagComponent.cs | 7 + .../Whitelist/Components/NFShakerComponent.cs | 7 + .../Components/UnremoveableComponent.cs | 1 - .../Components/ItemBorgModuleComponent.cs | 27 +++- .../Components/HandPlaceholderComponent.cs | 22 ++++ .../HandPlaceholderRemoveableComponent.cs | 17 +++ .../Systems/SharedHandPlaceholderSystem.cs | 120 ++++++++++++++++++ .../Locale/en-US/_NF/hands/hands-system.ftl | 1 + .../Consumable/Drinks/drinks_special.yml | 1 + .../Objects/Specific/Hydroponics/tools.yml | 1 + .../Objects/Specific/Librarian/books_bag.yml | 1 + .../Specific/Robotics/borg_modules.yml | 81 ++++++++++-- .../Objects/Specific/Salvage/ore_bag.yml | 5 +- .../Entities/Objects/Tools/lighters.yml | 1 + .../Robotics/borg_hand_placeholder.yml | 10 ++ 26 files changed, 507 insertions(+), 18 deletions(-) create mode 100644 Content.Client/_NF/Hands/UI/HandPlaceholderStatus.cs create mode 100644 Content.Client/_NF/Hands/UI/HandPlaceholderStatus.xaml create mode 100644 Content.Client/_NF/Interaction/Components/HandPlaceholderVisualsComponent.cs create mode 100644 Content.Client/_NF/Interaction/Systems/HandPlaceholderVisualsSystem.cs create mode 100644 Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs create mode 100644 Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs create mode 100644 Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs create mode 100644 Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs create mode 100644 Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs create mode 100644 Content.Shared/_NF/Interaction/Components/HandPlaceholderComponent.cs create mode 100644 Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs create mode 100644 Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs create mode 100644 Resources/Locale/en-US/_NF/hands/hands-system.ftl create mode 100644 Resources/Prototypes/_NF/Entities/Objects/Specific/Robotics/borg_hand_placeholder.yml diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs index edc02600611..67a30c61f82 100644 --- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs +++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs @@ -13,6 +13,7 @@ using Robust.Shared.Input; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Content.Shared._NF.Interaction.Components; namespace Content.Client.UserInterface.Systems.Hands; @@ -138,6 +139,13 @@ private void LoadPlayerHands(HandsComponent handsComp) handButton.SetEntity(virt.BlockingEntity); handButton.Blocked = true; } + // Frontier - borg hand placeholder + else if (_entities.TryGetComponent(hand.HeldEntity, out HandPlaceholderVisualsComponent? placeholder)) + { + handButton.SetEntity(placeholder.Dummy); + handButton.Blocked = true; + } + // End Frontier - borg hand placeholder else { handButton.SetEntity(hand.HeldEntity); @@ -189,6 +197,13 @@ private void OnItemAdded(string name, EntityUid entity) hand.SetEntity(virt.BlockingEntity); hand.Blocked = true; } + // Frontier: borg hand placeholders + else if (_entities.TryGetComponent(entity, out HandPlaceholderVisualsComponent? placeholder)) + { + hand.SetEntity(placeholder.Dummy); + hand.Blocked = true; + } + // End Frontier: borg hand placeholders else { hand.SetEntity(entity); diff --git a/Content.Client/_NF/Hands/UI/HandPlaceholderStatus.cs b/Content.Client/_NF/Hands/UI/HandPlaceholderStatus.cs new file mode 100644 index 00000000000..745ffee5966 --- /dev/null +++ b/Content.Client/_NF/Hands/UI/HandPlaceholderStatus.cs @@ -0,0 +1,13 @@ +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._NF.Hands.UI +{ + public sealed class HandPlaceholderStatus : Control + { + public HandPlaceholderStatus() + { + RobustXamlLoader.Load(this); + } + } +} diff --git a/Content.Client/_NF/Hands/UI/HandPlaceholderStatus.xaml b/Content.Client/_NF/Hands/UI/HandPlaceholderStatus.xaml new file mode 100644 index 00000000000..2eaf1145d12 --- /dev/null +++ b/Content.Client/_NF/Hands/UI/HandPlaceholderStatus.xaml @@ -0,0 +1,3 @@ + + diff --git a/Content.Client/_NF/Interaction/Components/HandPlaceholderVisualsComponent.cs b/Content.Client/_NF/Interaction/Components/HandPlaceholderVisualsComponent.cs new file mode 100644 index 00000000000..72c5ae093d3 --- /dev/null +++ b/Content.Client/_NF/Interaction/Components/HandPlaceholderVisualsComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared._NF.Interaction.Components; + +[RegisterComponent] +// Client-side component of the HandPlaceholder. Creates and tracks a client-side entity for hand blocking visuals +public sealed partial class HandPlaceholderVisualsComponent : Component +{ + [DataField] + public EntityUid Dummy; +} + diff --git a/Content.Client/_NF/Interaction/Systems/HandPlaceholderVisualsSystem.cs b/Content.Client/_NF/Interaction/Systems/HandPlaceholderVisualsSystem.cs new file mode 100644 index 00000000000..5d59b78aea0 --- /dev/null +++ b/Content.Client/_NF/Interaction/Systems/HandPlaceholderVisualsSystem.cs @@ -0,0 +1,47 @@ +using Content.Client._NF.Hands.UI; +using Content.Client.Items; +using Content.Client.Items.Systems; +using Content.Shared._NF.Interaction.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client._NF.Interaction.Systems; + +/// +/// Handles interactions with items that spawn HandPlaceholder items. +/// +[UsedImplicitly] +public sealed partial class HandPlaceholderVisualsSystem : EntitySystem +{ + [Dependency] ContainerSystem _container = default!; + [Dependency] ItemSystem _item = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterAutoHandleState); + + SubscribeLocalEvent(PlaceholderRemove); + + Subs.ItemStatus(_ => new HandPlaceholderStatus()); + } + + private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(ent, out HandPlaceholderVisualsComponent? placeholder)) + return; + + if (placeholder.Dummy != EntityUid.Invalid) + QueueDel(placeholder.Dummy); + placeholder.Dummy = Spawn(ent.Comp.Prototype); + + if (_container.IsEntityInContainer(ent)) + _item.VisualsChanged(ent); + } + + private void PlaceholderRemove(Entity ent, ref ComponentRemove args) + { + if (ent.Comp.Dummy != EntityUid.Invalid) + QueueDel(ent.Comp.Dummy); + } +} diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs index da7e1e8e9b0..623044f617a 100644 --- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs +++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs @@ -28,7 +28,8 @@ public sealed class PrototypeSaveTest { // The only prototypes that should get ignored are those that REQUIRE setup to get a sprite. At that point it is // the responsibility of the spawner to ensure that a valid sprite is set. - "VirtualItem" + "VirtualItem", + "HandPlaceholder" // Frontier }; [Test] diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index f95a5807360..f6155bf8902 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -4,6 +4,7 @@ using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Whitelist; using Robust.Shared.Containers; +using Content.Shared._NF.Interaction.Components; // Frontier namespace Content.Server.Silicons.Borgs; @@ -197,6 +198,7 @@ private void ProvideItems(EntityUid chassis, EntityUid uid, BorgChassisComponent if (!component.ItemsCreated) { item = Spawn(itemProto, xform.Coordinates); + _interaction.DoContactInteraction(chassis, item); // DeltaV - give items fibers before they might be dropped } else { @@ -225,6 +227,63 @@ private void ProvideItems(EntityUid chassis, EntityUid uid, BorgChassisComponent 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; } @@ -244,6 +303,14 @@ private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisCo _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; } @@ -257,6 +324,20 @@ private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisCo _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 } /// @@ -283,13 +364,16 @@ public bool CanInsertModule(EntityUid uid, EntityUid module, BorgChassisComponen 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.Items.All(itemModuleComp.Items.Contains)) + containedItemModuleComp.DroppableItems.Count == itemModuleComp.DroppableItems.Count && // Frontier + containedItemModuleComp.Items.All(itemModuleComp.Items.Contains) && + containedItemModuleComp.DroppableItems.All(x => itemModuleComp.DroppableItems.Contains(x, droppableComparer))) // Frontier { if (user != null) Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value); @@ -301,6 +385,30 @@ public bool CanInsertModule(EntityUid uid, EntityUid module, BorgChassisComponen 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.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index d8fa1f300be..4f69eaa6243 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -53,6 +53,7 @@ public sealed partial class BorgSystem : SharedBorgSystem [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; // DeltaV [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; diff --git a/Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs b/Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs new file mode 100644 index 00000000000..934fc2a971d --- /dev/null +++ b/Content.Server/_NF/Whitelist/Components/NFBookBagComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server._NF.Whitelist.Components; + +/// +/// Whitelist component for book bags to avoid tag redefinition and collisions +/// +[RegisterComponent] +public sealed partial class NFBookBagComponent : Component; diff --git a/Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs b/Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs new file mode 100644 index 00000000000..acc04149379 --- /dev/null +++ b/Content.Server/_NF/Whitelist/Components/NFLighterComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server._NF.Whitelist.Components; + +/// +/// Whitelist component for lighters to avoid tag redefinition and collisions +/// +[RegisterComponent] +public sealed partial class NFLighterComponent : Component; diff --git a/Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs b/Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs new file mode 100644 index 00000000000..2072de58eb4 --- /dev/null +++ b/Content.Server/_NF/Whitelist/Components/NFOreBagComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server._NF.Whitelist.Components; + +/// +/// Whitelist component for ore bags to avoid tag redefinition and collisions +/// +[RegisterComponent] +public sealed partial class NFOreBagComponent : Component; diff --git a/Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs b/Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs new file mode 100644 index 00000000000..f3ec9a69352 --- /dev/null +++ b/Content.Server/_NF/Whitelist/Components/NFPlantBagComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server._NF.Whitelist.Components; + +/// +/// Whitelist component for plant bags to avoid tag redefinition and collisions +/// +[RegisterComponent] +public sealed partial class NFPlantBagComponent : Component; diff --git a/Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs b/Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs new file mode 100644 index 00000000000..78831efabce --- /dev/null +++ b/Content.Server/_NF/Whitelist/Components/NFShakerComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server._NF.Whitelist.Components; + +/// +/// Whitelist component for shakers to avoid tag redefinition and collisions +/// +[RegisterComponent] +public sealed partial class NFShakerComponent : Component; diff --git a/Content.Shared/Interaction/Components/UnremoveableComponent.cs b/Content.Shared/Interaction/Components/UnremoveableComponent.cs index cd106948658..1b2328d00f9 100644 --- a/Content.Shared/Interaction/Components/UnremoveableComponent.cs +++ b/Content.Shared/Interaction/Components/UnremoveableComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Whitelist; using Robust.Shared.GameStates; namespace Content.Shared.Interaction.Components diff --git a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs index 75835d0cf01..ead55d97272 100644 --- a/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs +++ b/Content.Shared/Silicons/Borgs/Components/ItemBorgModuleComponent.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Containers; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; @@ -14,15 +15,27 @@ public sealed partial class ItemBorgModuleComponent : Component /// /// The items that are provided. /// - [DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] + [DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer))] // Frontier: removed 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 /// @@ -49,3 +62,13 @@ 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 new file mode 100644 index 00000000000..51611889e45 --- /dev/null +++ b/Content.Shared/_NF/Interaction/Components/HandPlaceholderComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._NF.Interaction.Components; + +[RegisterComponent] +[NetworkedComponent] +[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] + public EntityWhitelist? Whitelist; + + [ViewVariables, AutoNetworkedField] + public EntProtoId? Prototype; +} + diff --git a/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs b/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs new file mode 100644 index 00000000000..91062f35958 --- /dev/null +++ b/Content.Shared/_NF/Interaction/Components/HandPlaceholderRemoveableComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Whitelist; +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. +public sealed partial class HandPlaceholderRemoveableComponent : Component +{ + [DataField] + public EntityWhitelist? Whitelist; + + [DataField] + public EntProtoId? Prototype; +} diff --git a/Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs b/Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs new file mode 100644 index 00000000000..db259ffc649 --- /dev/null +++ b/Content.Shared/_NF/Interaction/Systems/SharedHandPlaceholderSystem.cs @@ -0,0 +1,120 @@ +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(OnUnequipHand); + SubscribeLocalEvent(OnDropped); + + SubscribeLocalEvent(AfterInteract); + SubscribeLocalEvent(BeforeRangedInteract); + } + + private void OnUnequipHand(Entity ent, ref GotUnequippedHandEvent args) + { + if (args.Handled) + return; // If this is happening in practice, this is a bug. + + SpawnAndPickUpPlaceholder(ent, args.User); + RemCompDeferred(ent); + args.Handled = true; + } + + private void OnDropped(Entity ent, ref DroppedEvent args) + { + if (args.Handled) + return; // If this is happening in practice, this is a bug. + + SpawnAndPickUpPlaceholder(ent, args.User); + RemCompDeferred(ent); + args.Handled = true; + } + + private void SpawnAndPickUpPlaceholder(Entity ent, EntityUid user) + { + 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 (!_hands.TryPickup(user, placeholder)) // Can we get the hand this came from? + 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/Resources/Locale/en-US/_NF/hands/hands-system.ftl b/Resources/Locale/en-US/_NF/hands/hands-system.ftl new file mode 100644 index 00000000000..36b66a2ee52 --- /dev/null +++ b/Resources/Locale/en-US/_NF/hands/hands-system.ftl @@ -0,0 +1 @@ +hand-placeholder-name = Module slot for \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml index 930cf817576..34a36433531 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml @@ -39,6 +39,7 @@ mixOnInteract: false reactionTypes: - Shake + - type: NFShaker # Frontier - type: entity parent: DrinkGlassBase diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml index fe7e0400af6..1e453e3d5a9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml @@ -157,3 +157,4 @@ difficulty: 2 recipes: - PlantBagOfHolding + - type: NFPlantBag # Frontier diff --git a/Resources/Prototypes/Entities/Objects/Specific/Librarian/books_bag.yml b/Resources/Prototypes/Entities/Objects/Specific/Librarian/books_bag.yml index 6eb56f3245b..9f28762a02e 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Librarian/books_bag.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Librarian/books_bag.yml @@ -28,3 +28,4 @@ - TabletopBoard - Write - type: Dumpable + - type: NFBookBag # Frontier diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 69580bd453c..a0a9f1e7c07 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -134,8 +134,13 @@ - state: generic - state: icon-fire-extinguisher - type: ItemBorgModule - items: - - FireExtinguisher + # Frontier: droppable borg items + droppableItems: + - id: FireExtinguisher + whitelist: + tags: + - FireExtinguisher + # End Frontier - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: extinguisher-module } @@ -221,9 +226,16 @@ items: - MiningDrill - MineralScannerUnpowered - - OreBag + # - OreBag # Frontier - Crowbar - RadioHandheld + # Frontier: droppable borg items + droppableItems: + - id: OreBag + whitelist: + components: + - NFOreBag + # End Frontier: droppable borg items - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: mining-module } @@ -345,8 +357,15 @@ - type: ItemBorgModule items: - MopItem - - Bucket + # - Bucket # Frontier - TrashBag + # Frontier: droppable items + droppableItems: + - id: Bucket + whitelist: + tags: + - Bucket + # End Frontier - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: cleaning-module } @@ -433,10 +452,21 @@ - type: ItemBorgModule items: - HandheldHealthAnalyzerUnpowered - - Beaker - - Beaker + # - Beaker # Frontier + # - Beaker # Frontier - BorgDropper - BorgHypo + # Frontier: droppable borg items + droppableItems: + - id: Beaker + whitelist: + tags: + - GlassBeaker + - id: Beaker + whitelist: + tags: + - GlassBeaker + # End Frontier: droppable borg items - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-diagnosis-module } @@ -489,11 +519,26 @@ - type: ItemBorgModule items: - Pen - - BooksBag + # - BooksBag # Frontier - HandLabeler - - Lighter - - DrinkShaker + # - Lighter # Frontier + # - DrinkShaker # Frontier - BorgDropper + # Frontier: droppable + droppableItems: + - id: BooksBag + whitelist: + components: + - NFBookBag + - id: Lighter + whitelist: + components: + - NFLighter + - id: DrinkShaker + whitelist: + components: + - NFShaker + # End Frontier - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: service-module } @@ -528,7 +573,14 @@ - HydroponicsToolMiniHoe - HydroponicsToolSpade - HydroponicsToolClippers - - Bucket + # - Bucket # Frontier + # Frontier: droppable borg items + droppableItems: + - id: Bucket + whitelist: + tags: + - Bucket + # End Frontier - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: gardening-module } @@ -545,7 +597,14 @@ items: - HydroponicsToolScythe - HydroponicsToolHatchet - - PlantBag + # - PlantBag # Frontier + # Frontier: droppable borg items + droppableItems: + - id: PlantBag + whitelist: + components: + - NFPlantBag + # End Frontier - type: BorgModuleIcon icon: { sprite: Interface/Actions/actions_borg.rsi, state: harvesting-module } diff --git a/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml index 9b0a4361f9f..51792823217 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml @@ -46,8 +46,9 @@ enum.ToggleVisuals.Layer: True: { state: icon_on } False: { state: icon } - # End DeltaV Additions - - type: ReverseEngineering # DeltaV + - type: ReverseEngineering difficulty: 2 recipes: - OreBagOfHolding + # End DeltaV Additions + - type: NFOreBag # Frontier diff --git a/Resources/Prototypes/Entities/Objects/Tools/lighters.yml b/Resources/Prototypes/Entities/Objects/Tools/lighters.yml index 94f501260b8..35187e20fc8 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lighters.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lighters.yml @@ -95,6 +95,7 @@ collection: lighterOnSounds endSound: collection: lighterOffSounds + - type: NFLighter # Frontier - type: entity name: cheap lighter 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 new file mode 100644 index 00000000000..1c84c2b08e5 --- /dev/null +++ b/Resources/Prototypes/_NF/Entities/Objects/Specific/Robotics/borg_hand_placeholder.yml @@ -0,0 +1,10 @@ +- type: entity + id: HandPlaceholder + name: unknown tool + categories: [ HideSpawnMenu ] + components: + - type: Item + size: Ginormous # no storage insertion visuals + - type: Unremoveable + - type: HandPlaceholder + - type: HandPlaceholderVisuals