]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
DetGadget Hat Revitalization (#35438)
authorArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Sat, 1 Mar 2025 18:03:27 +0000 (10:03 -0800)
committerGitHub <noreply@github.com>
Sat, 1 Mar 2025 18:03:27 +0000 (19:03 +0100)
* DetGadget Hat

* uh... half-assed item description

* Reduce hat range to one tile, you have to stand on someone to steal their hat items

* Fix Integration Errors

* Only the wearer can access voice commands

* init work - handscomp is unable to be pulled

* second bit of progress

* basic working implementation

* nuke storageslots and add adminlogging

* disallow trolling nukies or hiding objective items

* remove unnecessary tags additions

* finish nuking unused tags

* death to yamllinter

* int tests be damned

* milon is a furry

* address review

* upd desc

* address reviews part 2

* address more reviews

* remove unused refs

* fix order of dependencies

* add ShowVerb to SharedStorageSystem.cs

This will allow or disallow showing the "Open Storage" verb if defined on the component.

* orks is a nerd

* add proper locale, fix adminlogging

* orks is a nerd 2

---------

Co-authored-by: Coenx-flex <coengmurray@gmail.com>
Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs
Content.Server/VoiceTrigger/StorageVoiceControlComponent.cs [new file with mode: 0644]
Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs [new file with mode: 0644]
Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
Content.Shared/Storage/StorageComponent.cs
Resources/Locale/en-US/components/storage-voice-control-component.ftl [new file with mode: 0644]
Resources/Prototypes/Catalog/Fills/Lockers/security.yml
Resources/Prototypes/Entities/Clothing/Head/specific.yml

index c78a8923cd21f6fdc7144de45c239035a1eb5da0..8dd170e6676201292adcbb8f417329ca9a0a4a9a 100644 (file)
@@ -53,6 +53,9 @@ namespace Content.Server.Explosion.EntitySystems
                 _adminLogger.Add(LogType.Trigger, LogImpact.High,
                         $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}.");
                 Trigger(ent, args.Source);
+
+                var voice = new VoiceTriggeredEvent(args.Source, message);
+                RaiseLocalEvent(ent, ref voice);
             }
         }
 
@@ -137,3 +140,12 @@ namespace Content.Server.Explosion.EntitySystems
         }
     }
 }
+
+
+/// <summary>
+///    Raised when a voice trigger is activated, containing the message that triggered it.
+/// </summary>
+/// <param name="Source"> The EntityUid of the entity sending the message</param>
+/// <param name="Message"> The contents of the message</param>
+[ByRefEvent]
+public readonly record struct VoiceTriggeredEvent(EntityUid Source, string? Message);
diff --git a/Content.Server/VoiceTrigger/StorageVoiceControlComponent.cs b/Content.Server/VoiceTrigger/StorageVoiceControlComponent.cs
new file mode 100644 (file)
index 0000000..1689979
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Shared.Inventory;
+
+namespace Content.Server.VoiceTrigger;
+
+/// <summary>
+/// Entities with this component, Containers, and TriggerOnVoiceComponent will insert any item or extract the spoken item after the TriggerOnVoiceComponent has been activated
+/// </summary>
+[RegisterComponent]
+public sealed partial class StorageVoiceControlComponent : Component
+{
+    /// <summary>
+    /// Used to determine which slots the component can be used in.
+    /// <remarks>
+    /// If not set, the component can be used anywhere, even while inside other containers.
+    /// </remarks>
+    /// </summary>
+    [DataField]
+    public SlotFlags? AllowedSlots;
+}
diff --git a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs
new file mode 100644 (file)
index 0000000..72e361b
--- /dev/null
@@ -0,0 +1,98 @@
+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;
+
+/// <summary>
+/// Allows storages to be manipulated using voice commands.
+/// </summary>
+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<StorageVoiceControlComponent, VoiceTriggeredEvent>(VoiceTriggered);
+    }
+
+    private void VoiceTriggered(Entity<StorageVoiceControlComponent> 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<StorageComponent>(ent, out var storage))
+            return;
+
+        // Get the hands component
+        if (!TryComp<HandsComponent>(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;
+            }
+        }
+    }
+}
index d2fbe26002287be4d4da7c37060420c35bf2bba8..f523a6c8a0f9510a58f31de2cea17d7bd6c20917 100644 (file)
@@ -282,7 +282,7 @@ public abstract class SharedStorageSystem : EntitySystem
 
     private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent<ActivationVerb> args)
     {
-        if (!CanInteract(args.User, (uid, component), args.CanAccess && args.CanInteract))
+        if (component.ShowVerb == false || !CanInteract(args.User, (uid, component), args.CanAccess && args.CanInteract))
             return;
 
         // Does this player currently have the storage UI open?
index c59f7ab00e914af6f034ff77f84e9b750e524667..f772ad2022053882bd05c9412b2ad4553659bccd 100644 (file)
@@ -146,6 +146,13 @@ namespace Content.Shared.Storage
         {
             Key,
         }
+
+        /// <summary>
+        /// Allow or disallow showing the "open/close storage" verb.
+        /// This is desired on items that we don't want to be accessed by the player directly.
+        /// </summary>
+        [DataField]
+        public bool ShowVerb = true;
     }
 
     [Serializable, NetSerializable]
diff --git a/Resources/Locale/en-US/components/storage-voice-control-component.ftl b/Resources/Locale/en-US/components/storage-voice-control-component.ftl
new file mode 100644 (file)
index 0000000..019b5ec
--- /dev/null
@@ -0,0 +1 @@
+comp-storagevoicecontrol-self-insert = You can't insert { THE($entity) } into itself!
index aab2a8af9af092f92ee864728cf4f837f4e1ead9..dd9b602e009edfc501210406f1f5dfd9b2eaafaf 100644 (file)
     contents:
       - id: ClothingEyesGlassesSecurity
         prob: 0.3
-      - id: ClothingHeadHatFedoraBrown
+      - id: ClothingHeadHatDetGadget
       - id: ClothingNeckTieDet
       - id: ClothingOuterVestDetective
       - id: ClothingOuterCoatDetective
index 636f922d8576f95703f1ff0804d0e12f77cfcfa1..1e2e55f55bc8926fcdad96f7a2edd71b2eccd150 100644 (file)
       interfaces:
         enum.ChameleonUiKey.Key:
           type: ChameleonBoundUserInterface
+
+- type: entity
+  parent: ClothingHeadHatFedoraBrown
+  id: ClothingHeadHatDetGadget
+  name: go go hat
+  description: A novel hat with a built in toolkit. Automatically stores and retrieves items at the say of a phrase!
+  components:
+  - type: Tag
+    tags: [] # ignore "WhitelistChameleon" tag
+  - type: TriggerOnVoice
+    keyPhrase: "go go gadget"
+    listenRange: 0
+  - type: ActiveListener
+    range: 0
+  - type: StorageVoiceControl
+    allowedSlots:
+    - HEAD
+  - type: Storage
+    showVerb: false
+    grid:
+    - 0,0,6,3
+    maxItemSize: Small
+    blacklist:
+      tags:
+      - HighRiskItem # no hiding objectives or trolling nukies
+      - FakeNukeDisk # no disk checking
+      - QuantumSpinInverter # avoid the morbillionth QSI bug
+  - type: ContainerContainer
+    containers:
+      storagebase: !type:Container
+        ents: [ ]