using Content.Server.Hands.Systems; using Content.Server.Storage.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Hands.Components; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Storage; using Robust.Server.Containers; namespace Content.Server.VoiceTrigger; /// /// Allows storages to be manipulated using voice commands. /// public sealed class StorageVoiceControlSystem : EntitySystem { [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly StorageSystem _storage = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(VoiceTriggered); } private void VoiceTriggered(Entity ent, ref VoiceTriggeredEvent args) { // Check if the component has any slot restrictions via AllowedSlots // If it has slot restrictions, check if the item is in a slot that is allowed if (ent.Comp.AllowedSlots != null && _inventory.TryGetContainingSlot(ent.Owner, out var itemSlot) && (itemSlot.SlotFlags & ent.Comp.AllowedSlots) == 0) return; // Don't do anything if there is no message if (args.Message == null) return; // Get the storage component if (!TryComp(ent, out var storage)) return; // Get the hands component if (!TryComp(args.Source, out var hands)) return; // If the player has something in their hands, try to insert it into the storage if (hands.ActiveHand != null && hands.ActiveHand.HeldEntity.HasValue) { // Disallow insertion and provide a reason why if the person decides to insert the item into itself if (ent.Owner.Equals(hands.ActiveHand.HeldEntity.Value)) { _popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", hands.ActiveHand.HeldEntity.Value)), ent, args.Source); return; } if (_storage.CanInsert(ent, hands.ActiveHand.HeldEntity.Value, out var failedReason)) { // We adminlog before insertion, otherwise the logger will attempt to pull info on an entity that no longer is present and throw an exception _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control"); _storage.Insert(ent, hands.ActiveHand.HeldEntity.Value, out _); return; } { // Tell the player the reason why the item couldn't be inserted if (failedReason == null) return; _popup.PopupEntity(Loc.GetString(failedReason), ent, args.Source); _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control"); } return; } // If otherwise, we're retrieving an item, so check all the items currently in the attached storage foreach (var item in storage.Container.ContainedEntities) { // Get the item's name var itemName = MetaData(item).EntityName; // The message doesn't match the item name the requestor requested, skip and move on to the next item if (!args.Message.Contains(itemName, StringComparison.InvariantCultureIgnoreCase)) continue; // We found the item we want, so draw it from storage and place it into the player's hands if (storage.Container.ContainedEntities.Count != 0) { _container.RemoveEntity(ent, item); _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} retrieved {ToPrettyString(item)} from {ToPrettyString(ent)} via voice control"); _hands.TryPickup(args.Source, item, handsComp: hands); break; } } } }