[feat]jukebox (#9)

This commit is contained in:
rhailrake
2023-05-04 13:43:03 +06:00
committed by Aviu00
parent 1a729666f4
commit 68ffe42dbb
35 changed files with 1626 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Client.White.JoinQueue;
using Content.Client.White.Jukebox;
using Content.Client.White.Sponsors;
using Content.Shared.Ame;
using Content.Client.White.Stalin;
@@ -78,6 +79,7 @@ namespace Content.Client.Entry
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
[Dependency] private readonly JoinQueueManager _queueManager = default!;
[Dependency] private readonly StalinManager _stalinManager = default!;
[Dependency] private readonly ClientJukeboxSongsSyncManager _jukeboxSyncManager = default!;
//WD-EDIT
public override void Init()
@@ -183,6 +185,7 @@ namespace Content.Client.Entry
//WD-EDIT
_sponsorsManager.Initialize();
_queueManager.Initialize();
_jukeboxSyncManager.Initialize();
//WD-EDIT
_baseClient.RunLevelChanged += (_, args) =>

View File

@@ -18,6 +18,7 @@ using Content.Shared.Administration.Logs;
using Content.Client.Guidebook;
using Content.Client.Replay;
using Content.Client.White.JoinQueue;
using Content.Client.White.Jukebox;
using Content.Client.White.Sponsors;
using Content.Client.White.Stalin;
using Content.Shared.Administration.Managers;
@@ -53,6 +54,7 @@ namespace Content.Client.IoC
IoCManager.Register<JoinQueueManager>();
IoCManager.Register<SponsorsManager>();
IoCManager.Register<StalinManager>();
IoCManager.Register<ClientJukeboxSongsSyncManager>();
//WD-EDIT
}
}

View File

@@ -74,6 +74,19 @@
<Label Name="TtsVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-jukebox-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="JukeboxVolumeSlider"
MinValue="25"
MaxValue="200"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="JukeboxVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-lobby-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />

View File

@@ -39,6 +39,7 @@ namespace Content.Client.Options.UI.Tabs
LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
TtsVolumeSlider.OnValueChanged += OnTtsVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
JukeboxVolumeSlider.OnValueChanged += OnJukeboxVolumeSliderChanged;
LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
@@ -63,6 +64,7 @@ namespace Content.Client.Options.UI.Tabs
//WD-EDIT
TtsVolumeSlider.OnValueChanged -= OnTtsVolumeSliderChanged;
JukeboxVolumeSlider.OnValueChanged -= OnJukeboxVolumeSliderChanged;
//WD-EDIT
base.Dispose(disposing);
@@ -76,6 +78,13 @@ namespace Content.Client.Options.UI.Tabs
}
//TTS-End
//JUKEBOX
private void OnJukeboxVolumeSliderChanged(Range obj)
{
UpdateChanges();
}
//JUKEBOX
private void OnLobbyVolumeSliderChanged(Range obj)
{
UpdateChanges();
@@ -150,6 +159,7 @@ namespace Content.Client.Options.UI.Tabs
//WD-EDIT
_cfg.SetCVar(WhiteCVars.TtsVolume, LV100ToDB(TtsVolumeSlider.Value));
_cfg.SetCVar(WhiteCVars.JukeboxVolume, LV100ToDB(JukeboxVolumeSlider.Value));
//WD-EDIT
_cfg.SaveToFile();
@@ -179,6 +189,7 @@ namespace Content.Client.Options.UI.Tabs
//WD-EDIT
TtsVolumeSlider.Value = DBToLV100(_cfg.GetCVar(WhiteCVars.TtsVolume));
JukeboxVolumeSlider.Value = DBToLV100(_cfg.GetCVar(WhiteCVars.JukeboxVolume));
//WD-EDIT
@@ -222,6 +233,8 @@ namespace Content.Client.Options.UI.Tabs
//WD-EDIT
var isTtsVolumeSame =
Math.Abs(TtsVolumeSlider.Value - DBToLV100(_cfg.GetCVar(WhiteCVars.TtsVolume))) < 0.01f;
var isJukeboxVolumeSame =
Math.Abs(JukeboxVolumeSlider.Value - DBToLV100(_cfg.GetCVar(WhiteCVars.JukeboxVolume))) < 0.01f;
//WD-EDIT
var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
@@ -230,6 +243,7 @@ namespace Content.Client.Options.UI.Tabs
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
&& isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
isEverythingSame = isEverythingSame && isTtsVolumeSame; //WD-EDIT
isEverythingSame = isEverythingSame && isTtsVolumeSame && isJukeboxVolumeSame; //WD-EDIT
ApplyButton.Disabled = isEverythingSame;
ResetButton.Disabled = isEverythingSame;
MasterVolumeLabel.Text =
@@ -249,6 +263,8 @@ namespace Content.Client.Options.UI.Tabs
//WD-EDIT
TtsVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", TtsVolumeSlider.Value / 100));
JukeboxVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", JukeboxVolumeSlider.Value / 100));
//WD-EDIT
}
}

View File

@@ -0,0 +1,10 @@
namespace Content.Client.White;
//Система со смешным названием, чье предназначение заключается лишь в одном - отправке нетворк ивентов.
public sealed class CheZaHuetaSystem : EntitySystem
{
public void SendNetMessage(EntityEventArgs message)
{
RaiseNetworkEvent(message);
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.White.Jukebox;
namespace Content.Client.White.Jukebox;
public sealed class ClientJukeboxSongsSyncManager : JukeboxSongsSyncManager
{
public override void OnSongUploaded(JukeboxSongUploadNetMessage message)
{
ContentRoot.AddOrUpdateFile(message.RelativePath!, message.Data);
}
}

View File

@@ -0,0 +1,70 @@
using Content.Shared.Popups;
using Content.Shared.White.Jukebox;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.White.Jukebox;
public sealed class JukeboxBUI : BoundUserInterface
{
[Dependency] private readonly EntityManager _entityManager = default!;
private readonly SharedPopupSystem _sharedPopupSystem = default!;
private JukeboxMenu? _window;
public JukeboxBUI(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
_sharedPopupSystem = _entityManager.System<SharedPopupSystem>();
var uid = owner.Owner;
if (!_entityManager.TryGetComponent<JukeboxComponent>(uid, out var jukeboxComponent))
{
_sharedPopupSystem.PopupEntity($"Тут нет JukeboxComponent, звоните кодерам", uid);
return;
}
_window = new JukeboxMenu(jukeboxComponent);
_window.RepeatButton.OnToggled += OnRepeatButtonToggled;
_window.StopButton.OnPressed += OnStopButtonPressed;
_window.EjectButton.OnPressed += OnEjectButtonPressed;
}
private void OnEjectButtonPressed(BaseButton.ButtonEventArgs obj)
{
SendMessage(new JukeboxEjectRequest());
}
private void OnStopButtonPressed(BaseButton.ButtonEventArgs obj)
{
SendMessage(new JukeboxStopRequest());
}
private void OnRepeatButtonToggled(BaseButton.ButtonToggledEventArgs obj)
{
SendMessage(new JukeboxRepeatToggled(obj.Pressed));
}
protected override void Open()
{
base.Open();
if (_window == null)
{
Close();
return;
}
_window.OpenCentered();
_window.OnClose += Close;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_window?.Dispose();
}
}

View File

@@ -0,0 +1,29 @@
<DefaultWindow xmlns="https://spacestation14.io" MinSize="600 700" Name="BoomBoxWindow" Title="Альфа-Шкварки">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<Label Name="CurrenSongLabel" HorizontalAlignment="Center" HorizontalExpand="True"></Label>
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Button Access="Internal" Name="StopButton" Text="Остановить" HorizontalAlignment="Right"></Button>
<Button Access="Internal" Name="RepeatButton" Text="Повторять" HorizontalAlignment="Right" ToggleMode="True"></Button>
<Button Access="Internal" Name="EjectButton" Text="Извлечь"></Button>
</BoxContainer>
<TabContainer VerticalExpand="True" HorizontalExpand="True">
<!-- <ScrollContainer Name="DefaultSongsTab"
HScrollEnabled="False">
<BoxContainer Name="DefaultSongsContainer"
Orientation="Vertical"
Margin="2 2 0 0"
HorizontalExpand="True">
</BoxContainer>
</ScrollContainer> -->
<ScrollContainer Name="TapeSongsTab"
HScrollEnabled="False">
<BoxContainer Name="TapeSongsContainer"
Orientation="Vertical"
Margin="2 2 0 0"
HorizontalExpand="True">
</BoxContainer>
</ScrollContainer>
</TabContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,100 @@
using System.Linq;
using Content.Shared.White.Jukebox;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using TerraFX.Interop.Windows;
namespace Content.Client.White.Jukebox;
[GenerateTypedNameReferences]
public sealed partial class JukeboxMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly JukeboxSystem _jukeboxSystem = default!;
private readonly JukeboxComponent _component;
private List<JukeboxSongEntry> _defaultSongsEntries = new();
private List<JukeboxSongEntry> _tapeSongsEntries = new();
public JukeboxMenu(JukeboxComponent component)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_jukeboxSystem = _entityManager.System<JukeboxSystem>();
_component = component;
/*TabContainer.SetTabTitle(DefaultSongsTab, "Прямо с завода");*/
TabContainer.SetTabTitle(TapeSongsTab, "Песенки с кассеты");
/*PopulateDefaultSongsContainer(DefaultSongsContainer);*/
PopulateTapeSongsContainer(TapeSongsContainer);
}
protected override void FrameUpdate(FrameEventArgs args)
{
CurrenSongLabel.Text = _component.PlayingSongData == null ? "..." : _component.PlayingSongData.SongName!;
RepeatButton.Pressed = _component.Repeating;
base.FrameUpdate(args);
}
private void PopulateDefaultSongsContainer(BoxContainer defaultSongsContainer)
{
var tapes = _component.DefaultSongsContainer.ContainedEntities.ToList();
foreach (var tapeUid in tapes)
{
if (_entityManager.TryGetComponent<TapeComponent>(tapeUid, out var tape))
{
foreach (var song in tape.Songs)
{
var songEntry = new JukeboxSongEntry(song, RequestSongPlay);
_defaultSongsEntries.Add(songEntry);
}
}
}
_defaultSongsEntries.Sort((x,y ) => string.Compare(x.Name!, y.Name!, StringComparison.Ordinal));
foreach (var defaultSongsEntry in _defaultSongsEntries)
{
defaultSongsContainer.AddChild(defaultSongsEntry);
}
}
private void PopulateTapeSongsContainer(BoxContainer tapeSongsContainer)
{
var tapes = _component.TapeContainer.ContainedEntities.ToList();
foreach (var tapeUid in tapes)
{
if (_entityManager.TryGetComponent<TapeComponent>(tapeUid, out var tape))
{
foreach (var song in tape.Songs)
{
var songEntry = new JukeboxSongEntry(song, RequestSongPlay);
_tapeSongsEntries.Add(songEntry);
}
}
}
foreach (var tapeSongsEntry in _tapeSongsEntries)
{
tapeSongsContainer.AddChild(tapeSongsEntry);
}
}
private void RequestSongPlay(JukeboxSong song)
{
_jukeboxSystem.RequestSongToPlay(_component, song);
}
}

View File

@@ -0,0 +1,13 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer MaxHeight="32" HorizontalExpand="True" Margin="2 2 2 2">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#7B7E7E"></gfx:StyleBoxFlat>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" >
<Label Name="SongNameLabel" HorizontalAlignment="Left" HorizontalExpand="True"></Label>
<Button Name="PlaySongButton" HorizontalAlignment="Right" Text="Запустить"></Button>
</BoxContainer>
</PanelContainer>
</Control>

View File

@@ -0,0 +1,25 @@
using Content.Shared.White.Jukebox;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.White.Jukebox;
[GenerateTypedNameReferences]
public sealed partial class JukeboxSongEntry : Control
{
public JukeboxSong? Song { get; private set; }
private JukeboxSongEntry()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public JukeboxSongEntry(JukeboxSong song, Action<JukeboxSong> callback) : this()
{
Song = song;
SongNameLabel.Text = Song.SongName;
PlaySongButton.OnPressed += _ => callback.Invoke(Song);
}
}

View File

@@ -0,0 +1,277 @@
using System.Resources;
using Content.Shared.GameTicking;
using Content.Shared.Interaction.Events;
using Content.Shared.White;
using Content.Shared.White.Jukebox;
using Robust.Client.Audio;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Client.White.Jukebox;
public sealed class JukeboxSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly Dictionary<JukeboxComponent, JukeboxAudio> _playingJukeboxes = new();
private float _maxAudioRange;
private float _jukeboxVolume;
public override void Initialize()
{
base.Initialize();
_cfg.OnValueChanged(WhiteCVars.MaxJukeboxSoundRange, range => _maxAudioRange = range, true);
_cfg.OnValueChanged(WhiteCVars.JukeboxVolume, volume => JukeboxVolumeChanged(volume), true);
SubscribeLocalEvent<JukeboxComponent, ComponentHandleState>(OnStateChanged);
SubscribeLocalEvent<JukeboxComponent, ComponentRemove>(OnComponentRemoved);
SubscribeNetworkEvent<RoundRestartCleanupEvent>(OnRoundRestart);
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
SubscribeNetworkEvent<JukeboxStopPlaying>(OnStopPlaying);
}
private void JukeboxVolumeChanged(float volume)
{
_jukeboxVolume = volume;
CleanUp();
}
private void JoinLobby(TickerJoinLobbyEvent ev)
{
CleanUp();
}
private void OnRoundRestart(RoundRestartCleanupEvent ev)
{
CleanUp();
}
private void OnComponentRemoved(EntityUid uid, JukeboxComponent component, ComponentRemove args)
{
if (!_playingJukeboxes.TryGetValue(component, out var playingData)) return;
playingData.PlayingStream.Stop();
_playingJukeboxes.Remove(component);
}
private void OnStopPlaying(JukeboxStopPlaying ev)
{
if (!ev.JukeboxUid.HasValue) return;
if(!TryComp<JukeboxComponent>(ev.JukeboxUid, out var jukeboxComponent)) return;
if(!_playingJukeboxes.TryGetValue(jukeboxComponent, out var jukeboxAudio)) return;
jukeboxAudio.PlayingStream.Stop();
_playingJukeboxes.Remove(jukeboxComponent);
}
public void RequestSongToPlay(JukeboxComponent component, JukeboxSong jukeboxSong)
{
if (!_resource.TryGetResource<AudioResource>((ResPath) jukeboxSong.SongPath!, out var songResource))
{
return;
}
RaiseNetworkEvent(new JukeboxRequestSongPlay()
{
Jukebox = component.Owner,
SongName = jukeboxSong.SongName,
SongPath = jukeboxSong.SongPath,
SongDuration = (float)songResource.AudioStream.Length.TotalSeconds
});
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var localPlayerEntity = _playerManager.LocalPlayer!.ControlledEntity;
if(!localPlayerEntity.HasValue) return;
ProcessJukeboxes();
}
private void OnStateChanged(EntityUid uid, JukeboxComponent component, ref ComponentHandleState args)
{
if (args.Current is JukeboxComponentState state)
{
component.Repeating = state.Playing;
component.Volume = state.Volume;
component.PlayingSongData = state.SongData;
}
}
private void ProcessJukeboxes()
{
var jukeboxes = EntityQuery<JukeboxComponent, TransformComponent>();
var playerXform = Comp<TransformComponent>(_playerManager.LocalPlayer!.ControlledEntity!.Value);
foreach (var (jukeboxComponent, jukeboxXform) in jukeboxes)
{
if(jukeboxXform.MapID != playerXform.MapID) continue;
if ((jukeboxXform.MapPosition.Position - playerXform.MapPosition.Position).Length > _maxAudioRange) continue;
if (_playingJukeboxes.TryGetValue(jukeboxComponent, out var jukeboxAudio))
{
if (jukeboxAudio.PlayingStream.Done)
{
HandleDoneStream(jukeboxAudio, jukeboxComponent);
return;
}
if (jukeboxAudio.SongData.SongPath != jukeboxComponent.PlayingSongData?.SongPath)
{
HandleSongChanged(jukeboxAudio, jukeboxComponent);
return;
}
}
else
{
if (jukeboxComponent.PlayingSongData == null)
{
SetBarsLayerVisible(jukeboxComponent, false);
continue;
}
var stream = TryCreateStream(jukeboxComponent);
if (stream == null)
{
return;
}
_playingJukeboxes.Add(jukeboxComponent, stream);
SetBarsLayerVisible(jukeboxComponent, true);
}
}
}
private void HandleSongChanged(JukeboxAudio jukeboxAudio, JukeboxComponent jukeboxComponent)
{
jukeboxAudio.PlayingStream.Stop();
if (jukeboxComponent.PlayingSongData != null && jukeboxComponent.PlayingSongData.SongPath == jukeboxAudio.SongData.SongPath)
{
var newStream = TryCreateStream(jukeboxComponent);
if(newStream == null) return;
_playingJukeboxes[jukeboxComponent] = newStream;
SetBarsLayerVisible(jukeboxComponent, true);
}
else
{
_playingJukeboxes.Remove(jukeboxComponent);
SetBarsLayerVisible(jukeboxComponent, false);
}
}
private void HandleDoneStream(JukeboxAudio jukeboxAudio, JukeboxComponent jukeboxComponent)
{
if (!jukeboxComponent.Repeating)
{
jukeboxAudio.PlayingStream.Stop();
_playingJukeboxes.Remove(jukeboxComponent);
SetBarsLayerVisible(jukeboxComponent, false);
return;
}
if(jukeboxComponent.PlayingSongData == null) return;
var newStream = TryCreateStream(jukeboxComponent);
if (newStream == null)
{
_playingJukeboxes.Remove(jukeboxComponent);
SetBarsLayerVisible(jukeboxComponent, false);
}
else
{
_playingJukeboxes[jukeboxComponent] = newStream;
SetBarsLayerVisible(jukeboxComponent, true);
}
}
private JukeboxAudio? TryCreateStream(JukeboxComponent jukeboxComponent)
{
if (jukeboxComponent.PlayingSongData == null) return null;
var resourcePath = jukeboxComponent.PlayingSongData.SongPath!;
var localSession = _playerManager.LocalPlayer!.Session;
if(!_resource.TryGetResource<AudioResource>((ResPath) resourcePath, out var audio))
return null!;
if (audio!.AudioStream.Length.TotalSeconds < jukeboxComponent.PlayingSongData!.PlaybackPosition)
{
return null!;
}
var audioParams = new AudioParams
{
PlayOffsetSeconds = jukeboxComponent.PlayingSongData.PlaybackPosition,
Volume = _jukeboxVolume,
MaxDistance = _maxAudioRange
};
AudioSystem.PlayingStream? playingStream = null!;
playingStream = _audioSystem.PlayEntity(resourcePath.ToString()!, localSession, jukeboxComponent.Owner, audioParams) as AudioSystem.PlayingStream;
if (playingStream == null)
return null!;
return new JukeboxAudio(playingStream, audio!, jukeboxComponent.PlayingSongData);
}
private class JukeboxAudio
{
public PlayingSongData SongData { get; }
public AudioSystem.PlayingStream PlayingStream { get; }
public AudioResource AudioStream { get; }
public JukeboxAudio(AudioSystem.PlayingStream playingStream, AudioResource audioStream, PlayingSongData songData)
{
PlayingStream = playingStream;
AudioStream = audioStream;
SongData = songData;
}
}
private void SetBarsLayerVisible(JukeboxComponent jukeboxComponent, bool visible)
{
var spriteComponent = Comp<SpriteComponent>(jukeboxComponent.Owner);
spriteComponent.LayerMapTryGet("bars", out var layer);
spriteComponent.LayerSetVisible(layer, visible);
}
private void CleanUp()
{
foreach (var playingJukebox in _playingJukeboxes.Values)
{
playingJukebox.PlayingStream.Stop();
}
_playingJukeboxes.Clear();
}
}

View File

@@ -0,0 +1,51 @@
using Content.Shared.Popups;
using Content.Shared.White.Jukebox;
using Robust.Client.GameObjects;
namespace Content.Client.White.Jukebox;
public sealed class TapeCreatorBUI : BoundUserInterface
{
[Dependency] private readonly EntityManager _entityManager = default!;
private readonly SharedPopupSystem _sharedPopupSystem = default!;
private TapeCreatorMenu? _window;
public TapeCreatorBUI(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
_sharedPopupSystem = _entityManager.System<SharedPopupSystem>();
var uid = owner.Owner;
if (!_entityManager.TryGetComponent<TapeCreatorComponent>(uid, out var tapeCreatorComponent))
{
_sharedPopupSystem.PopupEntity($"Тут нет TapeCreatorComponent, звоните кодерам", uid);
return;
}
_window = new TapeCreatorMenu(tapeCreatorComponent);
}
protected override void Open()
{
base.Open();
if (_window == null)
{
Close();
return;
}
_window.OpenCentered();
_window.OnClose += Close;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_window?.Dispose();
}
}

View File

@@ -0,0 +1,16 @@
<DefaultWindow xmlns="https://spacestation14.io" MinSize="200 300" Title="Мысль">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" SetHeight="30">
<Label Text="Осталось циклов записи: "></Label>
<Label Name="CoinsLabel" Text="0"></Label>
</BoxContainer>
<BoxContainer Orientation="Vertical" SeparationOverride="5">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" SetHeight="30">
<Label Text="Название"></Label>
<LineEdit Name="SongNameField" MinWidth="200"></LineEdit>
<Button Name="LoadSongButton" Access="Internal" Text="Загрузить песню"></Button>
</BoxContainer>
<Button Name="UploadSong" Access="Internal" Text="Записать мозговую активность" MinHeight="50" HorizontalExpand="True"></Button>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,125 @@
using System.Text.RegularExpressions;
using Content.Shared.CCVar;
using Content.Shared.Popups;
using Content.Shared.White;
using Content.Shared.White.Jukebox;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.White.Jukebox;
[GenerateTypedNameReferences]
public sealed partial class TapeCreatorMenu : DefaultWindow
{
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IFileDialogManager _fileDialogManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly CheZaHuetaSystem _huetaSystem = default!;
private readonly SharedPopupSystem _popupSystem = default!;
private bool _fileDialogOpened;
private double _maxFileSize;
private double _currentFileSize;
private readonly List<byte> _songBytes = new();
private TapeCreatorComponent _component;
private const double BytesToMegabytes = 0.000001d;
public TapeCreatorMenu(TapeCreatorComponent component)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_huetaSystem = _entityManager.System<CheZaHuetaSystem>();
_popupSystem = _entityManager.System<SharedPopupSystem>();
_cfg.OnValueChanged(WhiteCVars.MaxJukeboxSongSizeInMB, x => _maxFileSize = x, true);
_component = component;
LoadSongButton.OnPressed += TryLoadSong;
UploadSong.OnPressed += OnUploadButtonPressed;
}
private void OnUploadButtonPressed(BaseButton.ButtonEventArgs obj)
{
if(!CanUploadSong()) return;
var input = SongNameField.Text;
string pattern = @"[^a-zA-Zа-яА-Я ]+";
string replacement = "";
var songName = Regex.Replace(input, pattern, replacement);
songName = Regex.Replace(songName, @"\s+", " ");
var songBytes = _songBytes;
var msg = new JukeboxSongUploadRequest()
{
SongName = songName,
SongBytes = songBytes,
TapeCreatorUid = _component.Owner
};
_huetaSystem.SendNetMessage(msg);
_currentFileSize = 0;
_songBytes.Clear();
SongNameField.Clear();
_popupSystem.PopupEntity("Внимание. Начинается запись мозговой активности.", _component.Owner);
}
private async void TryLoadSong(BaseButton.ButtonEventArgs obj)
{
var fileFilter = new FileDialogFilters(new FileDialogFilters.Group("ogg"));
var file = await _fileDialogManager.OpenFile(fileFilter);
if (Disposed) return;
if(file == null) return;
_currentFileSize = file.Length * BytesToMegabytes;
if (_currentFileSize > _maxFileSize)
{
_popupSystem.PopupEntity($"Лимит активности мозговых волн превышен на {_currentFileSize - _maxFileSize} мегахрюков", _component.Owner);
return;
}
//TODO: Песня слишком длинная пиздец
_songBytes.AddRange(file.CopyToArray());
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
CoinsLabel.Text = _component.CoinBalance.ToString();
if (CanUploadSong())
{
UploadSong.Disabled = false;
}
else
{
UploadSong.Disabled = true;
}
}
private bool CanUploadSong()
{
return SongNameField.Text.Length > 0 && _songBytes.Count > 0 && _component.CoinBalance > 0 && _component.InsertedTape.HasValue && !_component.Recording;
}
}

View File

@@ -0,0 +1,41 @@
 using Content.Shared.White.Jukebox;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
namespace Content.Client.White.Jukebox;
public sealed class TapeCreatorSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TapeCreatorComponent, ComponentHandleState>(OnStateChanged);
SubscribeLocalEvent<TapeComponent, ComponentHandleState>(OnTapeStateChanged);
}
private void OnTapeStateChanged(EntityUid uid, TapeComponent component, ref ComponentHandleState args)
{
if (args.Current is not TapeComponentState state) return;
component.Songs = state.Songs;
}
private void OnStateChanged(EntityUid uid, TapeCreatorComponent component, ref ComponentHandleState args)
{
if (args.Current is not TapeCreatorComponentState state) return;
component.Recording = state.Recording;
component.CoinBalance = state.CoinBalance;
component.InsertedTape = state.InsertedTape;
SetTapeLayerVisible(component, state.InsertedTape.HasValue);
}
private void SetTapeLayerVisible(TapeCreatorComponent component, bool visible)
{
var spriteComponent = Comp<SpriteComponent>(component.Owner);
spriteComponent.LayerMapTryGet("tape", out var layer);
spriteComponent.LayerSetVisible(layer, visible);
}
}

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Server.UtkaIntegration;
using Content.Server.White.JoinQueue;
using Content.Server.White.Jukebox;
using Content.Server.White.Sponsors;
using Content.Server.White.Stalin;
using Content.Server.White.TTS;
@@ -113,6 +114,7 @@ namespace Content.Server.Entry
IoCManager.Resolve<JoinQueueManager>().Initialize();
IoCManager.Resolve<TTSManager>().Initialize();
IoCManager.Resolve<StalinManager>().Initialize();
IoCManager.Resolve<ServerJukeboxSongsSyncManager>().Initialize();
//WD-EDIT
_voteManager.Initialize();

View File

@@ -21,6 +21,7 @@ using Content.Server.Voting.Managers;
using Content.Server.Worldgen.Tools;
using Content.Server.UtkaIntegration;
using Content.Server.White.JoinQueue;
using Content.Server.White.Jukebox;
using Content.Server.White.Sponsors;
using Content.Server.White.Stalin;
using Content.Server.White.TTS;
@@ -71,6 +72,7 @@ namespace Content.Server.IoC
IoCManager.Register<UtkaTCPWrapper>();
IoCManager.Register<TTSManager>();
IoCManager.Register<StalinManager>();
IoCManager.Register<ServerJukeboxSongsSyncManager>();
// WD-EDIT
}
}

View File

@@ -0,0 +1,207 @@
using System.Linq;
using Content.Shared.GameTicking;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
using Content.Shared.White.Jukebox;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Shared.Containers;
using Robust.Shared.ContentPack;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.White.Jukebox;
public sealed class JukeboxSystem : EntitySystem
{
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly PVSOverrideSystem _pvsOverrideSystem = default!;
private readonly List<JukeboxComponent> _playingJukeboxes = new();
private float _updateTimerDefaultTime = 1f;
private float _updateTimer;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<JukeboxRequestSongPlay>(OnSongRequestPlay);
SubscribeLocalEvent<JukeboxComponent, InteractUsingEvent>(OnInteract);
SubscribeLocalEvent<JukeboxComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<JukeboxComponent, JukeboxStopRequest>(OnRequestStop);
SubscribeLocalEvent<JukeboxComponent, JukeboxRepeatToggled>(OnRepeatToggled);
SubscribeLocalEvent<JukeboxComponent, JukeboxEjectRequest>(OnEjectRequest);
SubscribeLocalEvent<JukeboxComponent, GetVerbsEvent<Verb>>(OnGetVerb);
SubscribeLocalEvent<JukeboxComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
}
private void OnInit(EntityUid uid, JukeboxComponent component, ComponentInit args)
{
_pvsOverrideSystem.AddGlobalOverride(uid);
}
private void OnRoundRestart(RoundRestartCleanupEvent ev)
{
_playingJukeboxes.Clear();
}
private void OnEjectRequest(EntityUid uid, JukeboxComponent component, JukeboxEjectRequest args)
{
if(component.PlayingSongData != null) return;
var containedEntities = component.TapeContainer.ContainedEntities;
if (containedEntities.Count > 0)
{
_containerSystem.EmptyContainer(component.TapeContainer, true).ToList();
}
}
private void OnGetVerb(EntityUid uid, JukeboxComponent jukeboxComponent, GetVerbsEvent<Verb> ev)
{
if (ev.Hands == null) return;
if (jukeboxComponent.PlayingSongData != null) return;
if (jukeboxComponent.TapeContainer.ContainedEntities.Count == 0) return;
var removeTapeVerb = new Verb()
{
Text = "Вытащить касету",
Priority = 10000,
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/remove_tape.png")),
Act = () =>
{
var tapes = jukeboxComponent.TapeContainer.ContainedEntities.ToList();
_containerSystem.EmptyContainer(jukeboxComponent.TapeContainer, true);
foreach (var tape in tapes)
{
_handsSystem.PickupOrDrop(ev.User, tape);
}
}
};
ev.Verbs.Add(removeTapeVerb);
}
private void OnRepeatToggled(EntityUid uid, JukeboxComponent component, JukeboxRepeatToggled args)
{
component.Repeating = args.NewState;
Dirty(component);
}
private void OnRequestStop(EntityUid uid, JukeboxComponent component, JukeboxStopRequest args)
{
component.PlayingSongData = null;
Dirty(component);
}
private void OnInteract(EntityUid uid, JukeboxComponent component, InteractUsingEvent args)
{
if(component.PlayingSongData != null) return;
if (TryComp<TapeComponent>(args.Used, out var tape))
{
var containedEntities = component.TapeContainer.ContainedEntities;
if (containedEntities.Count >= 1)
{
var removedTapes = _containerSystem.EmptyContainer(component.TapeContainer, true).ToList();
component.TapeContainer.Insert(args.Used);
foreach (var tapeUid in removedTapes)
{
_handsSystem.PickupOrDrop(args.User, tapeUid);
}
}
else
{
component.TapeContainer.Insert(args.Used);
}
}
}
private void OnSongRequestPlay(JukeboxRequestSongPlay msg, EntitySessionEventArgs args)
{
var jukebox = Comp<JukeboxComponent>(msg.Jukebox!.Value);
jukebox.Repeating = true;
var songData = new PlayingSongData()
{
SongName = msg.SongName,
SongPath = msg.SongPath,
ActualSongLengthSeconds = msg.SongDuration,
PlaybackPosition = 0f
};
jukebox.PlayingSongData = songData;
_playingJukeboxes.Add(jukebox);
Dirty(jukebox);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (_updateTimer <= _updateTimerDefaultTime)
{
_updateTimer += frameTime;
return;
}
ProcessPlayingJukeboxes(frameTime);
}
private void ProcessPlayingJukeboxes(float frameTime)
{
for (int i = _playingJukeboxes.Count - 1; i >= 0; i--)
{
var playingJukeboxData = _playingJukeboxes[i];
if (playingJukeboxData.PlayingSongData == null)
{
_playingJukeboxes.RemoveAt(i);
continue;
}
playingJukeboxData.PlayingSongData.PlaybackPosition += _updateTimer;
if (playingJukeboxData.PlayingSongData.PlaybackPosition >= playingJukeboxData.PlayingSongData.ActualSongLengthSeconds)
{
if (playingJukeboxData.Repeating)
{
playingJukeboxData.PlayingSongData.PlaybackPosition = 0;
}
else
{
RaiseNetworkEvent(new JukeboxStopPlaying());
_playingJukeboxes.RemoveAt(i);
}
}
Dirty(playingJukeboxData);
}
_updateTimer = 0;
}
private void OnGetState(EntityUid uid, JukeboxComponent component, ref ComponentGetState args)
{
args.State = new JukeboxComponentState()
{
SongData = component.PlayingSongData,
Playing = component.Repeating,
Volume = component.Volume
};
}
}

View File

@@ -0,0 +1,60 @@
using Content.Shared.White.Jukebox;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Server.White.Jukebox;
public sealed class ServerJukeboxSongsSyncManager : JukeboxSongsSyncManager
{
[Dependency] private readonly INetManager _netManager = default!;
public override void Initialize()
{
base.Initialize();
_netManager.Connected += OnClientConnected;
}
private void OnClientConnected(object? sender, NetChannelArgs e)
{
foreach (var (path, data) in ContentRoot.GetAllFiles())
{
var msg = new JukeboxSongUploadNetMessage
{
RelativePath = path,
Data = data
};
e.Channel.SendMessage(msg);
}
}
public (string songName, ResPath path) SyncSongData(string songName, List<byte> bytes)
{
if (ContentRoot.TryGetFile(new ResPath(songName + ".ogg"), out _))
{
songName += "a";
}
var msg = new JukeboxSongUploadNetMessage()
{
Data = bytes.ToArray(),
RelativePath = new ResPath(songName + ".ogg")
};
OnSongUploaded(msg);
var path = new ResPath($"{Prefix}/{songName}.ogg");
return (songName, path);
}
public override void OnSongUploaded(JukeboxSongUploadNetMessage message)
{
ContentRoot.AddOrUpdateFile(message.RelativePath, message.Data);
foreach (var channel in _netManager.Channels)
{
channel.SendMessage(message);
}
}
}

View File

@@ -0,0 +1,181 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Shared.CCVar;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Content.Shared.White.Jukebox;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Server.White.Jukebox;
public sealed class TapeCreatorSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly ServerJukeboxSongsSyncManager _songsSyncManager = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
private readonly int _recordTime = 25;
private static string TapeCreatorContainerName = "tape_creator_container";
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<JukeboxSongUploadRequest>(OnSongUploaded);
SubscribeLocalEvent<TapeCreatorComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<TapeCreatorComponent, InteractUsingEvent>(OnInteract);
SubscribeLocalEvent<TapeCreatorComponent, GetVerbsEvent<Verb>>(OnTapeCreatorGetVerb);
SubscribeLocalEvent<TapeCreatorComponent, ComponentGetState>(OnTapeCreatorStateChanged);
SubscribeLocalEvent<TapeComponent, ComponentGetState>(OnTapeStateChanged);
}
private void OnTapeCreatorGetVerb(EntityUid uid, TapeCreatorComponent component, GetVerbsEvent<Verb> ev)
{
if (component.Recording) return;
if (ev.Hands == null) return;
if (component.TapeContainer.ContainedEntities.Count == 0) return;
var removeTapeVerb = new Verb()
{
Text = "Вытащить касету",
Priority = 10000,
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/remove_tape.png")),
Act = () =>
{
var tapes = component.TapeContainer.ContainedEntities.ToList();
_containerSystem.EmptyContainer(component.TapeContainer, true);
foreach (var tape in tapes)
{
_handsSystem.PickupOrDrop(ev.User, tape);
}
component.InsertedTape = null;
Dirty(component);
}
};
ev.Verbs.Add(removeTapeVerb);
}
private void OnTapeStateChanged(EntityUid uid, TapeComponent component, ref ComponentGetState args)
{
args.State = new TapeComponentState()
{
Songs = component.Songs
};
}
private void OnTapeCreatorStateChanged(EntityUid uid, TapeCreatorComponent component, ref ComponentGetState args)
{
args.State = new TapeCreatorComponentState
{
Recording = component.Recording,
CoinBalance = component.CoinBalance,
InsertedTape = component.InsertedTape
};
}
private void OnComponentInit(EntityUid uid, TapeCreatorComponent component, ComponentInit args)
{
component.TapeContainer = _containerSystem.EnsureContainer<Container>(uid, TapeCreatorContainerName);
}
private void OnInteract(EntityUid uid, TapeCreatorComponent component, InteractUsingEvent args)
{
if(component.Recording) return;
if (TryComp<TapeComponent>(args.Used, out var tape))
{
var containedEntities = component.TapeContainer.ContainedEntities;
if (containedEntities.Count > 1)
{
var removedTapes = _containerSystem.EmptyContainer(component.TapeContainer, true).ToList();
component.TapeContainer.Insert(args.Used);
foreach (var tapes in removedTapes)
{
_handsSystem.PickupOrDrop(args.User, tapes);
}
}
else
{
component.TapeContainer.Insert(args.Used);
}
component.InsertedTape = tape.Owner;
Dirty(component);
return;
}
if (_tagSystem.HasTag(args.Used, "TapeRecorderCoin"))
{
Del(args.Used);
component.CoinBalance += 1;
Dirty(component);
return;
}
}
private void OnSongUploaded(JukeboxSongUploadRequest ev)
{
if(!TryComp<TapeCreatorComponent>(ev.TapeCreatorUid, out var tapeCreatorComponent)) return;
if (!tapeCreatorComponent.InsertedTape.HasValue || tapeCreatorComponent.CoinBalance <= 0)
{
_popupSystem.PopupEntity("Т# %ак@ э*^о сdf{ал б2я~b? Запись была прервана.", tapeCreatorComponent.Owner);
return;
}
tapeCreatorComponent.CoinBalance -= 1;
tapeCreatorComponent.Recording = true;
var tapeComponent = Comp<TapeComponent>(tapeCreatorComponent.InsertedTape.Value);
var songData = _songsSyncManager.SyncSongData(ev.SongName, ev.SongBytes);
var song = new JukeboxSong()
{
SongName = songData.songName,
SongPath = songData.path
};
tapeComponent.Songs.Add(song);
_popupSystem.PopupEntity($"Запись началась, примерное время ожидания: {_recordTime} секунд", tapeCreatorComponent.Owner);
Dirty(ev.TapeCreatorUid);
Dirty(tapeComponent);
StartRecordDelayAsync(tapeCreatorComponent, _popupSystem, _containerSystem);
}
private async void StartRecordDelayAsync(TapeCreatorComponent component, SharedPopupSystem popupSystem, SharedContainerSystem containerSystem)
{
var recordTimeDelay = _recordTime * 1000 / 10;
await Task.Delay(1000);
for (int i = 0; i < 10; i++)
{
popupSystem.PopupEntity($"Запись мозговой активности выполнена на {i * 10}%", component.Owner);
await Task.Delay(recordTimeDelay);
}
containerSystem.EmptyContainer(component.TapeContainer, force: true).ToList();
component.Recording = false;
component.InsertedTape = null;
popupSystem.PopupEntity($"Запись мозговой активности завершена", component.Owner);
Dirty(component);
}
}

View File

@@ -0,0 +1,20 @@
using Robust.Shared.Serialization;
namespace Content.Shared.White.Jukebox;
[Serializable, NetSerializable]
public class JukeboxStopRequest : BoundUserInterfaceMessage { }
[Serializable, NetSerializable]
public class JukeboxRepeatToggled : BoundUserInterfaceMessage
{
public bool NewState { get; }
public JukeboxRepeatToggled(bool newState)
{
NewState = newState;
}
}
[Serializable, NetSerializable]
public class JukeboxEjectRequest : BoundUserInterfaceMessage { }

View File

@@ -0,0 +1,133 @@
using System.Linq;
using Lidgren.Network;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.White.Jukebox;
[Serializable, NetSerializable]
public enum JukeboxUIKey : byte
{
Key
}
[Serializable, NetSerializable]
public enum TapeCreatorUIKey : byte
{
Key
}
[NetworkedComponent, RegisterComponent]
public class JukeboxComponent : Component
{
public static string JukeboxContainerName = "jukebox_tapes";
public static string JukeboxDefaultSongsName = "jukebox_default_tapes";
[ViewVariables(VVAccess.ReadOnly)]
public Container TapeContainer = default!;
[DataField("defaultTapes")]
public List<string> DefaultTapes = new();
[ViewVariables(VVAccess.ReadOnly)]
public Container DefaultSongsContainer = default!;
[ViewVariables(VVAccess.ReadOnly)]
public bool Repeating { get; set; } = true;
[ViewVariables(VVAccess.ReadOnly)]
public float Volume { get; set; }
public PlayingSongData? PlayingSongData { get; set; }
}
public class TapeContainerComponent : Component
{
public int MaxTapeCount = 1;
public Container TapeContainer { get; set; } = new();
}
[Serializable, NetSerializable]
public class PlayingSongData
{
public ResPath? SongPath;
public string? SongName;
public float PlaybackPosition;
public float ActualSongLengthSeconds;
}
[Serializable, NetSerializable]
public class JukeboxComponentState : ComponentState
{
public bool Playing { get; set; }
public PlayingSongData? SongData { get; set; }
public float Volume { get; set; }
}
[Serializable, NetSerializable, DataDefinition]
public class JukeboxSong
{
[DataField("songName")]
public string? SongName;
[DataField("path")]
public ResPath? SongPath;
}
[Serializable, NetSerializable]
public class JukeboxRequestSongPlay : EntityEventArgs
{
public string? SongName { get; set; }
public ResPath? SongPath { get; set; }
public EntityUid? Jukebox { get; set; }
public float SongDuration { get; set; }
}
[Serializable, NetSerializable]
public class JukeboxRequestStop : EntityEventArgs
{
public EntityUid? JukeboxUid { get; set; }
}
[Serializable, NetSerializable]
public class JukeboxStopPlaying : EntityEventArgs
{
public EntityUid? JukeboxUid { get; set; }
}
[Serializable, NetSerializable]
public class JukeboxSongUploadRequest : EntityEventArgs
{
public string SongName = string.Empty;
public List<byte> SongBytes = new();
public EntityUid TapeCreatorUid = default!;
}
public class JukeboxSongUploadNetMessage : NetMessage
{
public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered;
public override MsgGroups MsgGroup => MsgGroups.Command;
public ResPath RelativePath { get; set; } = ResPath.Self;
public byte[] Data { get; set; } = Array.Empty<byte>();
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
var dataLength = buffer.ReadVariableInt32();
Data = buffer.ReadBytes(dataLength);
RelativePath = new ResPath(buffer.ReadString());
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.WriteVariableInt32(Data.Length);
buffer.Write(Data);
buffer.Write(RelativePath.ToString());
buffer.Write(ResPath.Separator);
}
}

View File

@@ -0,0 +1,37 @@
using Robust.Shared.Containers;
using Robust.Shared.Network;
namespace Content.Shared.White.Jukebox;
public class JukeboxSharedSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly INetManager _netManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<JukeboxComponent, ComponentStartup>(OnJukeboxInit);
}
public void OnJukeboxInit(EntityUid uid, JukeboxComponent component, ComponentStartup args)
{
component.TapeContainer = _containerSystem.EnsureContainer<Container>(uid, JukeboxComponent.JukeboxContainerName);
component.DefaultSongsContainer = _containerSystem.EnsureContainer<Container>(uid, JukeboxComponent.JukeboxDefaultSongsName);
if (_netManager.IsServer)
{
var transform = Transform(component.Owner);
foreach (var tapePrototype in component.DefaultTapes)
{
var tapeUid = EntityManager.SpawnEntity(tapePrototype, transform.MapPosition);
if(!TryComp<TapeComponent>(tapeUid, out _)) continue;
component.DefaultSongsContainer.Insert(tapeUid);
}
}
}
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.ContentPack;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Shared.White.Jukebox;
public abstract class JukeboxSongsSyncManager : IDisposable
{
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] protected readonly IResourceManager ResourceManager = default!;
public static readonly ResPath Prefix = ResPath.Root / "Jukebox";
protected readonly MemoryContentRoot ContentRoot = new();
public virtual void Initialize()
{
ResourceManager.AddRoot(Prefix, ContentRoot);
_netManager.RegisterNetMessage<JukeboxSongUploadNetMessage>(OnSongUploaded);
}
public abstract void OnSongUploaded(JukeboxSongUploadNetMessage message);
public void Dispose()
{
ContentRoot.Dispose();
}
}

View File

@@ -0,0 +1,17 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.White.Jukebox;
[RegisterComponent, NetworkedComponent]
public sealed class TapeComponent : Component
{
[DataField("songs")]
public List<JukeboxSong> Songs { get; set; } = new();
}
[Serializable, NetSerializable]
public sealed class TapeComponentState : ComponentState
{
public List<JukeboxSong> Songs { get; set; } = new();
}

View File

@@ -0,0 +1,29 @@
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.White.Jukebox;
[RegisterComponent, NetworkedComponent]
public sealed class TapeCreatorComponent : Component
{
[DataField("coins")]
public int CoinBalance { get; set; }
[ViewVariables(VVAccess.ReadOnly)]
public bool Recording { get; set; }
[ViewVariables(VVAccess.ReadOnly)]
public EntityUid? InsertedTape { get; set; }
[ViewVariables(VVAccess.ReadOnly)]
public Container TapeContainer { get; set; } = default!;
}
[Serializable, NetSerializable]
public sealed class TapeCreatorComponentState : ComponentState
{
public int CoinBalance { get; set; }
public bool Recording { get; set; }
public EntityUid? InsertedTape { get; set; }
}

View File

@@ -158,6 +158,19 @@ public sealed class WhiteCVars
public static readonly CVarDef<float> BwoinkVolume =
CVarDef.Create("white.admin.bwoinkVolume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
/*
* Jukebox
*/
public static readonly CVarDef<float> MaxJukeboxSongSizeInMB = CVarDef.Create("white.max_jukebox_song_size",
3.5f, CVar.SERVER | CVar.REPLICATED | CVar.ARCHIVE);
public static readonly CVarDef<float> MaxJukeboxSoundRange = CVarDef.Create("white.max_jukebox_sound_range", 20f,
CVar.SERVER | CVar.REPLICATED | CVar.ARCHIVE);
public static readonly CVarDef<float> JukeboxVolume =
CVarDef.Create("white.jukebox_volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
/*
* Chat

Binary file not shown.

View File

@@ -0,0 +1,52 @@
- type: entity
parent: BaseItem
id: Jukebox
name: jukebox
description: Альфа-Шкварки
suffix: Empty
components:
- type: Jukebox
- type: Sprite
sprite: White/Objects/Devices/jukebox.rsi
layers:
- state: boombox
- state: boombox_working_overlay
map: ["bars"]
netsync: false
- type: Item
sprite: White/Objects/Devices/jukebox.rsi
heldPrefix: boombox
size: 500
- type: UserInterface
interfaces:
- key: enum.JukeboxUIKey.Key
type: JukeboxBUI
- type: ActivatableUI
key: enum.JukeboxUIKey.Key
- type: entity
parent: BaseItem
id: TapeRecorder
name: tape recorder
suffix: Empty
components:
- type: TapeCreator
coins: 3
- type: Sprite
scale: 0.7, 0.7
sprite: White/Objects/Devices/tapeRecorder.rsi
layers:
- state: base
- state: tape
map: ["tape"]
visible: false
netsync: false
- type: Item
sprite: White/Objects/Devices/tapeRecorder.rsi
heldPrefix: tapeRecorder
- type: UserInterface
interfaces:
- key: enum.TapeCreatorUIKey.Key
type: TapeCreatorBUI
- type: ActivatableUI
key: enum.TapeCreatorUIKey.Key

View File

@@ -0,0 +1,27 @@
- type: entity
parent: BaseItem
id: BaseTape
name: baseTape
abstract: true
noSpawn: true
description: это база
suffix: Empty
components:
- type: Tape
- type: Sprite
sprite: Objects/Devices/jukeboxStuff/tape.rsi
layers:
- state: tape
netsync: false
- type: Item
sprite: Objects/Devices/jukeboxStuff/tape.rsi
heldPrefix: tape
- type: EmitSoundOnLand
sound:
path: /Audio/White/Jukebox/Tapes/tape_fall.ogg
- type: entity
parent: BaseTape
id: TapeEmpty
name: Old tape
description: Ligma balls

View File

@@ -1197,3 +1197,6 @@
id: Crystal
# WHITE END
- type: Tag
id: TapeRecorderCoin

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

View File

@@ -0,0 +1,12 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from infinity baystation 12, https://github.com/infinitystation/Baystation12/blob/073f678cdce92edb8fcd55f9ffc9f0523bf31506/icons/obj/radio.dmi",
"size": {
"x": 14,
"y": 8
},
"states" : [
{"name": "tape"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B