]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Syndicate locks are now selectable (#39532)
authorbeck-thompson <107373427+beck-thompson@users.noreply.github.com>
Tue, 26 Aug 2025 14:18:10 +0000 (07:18 -0700)
committerGitHub <noreply@github.com>
Tue, 26 Aug 2025 14:18:10 +0000 (16:18 +0200)
* Syndicate locks are now selectable

* Minor tweaks

* Make not syndicate themed

* Address refview

* review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Content.Shared/SelectableComponentAdder/SelectableComponentAdderComponent.cs [new file with mode: 0644]
Content.Shared/SelectableComponentAdder/SelectableComponentAdderSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs
Resources/Locale/en-US/locks/selectable-locks.ftl [new file with mode: 0644]
Resources/Locale/en-US/locks/voice-trigger-lock.ftl
Resources/Locale/en-US/selectable-component/selectable-component.ftl [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Devices/pda.yml
Resources/Prototypes/Entities/Objects/Specific/locks.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml
Resources/Prototypes/chameleon.yml

diff --git a/Content.Shared/SelectableComponentAdder/SelectableComponentAdderComponent.cs b/Content.Shared/SelectableComponentAdder/SelectableComponentAdderComponent.cs
new file mode 100644 (file)
index 0000000..5b7ccff
--- /dev/null
@@ -0,0 +1,77 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.SelectableComponentAdder;
+
+/// <summary>
+/// Brings up a verb menu that allows players to select components that will get added to the item with this component.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SelectableComponentAdderComponent : Component
+{
+    /// <summary>
+    /// List of verb -> components to add for that verb when selected basically!
+    /// </summary>
+    [DataField(required: true)]
+    public List<ComponentAdderEntry> Entries = new();
+
+    /// <summary>
+    /// The amount of times players can make a selection and add a component. If null, there is no limit.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int? Selections;
+
+    /// <summary>
+    /// The verb category name that will be used.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId VerbCategoryName = "selectable-component-adder-category-name";
+}
+
+[DataDefinition]
+public sealed partial class ComponentAdderEntry
+{
+    /// <summary>
+    /// Name of the verb that will add the components in <see cref="ComponentsToAdd"/>.
+    /// </summary>
+    [DataField(required: true)]
+    public LocId VerbName;
+
+    /// <summary>
+    /// Popup to show when this option is selected.
+    /// </summary>
+    [DataField(required: true)]
+    public LocId? Popup;
+
+    /// <summary>
+    /// List of all the components that will get added when the verb is selected.
+    /// </summary>
+    [DataField(required: true)]
+    public ComponentRegistry? ComponentsToAdd;
+
+    /// <summary>
+    /// The type of behavior that occurs when the component(s) already exist on the entity.
+    /// </summary>
+    [DataField]
+    public ComponentExistsSetting ComponentExistsBehavior = ComponentExistsSetting.Skip;
+
+    /// <summary>
+    /// The priorty of the verb in the list
+    /// </summary>
+    [DataField]
+    public int Priority;
+}
+
+[Serializable, NetSerializable]
+public enum ComponentExistsSetting : byte
+{
+    // If one of the components exist, skip adding it and continue adding the rest.
+    // If all components already exist, disable the verb.
+    Skip = 0,
+    // If a component already exists, replace it with the new one.
+    // The verb is always enabled.
+    Replace = 1,
+    // Disable the verb if any one of the components already exists.
+    Block = 2,
+}
diff --git a/Content.Shared/SelectableComponentAdder/SelectableComponentAdderSystem.cs b/Content.Shared/SelectableComponentAdder/SelectableComponentAdderSystem.cs
new file mode 100644 (file)
index 0000000..2961990
--- /dev/null
@@ -0,0 +1,95 @@
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.SelectableComponentAdder;
+
+public sealed partial class SelectableComponentAdderSystem : EntitySystem
+{
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SelectableComponentAdderComponent, GetVerbsEvent<Verb>>(OnGetVerb);
+    }
+
+    private void OnGetVerb(Entity<SelectableComponentAdderComponent> ent, ref GetVerbsEvent<Verb> args)
+    {
+        if (!args.CanAccess || !args.CanInteract || ent.Comp.Selections <= 0)
+            return;
+
+        var target = args.Target;
+        var user = args.User;
+        var verbCategory = new VerbCategory(ent.Comp.VerbCategoryName, null);
+
+        foreach (var entry in ent.Comp.Entries)
+        {
+            var verb = new Verb
+            {
+                Priority = entry.Priority,
+                Category = verbCategory,
+                Disabled = CheckDisabled(target, entry.ComponentsToAdd, entry.ComponentExistsBehavior),
+                Act = () =>
+                {
+                    AddComponents(target, entry.ComponentsToAdd, entry.ComponentExistsBehavior);
+                    ent.Comp.Selections--;
+                    Dirty(ent);
+                    if (entry.Popup == null)
+                        return;
+                    var message = Loc.GetString(entry.Popup.Value, ("target", target));
+                    _popup.PopupClient(message, target, user);
+                },
+                Text = Loc.GetString(entry.VerbName),
+            };
+            args.Verbs.Add(verb);
+        }
+    }
+
+    private bool CheckDisabled(EntityUid target, ComponentRegistry? registry, ComponentExistsSetting setting)
+    {
+        if (registry == null)
+            return false;
+
+        switch (setting)
+        {
+            case ComponentExistsSetting.Skip:
+                // disable the verb if all components already exist
+                foreach (var component in registry)
+                {
+                    if (!EntityManager.HasComponent(target, Factory.GetComponent(component.Key).GetType()))
+                        return false;
+                }
+                return true;
+            case ComponentExistsSetting.Replace:
+                // always allow the verb
+                return false;
+            case ComponentExistsSetting.Block:
+                // disable the verb if any component already exists.
+                foreach (var component in registry)
+                {
+                    if (EntityManager.HasComponent(target, Factory.GetComponent(component.Key).GetType()))
+                        return true;
+                }
+                return false;
+            default:
+                throw new NotImplementedException();
+        }
+    }
+
+    private void AddComponents(EntityUid target, ComponentRegistry? registry, ComponentExistsSetting setting)
+    {
+        if (registry == null || CheckDisabled(target, registry, setting))
+            return;
+
+        foreach (var component in registry)
+        {
+            if (EntityManager.HasComponent(target, Factory.GetComponent(component.Key).GetType()) &&
+                setting is ComponentExistsSetting.Skip or ComponentExistsSetting.Block)
+                continue;
+
+            EntityManager.AddComponent(target, component.Value, true);
+        }
+    }
+}
index 1fc3c1b966ce6859be18328982085367cac3b500..974d5322e02257f6909d0f22c1da14b9db7abc78 100644 (file)
@@ -60,36 +60,36 @@ public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent
     /// <summary>
     /// The verb text that is shown when you can start recording a message.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public LocId StartRecordingVerb = "trigger-on-voice-record";
 
     /// <summary>
     /// The verb text that is shown when you can stop recording a message.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public LocId StopRecordingVerb = "trigger-on-voice-stop";
 
     /// <summary>
     /// Tooltip that appears when hovering over the stop or start recording verbs.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public LocId? RecordingVerbMessage;
 
     /// <summary>
     /// The verb text that is shown when you can clear a recording.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public LocId ClearRecordingVerb = "trigger-on-voice-clear";
 
     /// <summary>
     /// The loc string that is shown when inspecting an uninitialized voice trigger.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public LocId? InspectUninitializedLoc = "trigger-on-voice-uninitialized";
 
     /// <summary>
     /// The loc string to use when inspecting voice trigger. Will also include the triggering phrase
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public LocId? InspectInitializedLoc = "trigger-on-voice-examine";
 }
diff --git a/Resources/Locale/en-US/locks/selectable-locks.ftl b/Resources/Locale/en-US/locks/selectable-locks.ftl
new file mode 100644 (file)
index 0000000..3dbf088
--- /dev/null
@@ -0,0 +1,3 @@
+selectable-lock-verb-category-name = Add lock
+selectable-lock-verb-no-lock = No lock
+selectable-lock-verb-no-lock-popup = No lock has been added to {THE($target)}.
index fd2dc38d23e40f29de6b01010c822cd6c452b1a0..a7069378f85fd938ffe5a17d658db8b1da12b106 100644 (file)
@@ -1,3 +1,6 @@
+voice-trigger-lock-add-verb = Voice Lock
+voice-trigger-lock-add-verb-popup = A voice lock has been added to {THE($target)}.
+
 voice-trigger-lock-verb-record = Record lock phrase
 voice-trigger-lock-verb-message = Locking the item will disable features that reveal its true nature!
 
diff --git a/Resources/Locale/en-US/selectable-component/selectable-component.ftl b/Resources/Locale/en-US/selectable-component/selectable-component.ftl
new file mode 100644 (file)
index 0000000..f30c499
--- /dev/null
@@ -0,0 +1 @@
+selectable-component-adder-category-name = Add feature
index 92bd4297ad669c7bedeabe438bfc14be4ec49cc5..1153047e7dedf0542e6576467e24e4bff4b942ac 100644 (file)
       - MedTekCartridge
 
 - type: entity
-  parent: [BasePDA, VoiceLock]
+  parent: [BasePDA, SelectableLock]
   id: ChameleonPDA
   name: passenger PDA
   description: Why isn't it gray?
index 296b9fd7d9c9cfb46ab37761275d21f7eb748d54..c280c4a60e7d2e6a3c56fa6e02ad9bcc6a0461c1 100644 (file)
@@ -1,5 +1,5 @@
 - type: entity
-  id: VoiceLock
+  id: SelectableLock
   abstract: true
   components:
   - type: Lock
     useAccess: false
     unlockingSound: null # TODO: Maybe add sounds but just to the user?
     lockingSound: null
+    breakOnAccessBreaker: true # more fun
     lockTime: 0
     unlockTime: 0
-  - type: TriggerOnVoice
-    listenRange: 2 # more fun
-    startRecordingVerb: voice-trigger-lock-verb-record
-    recordingVerbMessage: voice-trigger-lock-verb-message
-    inspectUninitializedLoc: voice-trigger-lock-on-uninitialized
-    inspectInitializedLoc: voice-trigger-lock-on-examine
   - type: LockOnTrigger
-  - type: ActiveListener
-  - type: VoiceTriggerLock
+  - type: SelectableComponentAdder
+    selections: 1
+    entries:
+    - verbName: selectable-lock-verb-no-lock
+      popup: selectable-lock-verb-no-lock-popup
+      priority: 0
+      componentsToAdd: null
+    - verbName: voice-trigger-lock-add-verb
+      popup: voice-trigger-lock-add-verb-popup
+      priority: 1
+      componentsToAdd:
+      - type: TriggerOnVoice
+        listenRange: 2 # more fun
+        startRecordingVerb: voice-trigger-lock-verb-record
+        recordingVerbMessage: voice-trigger-lock-verb-message
+        inspectUninitializedLoc: voice-trigger-lock-on-uninitialized
+        inspectInitializedLoc: voice-trigger-lock-on-examine
+      - type: ActiveListener
+      - type: VoiceTriggerLock
+    verbCategoryName: selectable-lock-verb-category-name
index 8b891a75333a83a51705f9b06bbe658b821f79b6..fcf0b91f8ec20faf34a2031dbe7611002f06f0b5 100644 (file)
@@ -54,7 +54,7 @@
   - type: DisarmMalus
 
 - type: entity
-  parent: [Cane, VoiceLock]
+  parent: [Cane, SelectableLock]
   id: CaneSheath
   suffix: Empty
   components:
index 5f431416e72e664e46e238ce53a5170c3c587312..04b65ddb6baf74a5d91006385583129ed56c6d91 100644 (file)
 
 - type: entity
   name: pen
-  parent: [BaseMeleeWeaponEnergy, VoiceLock]
+  parent: [BaseMeleeWeaponEnergy, SelectableLock]
   id: EnergyDagger
   suffix: E-Dagger
   description: 'A dark ink pen.'
index 6969380621d6bb55e938eeb80260b0b1e04334cb..ed08c979b5c0b9f399f5d4643470c52ed531941e 100644 (file)
@@ -1,6 +1,6 @@
 # for clothing that can be toggled, like magboots
 - type: entity
-  parent: VoiceLock
+  parent: SelectableLock
   abstract: true
   id: BaseChameleon
   components: