Voice recorder (#20)

*added voice recorder
This commit is contained in:
Viktor
2024-02-01 15:17:04 +02:00
committed by GitHub
parent 84c89245b6
commit 876bd21bd2
16 changed files with 427 additions and 1 deletions

View File

@@ -0,0 +1,68 @@
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared._White.VoiceRecorder;
/// <summary>
/// This is used for...
/// </summary>
[RegisterComponent]
public sealed partial class VoiceRecorderComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled { get; set; } = true;
[DataField("blacklist")]
public EntityWhitelist Blacklist { get; private set; } = new();
[ViewVariables(VVAccess.ReadWrite)]
[DataField("range")]
public int Range { get; private set; } = 10;
[DataField("listening")]
public bool Listening { get; set; } = false;
/// <summary>
/// The sound that's played when the scanner prints off a report.
/// </summary>
[DataField("soundPrint")]
public SoundSpecifier SoundPrint = new SoundPathSpecifier("/Audio/Machines/short_print_and_rip.ogg");
[DataField("soundEndOfRecording")]
public SoundSpecifier SoundEndOfRecording = new SoundPathSpecifier("/Audio/Machines/id_insert.ogg");
[DataField("soundStartOfRecording")]
public SoundSpecifier SoundStartOfRecording = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/pistol_magin.ogg");
/// <summary>
/// What the machine will print
/// </summary>
[DataField("machineOutput", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string MachineOutput = "PaperOffice";
[DataField("recordings")]
public List<string> Recordings = new();
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maximumEntries")]
public int MaximumEntries = 100;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("customTitle")]
public string CustomTitle = "";
/// <summary>
/// When will the recorder be ready to print again?
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public TimeSpan PrintReadyAt = TimeSpan.Zero;
/// <summary>
/// How often can the recorder print out reports?
/// </summary>
[DataField("printCooldown")]
public TimeSpan PrintCooldown = TimeSpan.FromSeconds(5);
}

View File

@@ -0,0 +1,178 @@
using System.Text;
using Content.Server.Paper;
using Content.Server.Popups;
using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Shared._White.VoiceRecorder;
using Content.Shared.Examine;
using Content.Shared.GameTicking;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Paper;
using Content.Shared.Toggleable;
using Content.Shared.Verbs;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server._White.VoiceRecorder;
/// <summary>
/// This handles the voice recorder all itself.
/// </summary>
public sealed class VoiceRecorderSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VoiceRecorderComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<VoiceRecorderComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<VoiceRecorderComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<VoiceRecorderComponent, ListenEvent>(SaveEntityMessage);
SubscribeLocalEvent<VoiceRecorderComponent, ListenAttemptEvent>(CanListen);
SubscribeLocalEvent<VoiceRecorderComponent, GetVerbsEvent<AlternativeVerb>>(AddVerbs);
}
public void OnInit(EntityUid uid, VoiceRecorderComponent component, ComponentInit args)
{
if (!TryComp<ActiveListenerComponent>(uid, out var listener))
{
RemComp<ActiveListenerComponent>(uid);
component.Listening = false;
return;
}
ToggleListening(uid, component, component.Listening);
}
public void OnExamine(EntityUid uid, VoiceRecorderComponent component, ExaminedEvent args)
{
if (args.IsInDetailsRange)
{
var message = $"{Loc.GetString("voice-recorder-state")} {Loc.GetString( component.Listening ? "voice-recorder-state-on" : "voice-recorder-state-off")}";
args.PushMarkup(message);
}
}
public void OnActivate(EntityUid uid, VoiceRecorderComponent component, ActivateInWorldEvent args)
{
component.Listening = !component.Listening;
ToggleListening(uid, component, component.Listening);
var message = Loc.GetString(component.Listening ? "voice-recorder-on" : "voice-recorder-off");
_popup.PopupEntity(message, args.User, args.User);
args.Handled = true;
}
private void AddVerbs(EntityUid uid, VoiceRecorderComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !component.Enabled || component.Listening || component.Recordings.Count == 0)
return;
AlternativeVerb verb = new();
verb.Text = Loc.GetString("voice-recorder-print");
verb.Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/eject.svg.192dpi.png"));
verb.Act = () => OnPrint(uid, component, component.Recordings.ToArray(), args.User);
args.Verbs.Add(verb);
}
public void ToggleListening(EntityUid uid, VoiceRecorderComponent component, bool listening)
{
component.Listening = listening;
if (listening)
{
component.Recordings.Clear();
EnsureComp<ActiveListenerComponent>(uid).Range = component.Range;
_audioSystem.PlayPvs(component.SoundStartOfRecording, uid,
AudioParams.Default
.WithVariation(0.25f)
.WithVolume(0.3f)
.WithRolloffFactor(2.8f)
.WithMaxDistance(1.5f));
}
else
{
RemComp<ActiveListenerComponent>(uid);
_audioSystem.PlayPvs(component.SoundEndOfRecording, uid,
AudioParams.Default
.WithVariation(0.25f)
.WithVolume(0.3f)
.WithRolloffFactor(2.8f)
.WithMaxDistance(1.5f));
}
if (TryComp<AppearanceComponent>(uid, out var appearance) &&
TryComp<ItemComponent>(uid, out var item))
{
_item.SetHeldPrefix(uid, listening ? "on" : "off", false, item);
_appearance.SetData(uid, ToggleVisuals.Toggled, listening, appearance);
}
}
public void CanListen(EntityUid uid, VoiceRecorderComponent component, ListenAttemptEvent args)
{
if (component.Blacklist.IsValid(args.Source))
args.Cancel();
}
public void SaveEntityMessage(EntityUid uid, VoiceRecorderComponent component, ListenEvent args)
{
if (!component.Listening)
return;
var message = $"{_gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan).ToString("hh\\:mm\\:ss")} {Name(args.Source)}: {args.Message}";
component.Recordings.Add(message);
if (component.Recordings.Count > (component.MaximumEntries - 1))
{
ToggleListening(uid, component, false);
}
}
public void OnPrint(EntityUid uid, VoiceRecorderComponent component, string[] messages, EntityUid user)
{
if (_gameTiming.CurTime < component.PrintReadyAt)
{
_popup.PopupEntity(Loc.GetString("forensic-scanner-printer-not-ready"), uid, user);
return;
}
// TEXT TO PRINT
var text = new StringBuilder();
text.AppendLine(component.CustomTitle == "" ? Loc.GetString("voice-recorder-title") : component.CustomTitle);
text.AppendLine("");
text.AppendLine(Loc.GetString("voice-recorder-start"));
foreach (var message in messages)
{
text.AppendLine(message);
}
text.AppendLine(Loc.GetString("voice-recorder-end"));
var printed = EntityManager.SpawnEntity(component.MachineOutput, Transform(uid).Coordinates);
_paperSystem.SetContent(printed, text.ToString());
var stamp = new StampDisplayInfo();
stamp.StampedName = Loc.GetString("voice-recorder-stamp");
stamp.StampedColor = new Color(47, 47, 56);
_paperSystem.TryStamp(printed, stamp, "paper_stamp-transcript", null);
_handsSystem.PickupOrDrop(user, printed, checkActionBlocker: false);
_audioSystem.PlayPvs(component.SoundPrint, uid,
AudioParams.Default
.WithVariation(0.25f)
.WithVolume(3f)
.WithRolloffFactor(2.8f)
.WithMaxDistance(4.5f));
_metaData.SetEntityName(printed, Loc.GetString("voice-recorder-paper-name"));
_metaData.SetEntityDescription(printed, Loc.GetString("voice-recorder-paper-desc"));
ToggleListening(uid, component, false);
component.PrintReadyAt = _gameTiming.CurTime + component.PrintCooldown;
}
}

View File

@@ -0,0 +1,15 @@
voice-recorder-on = recording now
voice-recorder-off = recording stopped
voice-recorder-print = Print recording
voice-recorder-stamp = TRANSCRIPT
voice-recorder-title = NANOTRASEN BLCK-M VOICE RECORDER TRANSCRIPT
voice-recorder-start = *start of recording*
voice-recorder-end = *end of recording*
voice-recorder-paper-name = recorder transcript
voice-recorder-paper-desc = BLCK-M voice recorder transcript
voice-recorder-state = Voice recorder
voice-recorder-state-on = is recording
voice-recorder-state-off = is off
ent-CrateSecurityVoiceRecorder = voice recorder crate
.desc = Contains 3 voice recorders to ensure that no evidence will be lost. Does not require any access to open.

View File

@@ -0,0 +1,17 @@
voice-recorder-on = запись включена
voice-recorder-off = запись остановлена
voice-recorder-print = Распечатать
voice-recorder-stamp = СТЕНОГРАММА
voice-recorder-title = NANOTRASEN BLCK-M VOICE RECORDER TRANSCRIPT
voice-recorder-start = *начало записи*
voice-recorder-end = *конец записи*
voice-recorder-paper-name = стенограмма
voice-recorder-paper-desc = копия стенограммы записанной на диктофон типа BLCK-M
voice-recorder-state = Диктофон
voice-recorder-state-on = ведёт запись
voice-recorder-state-off = выключен
ent-VoiceRecorder = диктофон
.desc = Диктофон типа BLCK-M. Имеет втроенный принтер для печати стенограмм. Каждая новая запись стирает предыдущую.
ent-CrateSecurityVoiceRecorder = ящик с диктофонами
.desc = Ящик с диктофонами типа BLCK-M. Содержит 3 единицы. Не трубует дополнительных доступов.

View File

@@ -87,3 +87,13 @@
cost: 2000
category: Security
group: market
- type: cargoProduct
id: SecurityVoiceRecorder
icon:
sprite: White/VoiceRecorder/voicerecorder.rsi
state: icon
product: CrateSecurityVoiceRecorder
cost: 1000
category: Security
group: market

View File

@@ -123,3 +123,12 @@
contents:
- id: BoxBodyCamera
amount: 2
- type: entity
id: CrateSecurityVoiceRecorder
parent: CrateGenericSteel
components:
- type: StorageFill
contents:
- id: VoiceRecorder
amount: 4

View File

@@ -0,0 +1,29 @@
- type: entity
parent: BaseItem
id: VoiceRecorder
name: voice recorder
description: BLCK-M type voice recorder. Has built-in printer for printing transcripts. Each new record erases previous one.
components:
- type: Sprite
sprite: White/VoiceRecorder/voicerecorder.rsi
layers:
- state: icon-on
- type: GenericVisualizer
visuals:
enum.ToggleVisuals.Toggled:
enum.ToggleVisuals.Layer:
True: { state: icon-on }
False: { state: icon }
- type: Item
heldPrefix: off
sprite: White/VoiceRecorder/voicerecorder.rsi
- type: Appearance
- type: VoiceRecorder
blacklist:
components:
- SurveillanceCamera
- SurveillanceCameraMonitor
- RadioSpeaker
range: 5
- type: ActiveListener
range: 5

View File

@@ -256,6 +256,9 @@
},
{
"name": "paper_stamp-geraldiy"
}
},
{
"name": "paper_stamp-transcript"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,97 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "by Gargrarien",
"size": {"x": 32, "y": 32},
"states":
[
{
"name": "icon-on",
"directions": 1,
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "icon",
"directions": 1
},
{
"name": "off-inhand-left",
"directions": 4
},
{
"name": "off-inhand-right",
"directions": 4
},
{
"name": "on-inhand-left",
"directions": 4,
"delays": [
[
0.2,
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2,
0.2
]
]
},
{
"name": "on-inhand-right",
"directions": 4,
"delays": [
[
0.2,
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2,
0.2
],
[
0.2,
0.2,
0.2,
0.2
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB