forked from space-syndicate/space-station-14-next
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port TapeRecorder (space-syndicate#129)
- Loading branch information
Showing
40 changed files
with
1,401 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
Content.Client/TapeRecorder/Ui/TapeRecorderBoundUserInterface.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
133 changes: 133 additions & 0 deletions
133
Content.Client/TapeRecorder/Ui/TapeRecorderWindow.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
Content.Shared/TapeRecorder/Components/ActiveTapeRecorderComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.