using Content.Server.Hands.Systems;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
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;
// Get the storage component
if (!TryComp(ent, out var storage))
return;
// If the player has something in their hands, try to insert it into the storage
if (_hands.TryGetActiveItem(ent.Owner, out var activeItem))
{
// Disallow insertion and provide a reason why if the person decides to insert the item into itself
if (ent.Owner.Equals(activeItem.Value))
{
_popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", activeItem.Value)), ent, args.Source);
return;
}
if (_storage.CanInsert(ent, activeItem.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(activeItem.Value)} into {ToPrettyString(ent)} via voice control");
_storage.Insert(ent, activeItem.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(activeItem.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)
{
// Check if the name contains the actual command.
// This will do comparisons against any length of string which is a little weird, but worth the tradeoff.
// E.g "go go s" would give you the screwdriver because "screwdriver" contains "s"
if (Name(item).Contains(args.MessageWithoutPhrase))
{
ExtractItemFromStorage(ent, item, args.Source);
break;
}
}
}
///
/// Extracts an item from storage and places it into the player's hands.
///
/// The entity with the
/// The entity to be extracted from the attached storage
/// The entity wearing the item
private void ExtractItemFromStorage(Entity ent,
EntityUid item,
EntityUid source)
{
_container.RemoveEntity(ent, item);
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(source)} retrieved {ToPrettyString(item)} from {ToPrettyString(ent)} via voice control");
_hands.TryPickup(source, item);
}
}