Syndicate locks are now selectable (#39532)

* Syndicate locks are now selectable

* Minor tweaks

* Make not syndicate themed

* Address refview

* review

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
beck-thompson
2025-08-26 07:18:10 -07:00
committed by GitHub
parent 49888f3c47
commit 688c0b5884
11 changed files with 211 additions and 19 deletions

View File

@@ -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,
}

View File

@@ -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);
}
}
}

View 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";
}

View File

@@ -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)}.

View 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!

View File

@@ -0,0 +1 @@
selectable-component-adder-category-name = Add feature

View File

@@ -1467,7 +1467,7 @@
- MedTekCartridge
- type: entity
parent: [BasePDA, VoiceLock]
parent: [BasePDA, SelectableLock]
id: ChameleonPDA
name: passenger PDA
description: Why isn't it gray?

View File

@@ -1,5 +1,5 @@
- type: entity
id: VoiceLock
id: SelectableLock
abstract: true
components:
- type: Lock
@@ -11,14 +11,27 @@
useAccess: false
unlockingSound: null # TODO: Maybe add sounds but just to the user?
lockingSound: null
breakOnAccessBreaker: true # more fun
lockTime: 0
unlockTime: 0
- type: LockOnTrigger
- 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: LockOnTrigger
- type: ActiveListener
- type: VoiceTriggerLock
verbCategoryName: selectable-lock-verb-category-name

View File

@@ -54,7 +54,7 @@
- type: DisarmMalus
- type: entity
parent: [Cane, VoiceLock]
parent: [Cane, SelectableLock]
id: CaneSheath
suffix: Empty
components:

View File

@@ -159,7 +159,7 @@
- type: entity
name: pen
parent: [BaseMeleeWeaponEnergy, VoiceLock]
parent: [BaseMeleeWeaponEnergy, SelectableLock]
id: EnergyDagger
suffix: E-Dagger
description: 'A dark ink pen.'

View File

@@ -1,6 +1,6 @@
# for clothing that can be toggled, like magboots
- type: entity
parent: VoiceLock
parent: SelectableLock
abstract: true
id: BaseChameleon
components: