Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tape recorder v3 revival #32234

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fcf7c8e
new tape recorder!
BombasterDS Jul 16, 2024
fd4e5ea
bye bye from detdrobe!
BombasterDS Jul 16, 2024
3474db8
code cleanup
BombasterDS Jul 16, 2024
38a9db2
detective locker and reporter equipment
BombasterDS Jul 18, 2024
f8d0f0f
serverside timer check + code cleanup
BombasterDS Jul 27, 2024
62b92b0
Merge branch 'master' into newtaperecorder
BombasterDS Jul 30, 2024
5121339
Merge branch 'master' into newtaperecorder
BombasterDS Aug 1, 2024
b76fec4
Merge branch 'master' into newtaperecorder
BombasterDS Aug 25, 2024
ffb4abc
text cleanup
BombasterDS Aug 25, 2024
118afb5
Merge branch 'newtaperecorder' of https://github.com/BombasterDS/spac…
BombasterDS Aug 25, 2024
8b9139d
paper
BombasterDS Aug 25, 2024
b40736f
Merge branch 'master' into newtaperecorder
BombasterDS Aug 28, 2024
bb11d26
serverside refactor/reworking
Sep 17, 2024
54b4fc7
clientside rework
Sep 17, 2024
4c575fc
center the buttons
Sep 17, 2024
3b6671c
fix visuals for removing the tape
Sep 17, 2024
34394e3
add verb recording
Sep 17, 2024
9609531
fix
Sep 17, 2024
2dfc777
a
Sep 17, 2024
b087237
a
Sep 17, 2024
b93cc45
tape layer invisible by default
Sep 17, 2024
286e972
fix double playing and not syncing after stopping
Sep 17, 2024
e3dbe51
use TimeOffsetSerializer for cooldownEndTime
Sep 17, 2024
b8a6598
a
Sep 17, 2024
c495996
merge master
Sep 26, 2024
5bb5576
fix
Sep 26, 2024
7c47042
fix funny thing before client gets state
Sep 26, 2024
d553ab1
have buttons disabled until state is received
Sep 26, 2024
5c5ea14
!
Sep 26, 2024
7bfe1b9
back to old verb playbak thing
Sep 26, 2024
8bcff4f
merge master
Oct 18, 2024
00b13ea
pro
Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Content.Client/TapeRecorder/TapeRecorderSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Content.Shared.TapeRecorder;

namespace Content.Client.TapeRecorder;

/// <summary>
/// Required for client side prediction stuff
/// </summary>
public sealed class TapeRecorderSystem : SharedTapeRecorderSystem
{
private TimeSpan _lastTickTime = TimeSpan.Zero;

public override void Update(float frameTime)
{
if (!Timing.IsFirstTimePredicted)
return;

//We need to know the exact time period that has passed since the last update to ensure the tape position is sync'd with the server
//Since the client can skip frames when lagging, we cannot use frameTime
var realTime = (float) (Timing.CurTime - _lastTickTime).TotalSeconds;
_lastTickTime = Timing.CurTime;

base.Update(realTime);
}
}
65 changes: 65 additions & 0 deletions Content.Client/TapeRecorder/Ui/TapeRecorderBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Content.Shared.TapeRecorder.Components;
using Content.Shared.TapeRecorder.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;

namespace Content.Client.TapeRecorder.Ui;

public sealed class TapeRecorderBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[Dependency] private readonly IEntityManager _entMan = default!;

[ViewVariables]
private TapeRecorderWindow? _window;

[ViewVariables]
private TimeSpan _printCooldown;

protected override void Open()
{
base.Open();

_window = new(_entMan, Owner);
_window.OnClose += Close;
_window.OnModeChanged += ChangeMode;
_window.OnPrintTranscript += PrintTranscript;
_window.OpenCentered();
}

private void ChangeMode(TapeRecorderMode mode)
{
SendMessage(new ChangeModeTapeRecorderMessage(mode));
}

private void PrintTranscript()
{
SendMessage(new PrintTapeRecorderMessage());

_window?.UpdatePrint(true);

Timer.Spawn(_printCooldown, () =>
{
_window?.UpdatePrint(false);
});
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);

if (state is not TapeRecorderState cast)
return;

_printCooldown = cast.PrintCooldown;

_window?.UpdateState(cast);
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
_window?.Dispose();
}
}

23 changes: 23 additions & 0 deletions Content.Client/TapeRecorder/Ui/TapeRecorderWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
MinSize="440 220"
SetSize="440 220"
Title="{Loc 'tape-recorder-menu-title'}"
Resizable="False">
<BoxContainer Margin = "10 5" Orientation="Vertical" SeparationOverride="5">
<BoxContainer Orientation="Vertical">
<Label Margin = "5 0" Name="CassetteLabel" Text="{Loc 'tape-recorder-menu-no-cassette-label'}" Align="Left" StyleClasses="StatusFieldTitle" />
<Slider Name="PlaybackSlider" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Name ="Test" Margin = "0 5 0 0" Orientation="Horizontal" VerticalExpand = "True">
<BoxContainer Orientation="Vertical" HorizontalExpand = "True">
<Label Text="{Loc 'tape-recorder-menu-controls-label'}" Align="Center" />
<BoxContainer Name="Buttons" Orientation="Horizontal" VerticalExpand="True" Align="Center"/> <!-- Populated in constructor -->
</BoxContainer>
</BoxContainer>
<BoxContainer Margin = "0 2 0 0" Orientation="Horizontal">
<Button Name="PrintButton" Text="{Loc 'tape-recorder-menu-print-button'}" TextAlign="Center" HorizontalExpand ="True"/>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
134 changes: 134 additions & 0 deletions Content.Client/TapeRecorder/Ui/TapeRecorderWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Shuttles.Components;
using Content.Shared.TapeRecorder.Components;
using Content.Shared.TapeRecorder.Events;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;

namespace Content.Client.TapeRecorder.Ui;

[GenerateTypedNameReferences]
public sealed partial class TapeRecorderWindow : FancyWindow
{
private IEntityManager _entMan;

private EntityUid _owner;
private bool _onCooldown;
private bool _hasCasette;
private TapeRecorderMode _mode = TapeRecorderMode.Stopped;

private RadioOptions<TapeRecorderMode> _options = default!;
private bool _updating;

public Action<TapeRecorderMode>? OnModeChanged;
public Action? OnPrintTranscript;

public TapeRecorderWindow(IEntityManager entMan, EntityUid owner)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

_entMan = entMan;

_owner = owner;

_options = new RadioOptions<TapeRecorderMode>(RadioOptionsLayout.Horizontal);
Buttons.AddChild(_options);
_options.FirstButtonStyle = "OpenRight";
_options.LastButtonStyle = "OpenLeft";
_options.ButtonStyle = "OpenBoth";
foreach (var mode in Enum.GetValues<TapeRecorderMode>())
{
var name = mode.ToString().ToLower();
_options.AddItem(Loc.GetString($"tape-recorder-menu-{name}-button"), mode);
}

_options.OnItemSelected += args =>
{
if (_updating) // don't tell server to change mode to the mode it told us
return;

args.Button.Select(args.Id);
var mode = args.Button.SelectedValue;
OnModeChanged?.Invoke(mode);
};

PrintButton.OnPressed += _ => OnPrintTranscript?.Invoke();

SetEnabled(TapeRecorderMode.Recording, false);
SetEnabled(TapeRecorderMode.Playing, false);
SetEnabled(TapeRecorderMode.Rewinding, false);
}

private void SetSlider(float maxTime, float currentTime)
{
PlaybackSlider.Disabled = true;
PlaybackSlider.MaxValue = maxTime;
PlaybackSlider.Value = currentTime;
}

public void UpdatePrint(bool disabled)
{
PrintButton.Disabled = disabled;
_onCooldown = disabled;
}

public void UpdateState(TapeRecorderState state)
{
if (!_entMan.TryGetComponent<TapeRecorderComponent>(_owner, out var comp))
return;

_mode = comp.Mode; // TODO: update UI on handling state instead of adding UpdateUI to everything
_hasCasette = state.HasCasette;

_updating = true;

CassetteLabel.Text = _hasCasette
? Loc.GetString("tape-recorder-menu-cassette-label", ("cassetteName", state.CassetteName))
: Loc.GetString("tape-recorder-menu-no-cassette-label");

// Select the currently used mode
_options.SelectByValue(_mode);

// When tape is ejected or a button can't be used, disable it
// Server will change to paused once a tape is inactive
var tapeLeft = state.CurrentTime < state.MaxTime;
SetEnabled(TapeRecorderMode.Recording, tapeLeft);
SetEnabled(TapeRecorderMode.Playing, tapeLeft);
SetEnabled(TapeRecorderMode.Rewinding, state.CurrentTime > float.Epsilon);

if (state.HasCasette)
SetSlider(state.MaxTime, state.CurrentTime);

_updating = false;
}

private void SetEnabled(TapeRecorderMode mode, bool condition)
{
_options.SetItemDisabled((int) mode, !(_hasCasette && condition));
}

protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);

if (!_entMan.HasComponent<ActiveTapeRecorderComponent>(_owner))
return;

if (!_entMan.TryGetComponent<TapeRecorderComponent>(_owner, out var comp))
return;

if (_mode != comp.Mode)
{
_mode = comp.Mode;
_options.SelectByValue(_mode);
}

var speed = _mode == TapeRecorderMode.Rewinding
? -comp.RewindSpeed
: 1f;
PlaybackSlider.Value += args.DeltaSeconds * speed;
}
}
132 changes: 132 additions & 0 deletions Content.Server/TapeRecorder/TapeRecorderSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using Content.Server.Chat.Systems;
using Content.Server.Hands.Systems;
using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Shared.Chat;
using Content.Shared.Paper;
using Content.Shared.Speech;
using Content.Shared.TapeRecorder;
using Content.Shared.TapeRecorder.Components;
using Content.Shared.TapeRecorder.Events;
using Robust.Server.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Text;

namespace Content.Server.TapeRecorder;

public sealed class TapeRecorderSystem : SharedTapeRecorderSystem
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly PaperSystem _paper = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<TapeRecorderComponent, ListenEvent>(OnListen);
SubscribeLocalEvent<TapeRecorderComponent, PrintTapeRecorderMessage>(OnPrintMessage);
}

/// <summary>
/// Given a time range, play all messages on a tape within said range, [start, end).
/// Split into this system as shared does not have ChatSystem access
/// </summary>
protected override void ReplayMessagesInSegment(Entity<TapeRecorderComponent> ent, TapeCassetteComponent tape, float segmentStart, float segmentEnd)
{
var voice = EnsureComp<VoiceOverrideComponent>(ent);
var speech = EnsureComp<SpeechComponent>(ent);

foreach (var message in tape.RecordedData)
{
if (message.Timestamp < tape.CurrentPosition || message.Timestamp >= segmentEnd)
continue;

//Change the voice to match the speaker
voice.NameOverride = message.Name ?? ent.Comp.DefaultName;
// TODO: mimic the exact string chosen when the message was recorded
var verb = message.Verb ?? SharedChatSystem.DefaultSpeechVerb;
speech.SpeechVerb = _proto.Index<SpeechVerbPrototype>(verb);
//Play the message
_chat.TrySendInGameICMessage(ent, message.Message, InGameICChatType.Speak, false);
}
}

/// <summary>
/// Whenever someone speaks within listening range, record it to tape
/// </summary>
private void OnListen(Entity<TapeRecorderComponent> ent, ref ListenEvent args)
{
// mode should never be set when it isn't active but whatever
if (ent.Comp.Mode != TapeRecorderMode.Recording || !HasComp<ActiveTapeRecorderComponent>(ent))
return;

// No feedback loops
if (args.Source == ent.Owner)
return;

if (!TryGetTapeCassette(ent, out var cassette))
return;

// TODO: Handle "Someone" when whispering from far away, needs chat refactor

//Handle someone using a voice changer
var nameEv = new TransformSpeakerNameEvent(args.Source, Name(args.Source));
RaiseLocalEvent(args.Source, nameEv);

//Add a new entry to the tape
var verb = _chat.GetSpeechVerb(args.Source, args.Message);
var name = nameEv.VoiceName;
cassette.Comp.Buffer.Add(new TapeCassetteRecordedMessage(cassette.Comp.CurrentPosition, name, verb, args.Message));
}

private void OnPrintMessage(Entity<TapeRecorderComponent> ent, ref PrintTapeRecorderMessage args)
{
var (uid, comp) = ent;

if (comp.CooldownEndTime > Timing.CurTime)
return;

if (!TryGetTapeCassette(ent, out var cassette))
return;

var text = new StringBuilder();
var paper = Spawn(comp.PaperPrototype, Transform(ent).Coordinates);

// Sorting list by time for overwrite order
// TODO: why is this needed? why wouldn't it be stored in order
var data = cassette.Comp.RecordedData;
data.Sort((x,y) => x.Timestamp.CompareTo(y.Timestamp));

// Looking if player's entity exists to give paper in its hand
var player = args.Actor;
if (Exists(player))
_hands.PickupOrDrop(player, paper, checkActionBlocker: false);

if (!TryComp<PaperComponent>(paper, out var paperComp))
return;

Audio.PlayPvs(comp.PrintSound, ent);

text.AppendLine(Loc.GetString("tape-recorder-print-start-text"));
text.AppendLine();
foreach (var message in cassette.Comp.RecordedData)
{
var name = message.Name ?? ent.Comp.DefaultName;
var time = TimeSpan.FromSeconds((double) message.Timestamp);

text.AppendLine(Loc.GetString("tape-recorder-print-message-text",
("time", time.ToString(@"hh\:mm\:ss")),
("source", name),
("message", message.Message)));
}
text.AppendLine();
text.Append(Loc.GetString("tape-recorder-print-end-text"));

_paper.SetContent((paper, paperComp), text.ToString());

comp.CooldownEndTime = Timing.CurTime + comp.PrintCooldown;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;

namespace Content.Shared.TapeRecorder.Components;

/// <summary>
/// Added to tape records that are updating, winding or rewinding the tape.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class ActiveTapeRecorderComponent : Component;
Loading
Loading