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.Prototypes; 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 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(BeforeRangedInteract); 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 EntityUid SpawnPlaceholder(BaseContainer container, EntityUid item, EntProtoId id, EntityWhitelist whitelist, EntityWhitelist? blacklist) { var placeholder = Spawn(Placeholder); var proto = _proto.Index(id); var comp = Comp(placeholder); comp.Prototype = id; comp.Whitelist = whitelist; comp.Blacklist = blacklist; comp.Source = container.Owner; comp.ContainerId = container.ID; comp.AllowNonItems = !proto.HasComponent(); Dirty(placeholder, comp); var name = proto.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)}"); return placeholder; } /// /// Sets the placeholder entity for an item. /// public void SetPlaceholder(EntityUid item, EntityUid placeholder) { if (!item.Valid) return; 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) || Transform(owner).MapID == MapId.Nullspace) return; var placeholder = ent.Comp.Placeholder; ent.Comp.Enabled = false; RemCompDeferred(ent); // stop tests failing if (TerminatingOrDeleted(placeholder)) return; 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 BeforeRangedInteract(Entity ent, ref BeforeRangedInteractEvent 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 (!ent.Comp.AllowNonItems && !HasComp(target) || _whitelist.IsWhitelistFail(ent.Comp.Whitelist, target) || _whitelist.IsBlacklistPass(ent.Comp.Blacklist, 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; 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???) SetPlaceholder(target, ent); SetEnabled(target, true); } }