Add voice locks to various hidden syndicate items (#39310)

This commit is contained in:
beck-thompson
2025-08-10 11:10:13 -07:00
committed by GitHub
parent 80299e863a
commit 80375370f8
36 changed files with 366 additions and 97 deletions

View File

@@ -13,6 +13,7 @@ using Content.Server.Clothing.Systems;
using Content.Server.Implants;
using Content.Shared.Implants;
using Content.Shared.Inventory;
using Content.Shared.Lock;
using Content.Shared.PDA;
namespace Content.Server.Access.Systems
@@ -25,6 +26,7 @@ namespace Content.Server.Access.Systems
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ChameleonClothingSystem _chameleon = default!;
[Dependency] private readonly ChameleonControllerSystem _chamController = default!;
[Dependency] private readonly LockSystem _lock = default!;
public override void Initialize()
{
@@ -79,7 +81,8 @@ namespace Content.Server.Access.Systems
private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach || !TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target))
if (args.Target == null || !args.CanReach || _lock.IsLocked(uid) ||
!TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target))
return;
if (!TryComp<AccessComponent>(uid, out var access) || !HasComp<IdCardComponent>(uid))

View File

@@ -5,11 +5,13 @@ using Content.Shared.Chat;
using Content.Shared.Clothing;
using Content.Shared.Database;
using Content.Shared.Inventory;
using Content.Shared.Lock;
using Content.Shared.Popups;
using Content.Shared.Preferences;
using Content.Shared.Speech;
using Content.Shared.VoiceMask;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Server.VoiceMask;
@@ -22,6 +24,8 @@ public sealed partial class VoiceMaskSystem : EntitySystem
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
// CCVar.
private int _maxNameLength;
@@ -30,6 +34,7 @@ public sealed partial class VoiceMaskSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<VoiceMaskComponent, InventoryRelayedEvent<TransformSpeakerNameEvent>>(OnTransformSpeakerName);
SubscribeLocalEvent<VoiceMaskComponent, LockToggledEvent>(OnLockToggled);
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName);
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeVerbMessage>(OnChangeVerb);
SubscribeLocalEvent<VoiceMaskComponent, ClothingGotEquippedEvent>(OnEquip);
@@ -44,6 +49,14 @@ public sealed partial class VoiceMaskSystem : EntitySystem
args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb;
}
private void OnLockToggled(Entity<VoiceMaskComponent> ent, ref LockToggledEvent args)
{
if (args.Locked)
_actions.RemoveAction(ent.Comp.ActionEntity);
else if (_container.TryGetContainingContainer(ent.Owner, out var container))
_actions.AddAction(container.Owner, ref ent.Comp.ActionEntity, ent.Comp.Action, ent);
}
#region User inputs from UI
private void OnChangeVerb(Entity<VoiceMaskComponent> entity, ref VoiceMaskChangeVerbMessage msg)
{
@@ -78,6 +91,9 @@ public sealed partial class VoiceMaskSystem : EntitySystem
#region UI
private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args)
{
if (_lock.IsLocked(uid))
return;
_actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid);
}

View File

@@ -5,6 +5,7 @@ using Content.Shared.Contraband;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Lock;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Shared.Prototypes;
@@ -23,6 +24,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] protected readonly IGameTiming _timing = default!;
[Dependency] private readonly LockSystem _lock = default!;
private static readonly SlotFlags[] IgnoredSlots =
{
@@ -122,7 +124,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
private void OnVerb(Entity<ChameleonClothingComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || ent.Comp.User != args.User)
if (!args.CanAccess || !args.CanInteract || _lock.IsLocked(ent.Owner))
return;
// Can't pass args from a ref event inside of lambdas

View File

@@ -78,7 +78,7 @@ public sealed class ItemToggleSystem : EntitySystem
if (ent.Comp.Activated)
{
var ev = new ItemToggleActivateAttemptEvent(args.User);
var ev = new ItemToggleDeactivateAttemptEvent(args.User);
RaiseLocalEvent(ent.Owner, ref ev);
if (ev.Cancelled)
@@ -86,7 +86,7 @@ public sealed class ItemToggleSystem : EntitySystem
}
else
{
var ev = new ItemToggleDeactivateAttemptEvent(args.User);
var ev = new ItemToggleActivateAttemptEvent(args.User);
RaiseLocalEvent(ent.Owner, ref ev);
if (ev.Cancelled)

View File

@@ -5,13 +5,19 @@ namespace Content.Shared.Lock;
/// <summary>
/// This is used for toggleable items that require the entity to have a lock in a certain state.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(LockSystem))]
public sealed partial class ItemToggleRequiresLockComponent : Component
{
/// <summary>
/// TRUE: the lock must be locked to toggle the item.
/// FALSE: the lock must be unlocked to toggle the item.
/// </summary>
[DataField]
[DataField, AutoNetworkedField]
public bool RequireLocked;
/// <summary>
/// Popup text for when someone tries to toggle the item, but it's locked. If null, no popup will be shown.
/// </summary>
[DataField]
public LocId? LockedPopup = "lock-comp-generic-fail";
}

View File

@@ -21,6 +21,18 @@ public sealed partial class LockComponent : Component
[AutoNetworkedField]
public bool Locked = true;
/// <summary>
/// If true, will show verbs to lock and unlock the item. Otherwise, it will not.
/// </summary>
[DataField, AutoNetworkedField]
public bool ShowLockVerbs = true;
/// <summary>
/// If true will show examine text.
/// </summary>
[DataField, AutoNetworkedField]
public bool ShowExamine = true;
/// <summary>
/// Whether or not the lock is locked by simply clicking.
/// </summary>
@@ -44,7 +56,7 @@ public sealed partial class LockComponent : Component
/// The sound played when unlocked.
/// </summary>
[DataField("unlockingSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg")
public SoundSpecifier? UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg")
{
Params = AudioParams.Default.WithVolume(-5f),
};
@@ -53,7 +65,7 @@ public sealed partial class LockComponent : Component
/// The sound played when locked.
/// </summary>
[DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg")
public SoundSpecifier? LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg")
{
Params = AudioParams.Default.WithVolume(-5f)
};

View File

@@ -28,12 +28,12 @@ public sealed class LockSystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
/// <inheritdoc />
public override void Initialize()
@@ -54,8 +54,8 @@ public sealed class LockSystem : EntitySystem
SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel);
SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, LockToggledEvent>(LockToggled);
SubscribeLocalEvent<UIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<UIRequiresLockComponent, LockToggledEvent>(LockToggled);
SubscribeLocalEvent<ItemToggleRequiresLockComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
}
@@ -96,6 +96,9 @@ public sealed class LockSystem : EntitySystem
private void OnExamined(EntityUid uid, LockComponent lockComp, ExaminedEvent args)
{
if (!lockComp.ShowExamine)
return;
args.PushText(Loc.GetString(lockComp.Locked
? "lock-comp-on-examined-is-locked"
: "lock-comp-on-examined-is-unlocked",
@@ -239,6 +242,20 @@ public sealed class LockSystem : EntitySystem
return true;
}
/// <summary>
/// Toggle the lock to locked if unlocked, and unlocked if locked.
/// </summary>
/// <param name="uid">Entity to toggle the lock state of.</param>
/// <param name="user">The person trying to toggle the lock</param>
/// <param name="lockComp">Entities lock comp (will be resolved)</param>
public void ToggleLock(EntityUid uid, EntityUid? user, LockComponent? lockComp = null)
{
if (IsLocked((uid, lockComp)))
Unlock(uid, user, lockComp);
else
Lock(uid, user, lockComp);
}
/// <summary>
/// Returns true if the entity is locked.
/// Entities with no lock component are considered unlocked.
@@ -287,7 +304,7 @@ public sealed class LockSystem : EntitySystem
private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract)
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract || !component.ShowLockVerbs)
return;
AlternativeVerb verb = new()
@@ -394,41 +411,54 @@ public sealed class LockSystem : EntitySystem
args.Cancel();
}
private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args)
private void OnUIOpenAttempt(EntityUid uid, UIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
return;
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked != component.RequireLocked)
{
args.Cancel();
if (lockComp.Locked)
{
_sharedPopupSystem.PopupClient(Loc.GetString("entity-storage-component-locked-message"), uid, args.User);
}
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
return;
_audio.PlayPredicted(component.AccessDeniedSound, uid, args.User);
args.Cancel();
if (lockComp.Locked && component.Popup != null)
{
_sharedPopupSystem.PopupClient(Loc.GetString(component.Popup), uid, args.User);
}
_audio.PlayPredicted(component.AccessDeniedSound, uid, args.User);
}
private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args)
private void LockToggled(EntityUid uid, UIRequiresLockComponent component, LockToggledEvent args)
{
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
return;
_activatableUI.CloseAll(uid);
if (component.UserInterfaceKeys == null)
{
_ui.CloseUis(uid);
return;
}
foreach (var key in component.UserInterfaceKeys)
{
_ui.CloseUi(uid, key);
}
}
private void OnActivateAttempt(EntityUid uid, ItemToggleRequiresLockComponent component, ref ItemToggleActivateAttemptEvent args)
{
if (args.Cancelled)
return;
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked != component.RequireLocked)
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
return;
args.Cancelled = true;
if (lockComp.Locked && component.LockedPopup != null)
{
args.Cancelled = true;
if (lockComp.Locked)
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-generic-fail",
("target", Identity.Entity(uid, EntityManager))),
_sharedPopupSystem.PopupClient(Loc.GetString(component.LockedPopup,
("target", Identity.Entity(uid, EntityManager))),
uid,
args.User);
}

View File

@@ -7,8 +7,15 @@ namespace Content.Shared.Lock;
/// This is used for activatable UIs that require the entity to have a lock in a certain state.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
public sealed partial class ActivatableUIRequiresLockComponent : Component
public sealed partial class UIRequiresLockComponent : Component
{
/// <summary>
/// UIs that are locked behind this component.
/// If null, will close all UIs.
/// </summary>
[DataField]
public List<Enum>? UserInterfaceKeys;
/// <summary>
/// TRUE: the lock must be locked to access the UI.
/// FALSE: the lock must be unlocked to access the UI.
@@ -21,4 +28,7 @@ public sealed partial class ActivatableUIRequiresLockComponent : Component
/// </summary>
[DataField]
public SoundSpecifier? AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
[DataField]
public LocId? Popup = "entity-storage-component-locked-message";
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Item.ItemToggle;
using Content.Shared.Lock;
using Content.Shared.Trigger.Components.Triggers;
namespace Content.Shared.SecretLocks;
public sealed partial class SharedVoiceTriggerLockSystem : EntitySystem
{
[Dependency] private readonly ItemToggleSystem _toggle = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VoiceTriggerLockComponent, LockToggledEvent>(OnLockToggled);
}
private void OnLockToggled(Entity<VoiceTriggerLockComponent> ent, ref LockToggledEvent args)
{
if (!TryComp<TriggerOnVoiceComponent>(ent.Owner, out var triggerComp))
return;
triggerComp.ShowVerbs = !args.Locked;
triggerComp.ShowExamine = !args.Locked;
_toggle.TryDeactivate(ent.Owner, null, true, false);
Dirty(ent.Owner, triggerComp);
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameStates;
namespace Content.Shared.SecretLocks;
/// <summary>
/// "Locks" items (Doesn't actually lock them but just switches various settings) so its not possible to tell
/// the item is triggered by a voice activation.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class VoiceTriggerLockComponent : Component;

View File

@@ -0,0 +1,19 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Trigger.Components.Effects;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class LockOnTriggerComponent : BaseXOnTriggerComponent
{
[DataField, AutoNetworkedField]
public LockAction LockOnTrigger = LockAction.Toggle;
}
[Serializable, NetSerializable]
public enum LockAction
{
Lock = 0,
Unlock = 1,
Toggle = 2,
}

View File

@@ -44,4 +44,52 @@ public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent
/// </summary>
[DataField, AutoNetworkedField]
public int MaxLength = 50;
/// <summary>
/// When examining the item, should it show information about what word is recorded?
/// </summary>
[DataField, AutoNetworkedField]
public bool ShowExamine = true;
/// <summary>
/// Should there be verbs that allow re-recording of the trigger word?
/// </summary>
[DataField, AutoNetworkedField]
public bool ShowVerbs = true;
/// <summary>
/// The verb text that is shown when you can start recording a message.
/// </summary>
[DataField]
public LocId StartRecordingVerb = "trigger-on-voice-record";
/// <summary>
/// The verb text that is shown when you can stop recording a message.
/// </summary>
[DataField]
public LocId StopRecordingVerb = "trigger-on-voice-stop";
/// <summary>
/// Tooltip that appears when hovering over the stop or start recording verbs.
/// </summary>
[DataField]
public LocId? RecordingVerbMessage;
/// <summary>
/// The verb text that is shown when you can clear a recording.
/// </summary>
[DataField]
public LocId ClearRecordingVerb = "trigger-on-voice-clear";
/// <summary>
/// The loc string that is shown when inspecting an uninitialized voice trigger.
/// </summary>
[DataField]
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]
public LocId? InspectInitializedLoc = "trigger-on-voice-examine";
}

View File

@@ -0,0 +1,35 @@
using Content.Shared.Lock;
using Content.Shared.Trigger.Components.Effects;
namespace Content.Shared.Trigger.Systems;
public sealed class LockOnTriggerSystem : EntitySystem
{
[Dependency] private readonly LockSystem _lock = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LockOnTriggerComponent, TriggerEvent>(OnTrigger);
}
private void OnTrigger(Entity<LockOnTriggerComponent> ent, ref TriggerEvent args)
{
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
return;
switch (ent.Comp.LockOnTrigger)
{
case LockAction.Lock:
_lock.Lock(ent.Owner, args.User);
break;
case LockAction.Unlock:
_lock.Unlock(ent, args.User);
break;
case LockAction.Toggle:
_lock.ToggleLock(ent, args.User);
break;
}
}
}

View File

@@ -25,15 +25,21 @@ public sealed partial class TriggerSystem
RemCompDeferred<ActiveListenerComponent>(ent);
}
private void OnVoiceExamine(Entity<TriggerOnVoiceComponent> ent, ref ExaminedEvent args)
private void OnVoiceExamine(EntityUid uid, TriggerOnVoiceComponent component, ExaminedEvent args)
{
if (args.IsInDetailsRange)
if (!args.IsInDetailsRange || !component.ShowExamine)
return;
if (component.InspectUninitializedLoc != null && string.IsNullOrWhiteSpace(component.KeyPhrase))
{
args.PushText(string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase)
? Loc.GetString("trigger-on-voice-uninitialized")
: Loc.GetString("trigger-on-voice-examine", ("keyphrase", ent.Comp.KeyPhrase)));
args.PushText(Loc.GetString(component.InspectUninitializedLoc));
}
else if (component.InspectInitializedLoc != null && !string.IsNullOrWhiteSpace(component.KeyPhrase))
{
args.PushText(Loc.GetString(component.InspectInitializedLoc.Value, ("keyphrase", component.KeyPhrase)));
}
}
private void OnListen(Entity<TriggerOnVoiceComponent> ent, ref ListenEvent args)
{
var component = ent.Comp;
@@ -71,13 +77,13 @@ public sealed partial class TriggerSystem
private void OnVoiceGetAltVerbs(Entity<TriggerOnVoiceComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
if (!args.CanInteract || !args.CanAccess || !ent.Comp.ShowVerbs)
return;
var user = args.User;
args.Verbs.Add(new AlternativeVerb
{
Text = Loc.GetString(ent.Comp.IsRecording ? "trigger-on-voice-stop" : "trigger-on-voice-record"),
Text = Loc.GetString(ent.Comp.IsRecording ? ent.Comp.StopRecordingVerb : ent.Comp.StartRecordingVerb),
Act = () =>
{
if (ent.Comp.IsRecording)
@@ -93,7 +99,7 @@ public sealed partial class TriggerSystem
args.Verbs.Add(new AlternativeVerb
{
Text = Loc.GetString("trigger-on-voice-clear"),
Text = Loc.GetString(ent.Comp.ClearRecordingVerb),
Act = () =>
{
ClearRecording(ent);

View File

@@ -116,7 +116,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
}
}
return args.CanInteract || HasComp<GhostComponent>(args.User) && !component.BlockSpectators;
return (args.CanInteract || HasComp<GhostComponent>(args.User) && !component.BlockSpectators) && !RaiseCanOpenEventChecks(args.User, uid);
}
private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
@@ -225,11 +225,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
// If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
// This is so that stuff can require further conditions (like power).
var oae = new ActivatableUIOpenAttemptEvent(user);
var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity);
RaiseLocalEvent(user, uae);
RaiseLocalEvent(uiEntity, oae);
if (oae.Cancelled || uae.Cancelled)
if (RaiseCanOpenEventChecks(user, uiEntity))
return false;
// Give the UI an opportunity to prepare itself if it needs to do anything
@@ -286,4 +282,15 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (ent.Comp.InHandsOnly)
CloseAll(ent, ent);
}
private bool RaiseCanOpenEventChecks(EntityUid user, EntityUid uiEntity)
{
// If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
// This is so that stuff can require further conditions (like power).
var oae = new ActivatableUIOpenAttemptEvent(user);
var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity);
RaiseLocalEvent(user, uae);
RaiseLocalEvent(uiEntity, oae);
return oae.Cancelled || uae.Cancelled;
}
}

View File

@@ -8,3 +8,5 @@ agent-id-card-current-name = Name:
agent-id-card-current-job = Job:
agent-id-card-job-icon-label = Job icon:
agent-id-menu-title = Agent ID Card
agent-id-open-ui-verb = Change settings

View File

@@ -0,0 +1,5 @@
voice-trigger-lock-verb-record = Record lock phrase
voice-trigger-lock-verb-message = Locking the item will disable features that reveal its true nature!
voice-trigger-lock-on-uninitialized = The display is blank
voice-trigger-lock-on-examine = The display shows the passphrase: "{$keyphrase}"

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingBackpack
parent: [ClothingBackpack, BaseChameleon]
id: ClothingBackpackChameleon
name: backpack
description: You wear this on your back and put items into it.

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingHeadsetGrey
parent: [ClothingHeadsetGrey, BaseChameleon]
id: ClothingHeadsetChameleon
name: passenger headset
description: An updated, modular intercom that fits over the head. Takes encryption keys.
@@ -14,7 +14,3 @@
- type: ChameleonClothing
slot: [ears]
default: ClothingHeadsetGrey
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingEyesBase
parent: [ClothingEyesBase, BaseChameleon]
id: ClothingEyesChameleon # no flash immunity, sorry
name: sun glasses
description: Useful both for security and cargonia.
@@ -15,8 +15,3 @@
- type: ChameleonClothing
slot: [eyes]
default: ClothingEyesGlassesSunglasses
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingHandsButcherable
parent: [ClothingHandsButcherable, BaseChameleon]
id: ClothingHandsChameleon # doesn't protect from electricity or heat
name: black gloves
description: Regular black gloves that do not keep you from frying.
@@ -17,10 +17,6 @@
- type: Fiber
fiberMaterial: fibers-chameleon
- type: FingerprintMask
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface
- type: entity
parent: ClothingHandsChameleon

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingHeadBase
parent: [ClothingHeadBase, BaseChameleon]
id: ClothingHeadHatChameleon
name: beret
description: A beret, an artists favorite headwear.
@@ -14,10 +14,6 @@
- type: ChameleonClothing
slot: [HEAD]
default: ClothingHeadHatBeret
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface
- type: entity
parent: ClothingHeadHatFedoraBrown

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingMaskBase
parent: [ClothingMaskBase, BaseChameleon]
id: ClothingMaskGasChameleon
name: gas mask
description: A face-covering mask that can be connected to an air supply.
@@ -16,10 +16,6 @@
default: ClothingMaskGas
- type: BreathMask
- type: IdentityBlocker # need that for default ClothingMaskGas
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface
- type: HideLayerClothing
slots:
- Snout
@@ -30,6 +26,12 @@
suffix: Voice Mask, Chameleon
components:
- type: VoiceMask
- type: UIRequiresLock
userInterfaceKeys:
- enum.ChameleonUiKey.Key
- enum.VoiceMaskUIKey.Key
accessDeniedSound: null
popup: null
- type: HideLayerClothing
slots:
- Snout

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingNeckBase
parent: [ClothingNeckBase, BaseChameleon]
id: ClothingNeckChameleon
name: striped red scarf
description: A stylish striped red scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks.
@@ -14,7 +14,3 @@
- type: ChameleonClothing
slot: [NECK]
default: ClothingNeckScarfStripedRed
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingOuterBase
parent: [ClothingOuterBase, BaseChameleon]
id: ClothingOuterChameleon
name: vest
description: A thick vest with a rubbery, water-resistant shell.
@@ -14,10 +14,6 @@
- type: ChameleonClothing
slot: [outerClothing]
default: ClothingOuterVest
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface
- type: TemperatureProtection # Same as a basic winter coat.
heatingCoefficient: 1.1
coolingCoefficient: 0.1

View File

@@ -163,7 +163,7 @@
sprite: Clothing/Shoes/Specific/wizard.rsi
- type: entity
parent: ClothingShoesBase
parent: [ClothingShoesBase, BaseChameleon]
id: ClothingShoesChameleon
name: black shoes
suffix: Chameleon
@@ -197,10 +197,6 @@
- type: ChameleonClothing
slot: [FEET]
default: ClothingShoesColorBlack
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface
- type: entity
parent: ClothingShoesChameleon

View File

@@ -1,5 +1,5 @@
- type: entity
parent: ClothingUniformBase
parent: [ClothingUniformBase, BaseChameleon]
id: ClothingUniformJumpsuitChameleon
name: black jumpsuit
description: A generic black jumpsuit with no rank markings.
@@ -36,7 +36,3 @@
- type: ChameleonClothing
slot: [innerclothing]
default: ClothingUniformJumpsuitColorBlack
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface

View File

@@ -167,7 +167,9 @@
- type: Lock
locked: true
unlockOnClick: false
- type: ActivatableUIRequiresLock
- type: UIRequiresLock
userInterfaceKeys:
- enum.BorgUiKey.Key
- type: LockedWiresPanel
- type: Damageable
damageContainer: Silicon

View File

@@ -2320,6 +2320,7 @@
types:
Heat : 0.2 #per second, scales with temperature & other constants
# TODO: Make grenade penguin voice activated like the rest of the stealth items.
- type: entity
name: grenade penguin
parent: [ MobPenguin, MobCombat, BaseSyndicateContraband ]

View File

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

View File

@@ -580,7 +580,7 @@
- type: entity
name: passenger ID card
parent: IDCardStandard
parent: [IDCardStandard, BaseChameleon]
id: AgentIDCard
suffix: Agent
components:
@@ -595,9 +595,11 @@
- state: default
- state: idpassenger
- type: AgentIDCard
- type: UIRequiresLock
- type: ActivatableUI
key: enum.AgentIDCardUiKey.Key
inHandsOnly: true
verbText: agent-id-open-ui-verb
- type: Tag
tags:
- DoorBumpOpener

View File

@@ -0,0 +1,24 @@
- type: entity
id: VoiceLock
abstract: true
components:
- type: Lock
locked: false
showLockVerbs: false
showExamine: false
lockOnClick: false
unlockOnClick: false
useAccess: false
unlockingSound: null # TODO: Maybe add sounds but just to the user?
lockingSound: null
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

View File

@@ -53,7 +53,7 @@
- type: DisarmMalus
- type: entity
parent: Cane
parent: [Cane, VoiceLock]
id: CaneSheath
suffix: Empty
components:
@@ -69,6 +69,9 @@
interfaces:
enum.StorageUiKey.Key:
type: StorageBoundUserInterface
- type: ItemSlotsLock
slots:
- item
- type: ItemSlots
slots:
item:

View File

@@ -159,7 +159,7 @@
- type: entity
name: pen
parent: BaseMeleeWeaponEnergy
parent: [BaseMeleeWeaponEnergy, VoiceLock]
id: EnergyDagger
suffix: E-Dagger
description: 'A dark ink pen.'
@@ -222,6 +222,16 @@
damage:
types:
Blunt: 1
- type: EmitSoundOnUse
sound:
path: /Audio/Items/pen_click.ogg
params:
volume: -4
maxDistance: 2
- type: UseDelay
delay: 1.5
- type: ItemToggleRequiresLock # TODO: FIX THIS VERB IS COOKED
lockedPopup: null
- type: Tag
tags:
- Write

View File

@@ -37,7 +37,9 @@
3: { state: can-o3, shader: "unshaded" }
- type: ActivatableUI
key: enum.GasCanisterUiKey.Key
- type: ActivatableUIRequiresLock
- type: UIRequiresLock
userInterfaceKeys:
- enum.GasCanisterUiKey.Key
- type: UserInterface
interfaces:
enum.GasCanisterUiKey.Key:

View File

@@ -0,0 +1,15 @@
# for clothing that can be toggled, like magboots
- type: entity
parent: VoiceLock
abstract: true
id: BaseChameleon
components:
- type: UIRequiresLock
userInterfaceKeys:
- enum.ChameleonUiKey.Key
accessDeniedSound: null
popup: null
- type: UserInterface
interfaces:
enum.ChameleonUiKey.Key:
type: ChameleonBoundUserInterface