Hackable intercoms (#23984)

* Enable wire interface for intercom

* Implement BlockListening component and system

* Implement ListenWireAction

* Added cooldown/overload to mic wire pulse

* Properly persist voicemask settings when user already has one.

* Addressed requested changes

* Added wire panel open/closed visuals
This commit is contained in:
Tayrtahn
2024-01-14 00:37:28 -05:00
committed by GitHub
parent 22c0b4425d
commit 2d6d2aba0b
11 changed files with 186 additions and 0 deletions

View File

@@ -847,6 +847,16 @@ public sealed partial class ChatSystem : SharedChatSystem
return modifiedMessage.ToString(); return modifiedMessage.ToString();
} }
public string BuildGibberishString(IReadOnlyList<char> charOptions, int length)
{
var sb = new StringBuilder();
for (var i = 0; i < length; i++)
{
sb.Append(_random.Pick(charOptions));
}
return sb.ToString();
}
#endregion #endregion
} }

View File

@@ -0,0 +1,11 @@
namespace Content.Server.Speech.Components;
/// <summary>
/// Causes all ListenAttemptEvents to fail on the entity.
/// </summary>
[RegisterComponent]
public sealed partial class BlockListeningComponent : Component
{
}

View File

@@ -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!;
/// <summary>
/// Length of the gibberish string sent when pulsing the wire
/// </summary>
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<WiresSystem>();
_chat = EntityManager.System<ChatSystem>();
}
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<BlockListeningComponent>(owner);
}
else
{
EntityManager.EnsureComponent<BlockListeningComponent>(owner);
}
}
public override bool GetValue(EntityUid owner)
{
return !EntityManager.HasComponent<BlockListeningComponent>(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<VoiceMaskComponent>(user, out var oldMask))
{
oldEnabled = oldMask.Enabled;
oldVoiceName = oldMask.VoiceName;
}
// Give the user a temporary voicemask component
var mask = EntityManager.EnsureComponent<VoiceMaskComponent>(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);
}
}

View File

@@ -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<BlockListeningComponent, ListenAttemptEvent>(OnListenAttempt);
}
private void OnListenAttempt(EntityUid uid, BlockListeningComponent component, ListenAttemptEvent args)
{
args.Cancel();
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Speech;
[Serializable, NetSerializable]
public enum ListenWireActionKey : byte
{
StatusKey,
TimeoutKey,
}

View File

@@ -0,0 +1,3 @@
wire-listen-pulse-identifier = electricity
wire-listen-pulse-characters = eee EEo
wire-listen-pulse-error-name = ERROR

View File

@@ -64,3 +64,4 @@ wire-name-bomb-proceed = PRCD
wire-name-bomb-boom = BOOM wire-name-bomb-boom = BOOM
wire-name-bomb-bolt = BOLT wire-name-bomb-bolt = BOLT
wire-name-speech = SPKR wire-name-speech = SPKR
wire-name-listen = MIC

View File

@@ -23,6 +23,7 @@
- type: Clickable - type: Clickable
- type: InteractionOutline - type: InteractionOutline
- type: Appearance - type: Appearance
- type: WiresVisuals
- type: ContainerFill - type: ContainerFill
containers: containers:
board: [ IntercomElectronics ] board: [ IntercomElectronics ]
@@ -46,6 +47,10 @@
map: ["enum.RadioDeviceVisualLayers.Speaker"] map: ["enum.RadioDeviceVisualLayers.Speaker"]
shader: unshaded shader: unshaded
visible: false visible: false
- state: panel
map: ["enum.WiresVisualLayers.MaintenancePanel"]
shader: unshaded
visible: false
- type: Transform - type: Transform
noRot: false noRot: false
anchored: true anchored: true
@@ -60,6 +65,8 @@
interfaces: interfaces:
- key: enum.IntercomUiKey.Key - key: enum.IntercomUiKey.Key
type: IntercomBoundUserInterface type: IntercomBoundUserInterface
- key: enum.WiresUiKey.Key
type: WiresBoundUserInterface
- type: Construction - type: Construction
graph: Intercom graph: Intercom
node: intercom node: intercom
@@ -87,6 +94,10 @@
volume: -4 volume: -4
- type: GenericVisualizer - type: GenericVisualizer
visuals: visuals:
enum.WiresVisualLayers.MaintenancePanel:
enum.WiresVisualLayers.MaintenancePanel:
True: { visible: true }
False: { visible: false }
enum.PowerDeviceVisuals.Powered: enum.PowerDeviceVisuals.Powered:
enum.PowerDeviceVisualLayers.Powered: enum.PowerDeviceVisualLayers.Powered:
True: { visible: true } True: { visible: true }

View File

@@ -49,6 +49,13 @@
- !type:AirAlarmPanicWire - !type:AirAlarmPanicWire
- !type:AtmosMonitorDeviceNetWire - !type:AtmosMonitorDeviceNetWire
- type: wireLayout
id: Intercom
wires:
- !type:PowerWireAction
- !type:SpeechWireAction
- !type:ListenWireAction
- type: wireLayout - type: wireLayout
id: FireAlarm id: FireAlarm
wires: wires:

View File

@@ -26,6 +26,10 @@
{ {
"name": "unshaded", "name": "unshaded",
"directions": 4 "directions": 4
},
{
"name": "panel",
"directions": 4
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B