diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 2ca1404bc5..194844997b 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -847,6 +847,16 @@ public sealed partial class ChatSystem : SharedChatSystem return modifiedMessage.ToString(); } + public string BuildGibberishString(IReadOnlyList charOptions, int length) + { + var sb = new StringBuilder(); + for (var i = 0; i < length; i++) + { + sb.Append(_random.Pick(charOptions)); + } + return sb.ToString(); + } + #endregion } diff --git a/Content.Server/Speech/Components/BlockListeningComponent.cs b/Content.Server/Speech/Components/BlockListeningComponent.cs new file mode 100644 index 0000000000..3c747252ba --- /dev/null +++ b/Content.Server/Speech/Components/BlockListeningComponent.cs @@ -0,0 +1,11 @@ + +namespace Content.Server.Speech.Components; + +/// +/// Causes all ListenAttemptEvents to fail on the entity. +/// +[RegisterComponent] +public sealed partial class BlockListeningComponent : Component +{ + +} diff --git a/Content.Server/Speech/Components/ListenWireAction.cs b/Content.Server/Speech/Components/ListenWireAction.cs new file mode 100644 index 0000000000..1748ab3d6d --- /dev/null +++ b/Content.Server/Speech/Components/ListenWireAction.cs @@ -0,0 +1,111 @@ +using System.Text; + +using Content.Server.Speech.Components; +using Content.Server.Chat.Systems; +using Content.Server.Speech.EntitySystems; +using Content.Server.VoiceMask; +using Content.Server.Wires; +using Content.Shared.Speech; +using Content.Shared.Wires; + +namespace Content.Server.Speech; + +public sealed partial class ListenWireAction : BaseToggleWireAction +{ + private WiresSystem _wires = default!; + private ChatSystem _chat = default!; + + /// + /// Length of the gibberish string sent when pulsing the wire + /// + private int _noiseLength = 16; + public override Color Color { get; set; } = Color.Green; + public override string Name { get; set; } = "wire-name-listen"; + + public override object? StatusKey { get; } = ListenWireActionKey.StatusKey; + + public override object? TimeoutKey { get; } = ListenWireActionKey.TimeoutKey; + + public override int Delay { get; } = 10; + + public override void Initialize() + { + base.Initialize(); + + _wires = EntityManager.System(); + _chat = EntityManager.System(); + } + public override StatusLightState? GetLightState(Wire wire) + { + if (GetValue(wire.Owner)) + return StatusLightState.On; + else + { + if (TimeoutKey != null && _wires.HasData(wire.Owner, TimeoutKey)) + return StatusLightState.BlinkingSlow; + return StatusLightState.Off; + } + } + public override void ToggleValue(EntityUid owner, bool setting) + { + if (setting) + { + // If we defer removal, the status light gets out of sync + EntityManager.RemoveComponent(owner); + } + else + { + EntityManager.EnsureComponent(owner); + } + } + + public override bool GetValue(EntityUid owner) + { + return !EntityManager.HasComponent(owner); + } + + public override void Pulse(EntityUid user, Wire wire) + { + if (!GetValue(wire.Owner) || !IsPowered(wire.Owner)) + return; + + // We have to use a valid euid in the ListenEvent. The user seems + // like a sensible choice, but we need to mask their name. + + // Save the user's existing voicemask if they have one + var oldEnabled = true; + var oldVoiceName = Loc.GetString("wire-listen-pulse-error-name"); + if (EntityManager.TryGetComponent(user, out var oldMask)) + { + oldEnabled = oldMask.Enabled; + oldVoiceName = oldMask.VoiceName; + } + + // Give the user a temporary voicemask component + var mask = EntityManager.EnsureComponent(user); + mask.Enabled = true; + mask.VoiceName = Loc.GetString("wire-listen-pulse-identifier"); + + var chars = Loc.GetString("wire-listen-pulse-characters").ToCharArray(); + var noiseMsg = _chat.BuildGibberishString(chars, _noiseLength); + + var attemptEv = new ListenAttemptEvent(wire.Owner); + EntityManager.EventBus.RaiseLocalEvent(wire.Owner, attemptEv); + if (!attemptEv.Cancelled) + { + var ev = new ListenEvent(noiseMsg, user); + EntityManager.EventBus.RaiseLocalEvent(wire.Owner, ev); + } + + // Remove the voicemask component, or set it back to what it was before + if (oldMask == null) + EntityManager.RemoveComponent(user, mask); + else + { + mask.Enabled = oldEnabled; + mask.VoiceName = oldVoiceName; + } + + base.Pulse(user, wire); + } +} diff --git a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs new file mode 100644 index 0000000000..a13eda1f73 --- /dev/null +++ b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs @@ -0,0 +1,18 @@ +using Content.Server.Speech.Components; + +namespace Content.Server.Speech.EntitySystems; + +public sealed class BlockListeningSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnListenAttempt); + } + + private void OnListenAttempt(EntityUid uid, BlockListeningComponent component, ListenAttemptEvent args) + { + args.Cancel(); + } +} diff --git a/Content.Shared/Speech/EntitySystems/SharedListenWireAction.cs b/Content.Shared/Speech/EntitySystems/SharedListenWireAction.cs new file mode 100644 index 0000000000..4ffa13e0a7 --- /dev/null +++ b/Content.Shared/Speech/EntitySystems/SharedListenWireAction.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Speech; + +[Serializable, NetSerializable] +public enum ListenWireActionKey : byte +{ + StatusKey, + TimeoutKey, +} diff --git a/Resources/Locale/en-US/speech/listen-wire-action.ftl b/Resources/Locale/en-US/speech/listen-wire-action.ftl new file mode 100644 index 0000000000..4e8d2118fc --- /dev/null +++ b/Resources/Locale/en-US/speech/listen-wire-action.ftl @@ -0,0 +1,3 @@ +wire-listen-pulse-identifier = electricity +wire-listen-pulse-characters = eee EEo +wire-listen-pulse-error-name = ERROR diff --git a/Resources/Locale/en-US/wires/wire-names.ftl b/Resources/Locale/en-US/wires/wire-names.ftl index 3204426fdb..8b760ca60f 100644 --- a/Resources/Locale/en-US/wires/wire-names.ftl +++ b/Resources/Locale/en-US/wires/wire-names.ftl @@ -64,3 +64,4 @@ wire-name-bomb-proceed = PRCD wire-name-bomb-boom = BOOM wire-name-bomb-bolt = BOLT wire-name-speech = SPKR +wire-name-listen = MIC diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml index 4ff7638594..9848993b08 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -23,6 +23,7 @@ - type: Clickable - type: InteractionOutline - type: Appearance + - type: WiresVisuals - type: ContainerFill containers: board: [ IntercomElectronics ] @@ -46,6 +47,10 @@ map: ["enum.RadioDeviceVisualLayers.Speaker"] shader: unshaded visible: false + - state: panel + map: ["enum.WiresVisualLayers.MaintenancePanel"] + shader: unshaded + visible: false - type: Transform noRot: false anchored: true @@ -60,6 +65,8 @@ interfaces: - key: enum.IntercomUiKey.Key type: IntercomBoundUserInterface + - key: enum.WiresUiKey.Key + type: WiresBoundUserInterface - type: Construction graph: Intercom node: intercom @@ -87,6 +94,10 @@ volume: -4 - type: GenericVisualizer visuals: + enum.WiresVisualLayers.MaintenancePanel: + enum.WiresVisualLayers.MaintenancePanel: + True: { visible: true } + False: { visible: false } enum.PowerDeviceVisuals.Powered: enum.PowerDeviceVisualLayers.Powered: True: { visible: true } diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index d54965d887..338bf188ba 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -49,6 +49,13 @@ - !type:AirAlarmPanicWire - !type:AtmosMonitorDeviceNetWire +- type: wireLayout + id: Intercom + wires: + - !type:PowerWireAction + - !type:SpeechWireAction + - !type:ListenWireAction + - type: wireLayout id: FireAlarm wires: diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json index 78fe02a9bb..f5a9d7b89c 100644 --- a/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json +++ b/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json @@ -26,6 +26,10 @@ { "name": "unshaded", "directions": 4 + }, + { + "name": "panel", + "directions": 4 } ] } diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/panel.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/panel.png new file mode 100644 index 0000000000..3bfeb8df58 Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/panel.png differ