Added Jukebox (#26736)
* Added Jukebox, along with music for jukebox * Fixed Jukebox meta.json copyright * Removed songs I couldn't find a license for. * Renamed files to solve check failures from spaces * Added missing attributions.yml * Fixed lack of description in Jukebox * Jukebox is now constructable. * Change Jukebox menu to FancyWindow * Moved Jukebox messages out of jukebox component * Removed Jukebox OnValueChanged. * JukeboxComp now uses AutoGenerateComponentState * Removed state code, since it's auto generated * Fixed various Jukebox code to match conventions. * Updated Standard.yml to match changed song list. * fixes * Jukebox workin * Fix * Polishing * Finalising * Revert * bad * jukey * Reviews * name * Update submodule to 218.2.0 --------- Co-authored-by: iNVERTED <alextjorgensen@gmail.com>
This commit is contained in:
119
Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
Normal file
119
Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Audio.Jukebox;
|
||||
|
||||
public sealed class JukeboxBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private JukeboxMenu? _menu;
|
||||
|
||||
public JukeboxBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new JukeboxMenu();
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
|
||||
_menu.OnPlayPressed += args =>
|
||||
{
|
||||
if (args)
|
||||
{
|
||||
SendMessage(new JukeboxPlayingMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
SendMessage(new JukeboxPauseMessage());
|
||||
}
|
||||
};
|
||||
|
||||
_menu.OnStopPressed += () =>
|
||||
{
|
||||
SendMessage(new JukeboxStopMessage());
|
||||
};
|
||||
|
||||
_menu.OnSongSelected += SelectSong;
|
||||
|
||||
_menu.SetTime += SetTime;
|
||||
PopulateMusic();
|
||||
Reload();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the attached menu if it exists.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox))
|
||||
return;
|
||||
|
||||
_menu.SetAudioStream(jukebox.AudioStream);
|
||||
|
||||
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto))
|
||||
{
|
||||
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString());
|
||||
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
_menu.SetSelectedSong(string.Empty, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void PopulateMusic()
|
||||
{
|
||||
_menu?.Populate(_protoManager.EnumeratePrototypes<JukeboxPrototype>());
|
||||
}
|
||||
|
||||
public void SelectSong(ProtoId<JukeboxPrototype> songid)
|
||||
{
|
||||
SendMessage(new JukeboxSelectedMessage(songid));
|
||||
}
|
||||
|
||||
public void SetTime(float time)
|
||||
{
|
||||
var sentTime = time;
|
||||
|
||||
// You may be wondering, what the fuck is this
|
||||
// Well we want to be able to predict the playback slider change, of which there are many ways to do it
|
||||
// We can't just use SendPredictedMessage because it will reset every tick and audio updates every frame
|
||||
// so it will go BRRRRT
|
||||
// Using ping gets us close enough that it SHOULD, MOST OF THE TIME, fall within the 0.1 second tolerance
|
||||
// that's still on engine so our playback position never gets corrected.
|
||||
if (EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox) &&
|
||||
EntMan.TryGetComponent(jukebox.AudioStream, out AudioComponent? audioComp))
|
||||
{
|
||||
audioComp.PlaybackPosition = time;
|
||||
}
|
||||
|
||||
SendMessage(new JukeboxSetTimeMessage(sentTime));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.OnClose -= Close;
|
||||
_menu.Dispose();
|
||||
_menu = null;
|
||||
}
|
||||
}
|
||||
|
||||
18
Content.Client/Audio/Jukebox/JukeboxMenu.xaml
Normal file
18
Content.Client/Audio/Jukebox/JukeboxMenu.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<ui:FancyWindow xmlns="https://spacestation14.io" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="400 500" Title="{Loc 'jukebox-menu-title'}">
|
||||
<BoxContainer Margin="4 0" Orientation="Vertical">
|
||||
<ItemList Name="MusicList" SelectMode="Button" Margin="3 3 3 3"
|
||||
HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="8"/>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="SongSelected" Text="{Loc 'jukebox-menu-selectedsong'}" />
|
||||
<Label Name="SongName" Text="---" />
|
||||
<Slider Name="PlaybackSlider" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"
|
||||
VerticalExpand="False" SizeFlagsStretchRatio="1">
|
||||
<Button Name="PlayButton" Text="{Loc 'jukebox-menu-buttonplay'}" />
|
||||
<Button Name="StopButton" Text="{Loc 'jukebox-menu-buttonstop'}" />
|
||||
<Label Name="DurationLabel" Text="00:00 / 00:00" HorizontalAlignment="Right" HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ui:FancyWindow>
|
||||
166
Content.Client/Audio/Jukebox/JukeboxMenu.xaml.cs
Normal file
166
Content.Client/Audio/Jukebox/JukeboxMenu.xaml.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
|
||||
namespace Content.Client.Audio.Jukebox;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class JukeboxMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private AudioSystem _audioSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently 'playing' or paused for the play / pause button.
|
||||
/// </summary>
|
||||
private bool _playState;
|
||||
|
||||
/// <summary>
|
||||
/// True if playing, false if paused.
|
||||
/// </summary>
|
||||
public event Action<bool>? OnPlayPressed;
|
||||
public event Action? OnStopPressed;
|
||||
public event Action<ProtoId<JukeboxPrototype>>? OnSongSelected;
|
||||
public event Action<float>? SetTime;
|
||||
|
||||
private EntityUid? _audio;
|
||||
|
||||
private float _lockTimer;
|
||||
|
||||
public JukeboxMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_audioSystem = _entManager.System<AudioSystem>();
|
||||
|
||||
MusicList.OnItemSelected += args =>
|
||||
{
|
||||
var entry = MusicList[args.ItemIndex];
|
||||
|
||||
if (entry.Metadata is not string juke)
|
||||
return;
|
||||
|
||||
OnSongSelected?.Invoke(juke);
|
||||
};
|
||||
|
||||
PlayButton.OnPressed += args =>
|
||||
{
|
||||
OnPlayPressed?.Invoke(!_playState);
|
||||
};
|
||||
|
||||
StopButton.OnPressed += args =>
|
||||
{
|
||||
OnStopPressed?.Invoke();
|
||||
};
|
||||
PlaybackSlider.OnReleased += PlaybackSliderKeyUp;
|
||||
|
||||
SetPlayPauseButton(_audioSystem.IsPlaying(_audio), force: true);
|
||||
}
|
||||
|
||||
public JukeboxMenu(AudioSystem audioSystem)
|
||||
{
|
||||
_audioSystem = audioSystem;
|
||||
}
|
||||
|
||||
public void SetAudioStream(EntityUid? audio)
|
||||
{
|
||||
_audio = audio;
|
||||
}
|
||||
|
||||
private void PlaybackSliderKeyUp(Slider args)
|
||||
{
|
||||
SetTime?.Invoke(PlaybackSlider.Value);
|
||||
_lockTimer = 0.5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-populates the list of jukebox prototypes available.
|
||||
/// </summary>
|
||||
public void Populate(IEnumerable<JukeboxPrototype> jukeboxProtos)
|
||||
{
|
||||
MusicList.Clear();
|
||||
|
||||
foreach (var entry in jukeboxProtos)
|
||||
{
|
||||
MusicList.AddItem(entry.Name, metadata: entry.ID);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlayPauseButton(bool playing, bool force = false)
|
||||
{
|
||||
if (_playState == playing && !force)
|
||||
return;
|
||||
|
||||
_playState = playing;
|
||||
|
||||
if (playing)
|
||||
{
|
||||
PlayButton.Text = Loc.GetString("jukebox-menu-buttonpause");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayButton.Text = Loc.GetString("jukebox-menu-buttonplay");
|
||||
}
|
||||
|
||||
public void SetSelectedSong(string name, float length)
|
||||
{
|
||||
SetSelectedSongText(name);
|
||||
PlaybackSlider.MaxValue = length;
|
||||
PlaybackSlider.SetValueWithoutEvent(0);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_lockTimer > 0f)
|
||||
{
|
||||
_lockTimer -= args.DeltaSeconds;
|
||||
}
|
||||
|
||||
PlaybackSlider.Disabled = _lockTimer > 0f;
|
||||
|
||||
if (_entManager.TryGetComponent(_audio, out AudioComponent? audio))
|
||||
{
|
||||
DurationLabel.Text = $@"{TimeSpan.FromSeconds(audio.PlaybackPosition):mm\:ss} / {_audioSystem.GetAudioLength(audio.FileName):mm\:ss}";
|
||||
}
|
||||
else
|
||||
{
|
||||
DurationLabel.Text = $"00:00 / 00:00";
|
||||
}
|
||||
|
||||
if (PlaybackSlider.Grabbed)
|
||||
return;
|
||||
|
||||
if (audio != null || _entManager.TryGetComponent(_audio, out audio))
|
||||
{
|
||||
PlaybackSlider.SetValueWithoutEvent(audio.PlaybackPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaybackSlider.SetValueWithoutEvent(0f);
|
||||
}
|
||||
|
||||
SetPlayPauseButton(_audioSystem.IsPlaying(_audio, audio));
|
||||
}
|
||||
|
||||
public void SetSelectedSongText(string? text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
SongName.Text = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongName.Text = "---";
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Content.Client/Audio/Jukebox/JukeboxSystem.cs
Normal file
153
Content.Client/Audio/Jukebox/JukeboxSystem.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Audio.Jukebox;
|
||||
|
||||
|
||||
public sealed class JukeboxSystem : SharedJukeboxSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<JukeboxComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<JukeboxComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<JukeboxComponent, AfterAutoHandleStateEvent>(OnJukeboxAfterState);
|
||||
|
||||
_protoManager.PrototypesReloaded += OnProtoReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_protoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.WasModified<JukeboxPrototype>())
|
||||
return;
|
||||
|
||||
var query = AllEntityQuery<JukeboxComponent, UserInterfaceComponent>();
|
||||
|
||||
while (query.MoveNext(out _, out var ui))
|
||||
{
|
||||
if (!ui.OpenInterfaces.TryGetValue(JukeboxUiKey.Key, out var baseBui) ||
|
||||
baseBui is not JukeboxBoundUserInterface bui)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bui.PopulateMusic();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxAfterState(Entity<JukeboxComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp(ent, out UserInterfaceComponent? ui))
|
||||
return;
|
||||
|
||||
if (!ui.OpenInterfaces.TryGetValue(JukeboxUiKey.Key, out var baseBui) ||
|
||||
baseBui is not JukeboxBoundUserInterface bui)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bui.Reload();
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, JukeboxComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance) ||
|
||||
!_appearanceSystem.TryGetData<JukeboxVisualState>(uid, JukeboxVisuals.VisualState, out var visualState, appearance))
|
||||
{
|
||||
visualState = JukeboxVisualState.On;
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, visualState, component, sprite);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, JukeboxComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(JukeboxVisuals.VisualState, out var visualStateObject) ||
|
||||
visualStateObject is not JukeboxVisualState visualState)
|
||||
{
|
||||
visualState = JukeboxVisualState.On;
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, visualState, component, args.Sprite);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, JukeboxVisualState visualState, JukeboxComponent component, SpriteComponent sprite)
|
||||
{
|
||||
SetLayerState(JukeboxVisualLayers.Base, component.OffState, sprite);
|
||||
|
||||
switch (visualState)
|
||||
{
|
||||
case JukeboxVisualState.On:
|
||||
SetLayerState(JukeboxVisualLayers.Base, component.OnState, sprite);
|
||||
break;
|
||||
|
||||
case JukeboxVisualState.Off:
|
||||
SetLayerState(JukeboxVisualLayers.Base, component.OffState, sprite);
|
||||
break;
|
||||
|
||||
case JukeboxVisualState.Select:
|
||||
PlayAnimation(uid, JukeboxVisualLayers.Base, component.SelectState, 1.0f, sprite);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayAnimation(EntityUid uid, JukeboxVisualLayers layer, string? state, float animationTime, SpriteComponent sprite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(state))
|
||||
return;
|
||||
|
||||
if (!_animationPlayer.HasRunningAnimation(uid, state))
|
||||
{
|
||||
var animation = GetAnimation(layer, state, animationTime);
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
_animationPlayer.Play(uid, animation, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static Animation GetAnimation(JukeboxVisualLayers layer, string state, float animationTime)
|
||||
{
|
||||
return new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(animationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = layer,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(state, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void SetLayerState(JukeboxVisualLayers layer, string? state, SpriteComponent sprite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(state))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetAutoAnimated(layer, true);
|
||||
sprite.LayerSetState(layer, state);
|
||||
}
|
||||
}
|
||||
152
Content.Server/Audio/Jukebox/JukeboxSystem.cs
Normal file
152
Content.Server/Audio/Jukebox/JukeboxSystem.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using JukeboxComponent = Content.Shared.Audio.Jukebox.JukeboxComponent;
|
||||
|
||||
namespace Content.Server.Audio.Jukebox;
|
||||
|
||||
|
||||
public sealed class JukeboxSystem : SharedJukeboxSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxSelectedMessage>(OnJukeboxSelected);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxPlayingMessage>(OnJukeboxPlay);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxPauseMessage>(OnJukeboxPause);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxStopMessage>(OnJukeboxStop);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxSetTimeMessage>(OnJukeboxSetTime);
|
||||
SubscribeLocalEvent<JukeboxComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<JukeboxComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
|
||||
SubscribeLocalEvent<JukeboxComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, JukeboxComponent component, ComponentInit args)
|
||||
{
|
||||
if (HasComp<ApcPowerReceiverComponent>(uid))
|
||||
{
|
||||
TryUpdateVisualState(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxPlay(EntityUid uid, JukeboxComponent component, ref JukeboxPlayingMessage args)
|
||||
{
|
||||
if (Exists(component.AudioStream))
|
||||
{
|
||||
Audio.SetState(component.AudioStream, AudioState.Playing);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.AudioStream = Audio.Stop(component.AudioStream);
|
||||
|
||||
if (string.IsNullOrEmpty(component.SelectedSongId) ||
|
||||
!_protoManager.TryIndex(component.SelectedSongId, out var jukeboxProto))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
component.AudioStream = Audio.PlayPvs(jukeboxProto.Path, uid, AudioParams.Default.WithMaxDistance(10f))?.Entity;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxPause(Entity<JukeboxComponent> ent, ref JukeboxPauseMessage args)
|
||||
{
|
||||
Audio.SetState(ent.Comp.AudioStream, AudioState.Paused);
|
||||
}
|
||||
|
||||
private void OnJukeboxSetTime(EntityUid uid, JukeboxComponent component, JukeboxSetTimeMessage args)
|
||||
{
|
||||
var offset = (args.Session.Channel.Ping * 1.5f) / 1000f;
|
||||
Audio.SetPlaybackPosition(component.AudioStream, args.SongTime + offset);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(Entity<JukeboxComponent> entity, ref PowerChangedEvent args)
|
||||
{
|
||||
TryUpdateVisualState(entity);
|
||||
|
||||
if (!this.IsPowered(entity.Owner, EntityManager))
|
||||
{
|
||||
Stop(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxStop(Entity<JukeboxComponent> entity, ref JukeboxStopMessage args)
|
||||
{
|
||||
Stop(entity);
|
||||
}
|
||||
|
||||
private void Stop(Entity<JukeboxComponent> entity)
|
||||
{
|
||||
Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped);
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args)
|
||||
{
|
||||
if (!Audio.IsPlaying(component.AudioStream))
|
||||
{
|
||||
component.SelectedSongId = args.SongId;
|
||||
DirectSetVisualState(uid, JukeboxVisualState.Select);
|
||||
component.Selecting = true;
|
||||
component.AudioStream = Audio.Stop(component.AudioStream);
|
||||
}
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<JukeboxComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.Selecting)
|
||||
{
|
||||
comp.SelectAccumulator += frameTime;
|
||||
if (comp.SelectAccumulator >= 0.5f)
|
||||
{
|
||||
comp.SelectAccumulator = 0f;
|
||||
comp.Selecting = false;
|
||||
|
||||
TryUpdateVisualState(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(EntityUid uid, JukeboxComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.AudioStream = Audio.Stop(component.AudioStream);
|
||||
}
|
||||
|
||||
private void DirectSetVisualState(EntityUid uid, JukeboxVisualState state)
|
||||
{
|
||||
_appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, state);
|
||||
}
|
||||
|
||||
private void TryUpdateVisualState(EntityUid uid, JukeboxComponent? jukeboxComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref jukeboxComponent))
|
||||
return;
|
||||
|
||||
var finalState = JukeboxVisualState.On;
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
{
|
||||
finalState = JukeboxVisualState.Off;
|
||||
}
|
||||
|
||||
_appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, finalState);
|
||||
}
|
||||
}
|
||||
80
Content.Shared/Audio/Jukebox/JukeboxComponent.cs
Normal file
80
Content.Shared/Audio/Jukebox/JukeboxComponent.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Audio.Jukebox;
|
||||
|
||||
[NetworkedComponent, RegisterComponent, AutoGenerateComponentState(true)]
|
||||
[Access(typeof(SharedJukeboxSystem))]
|
||||
public sealed partial class JukeboxComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<JukeboxPrototype>? SelectedSongId;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? AudioStream;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the jukebox being on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? OnState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the jukebox being on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? OffState;
|
||||
|
||||
/// <summary>
|
||||
/// RSI state for the jukebox track being selected.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? SelectState;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Selecting;
|
||||
|
||||
[ViewVariables]
|
||||
public float SelectAccumulator;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class JukeboxPlayingMessage : BoundUserInterfaceMessage;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class JukeboxPauseMessage : BoundUserInterfaceMessage;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class JukeboxStopMessage : BoundUserInterfaceMessage;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class JukeboxSelectedMessage(ProtoId<JukeboxPrototype> songId) : BoundUserInterfaceMessage
|
||||
{
|
||||
public ProtoId<JukeboxPrototype> SongId { get; } = songId;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class JukeboxSetTimeMessage(float songTime) : BoundUserInterfaceMessage
|
||||
{
|
||||
public float SongTime { get; } = songTime;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum JukeboxVisuals : byte
|
||||
{
|
||||
VisualState
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum JukeboxVisualState : byte
|
||||
{
|
||||
On,
|
||||
Off,
|
||||
Select,
|
||||
}
|
||||
|
||||
public enum JukeboxVisualLayers : byte
|
||||
{
|
||||
Base
|
||||
}
|
||||
23
Content.Shared/Audio/Jukebox/JukeboxPrototype.cs
Normal file
23
Content.Shared/Audio/Jukebox/JukeboxPrototype.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Audio.Jukebox;
|
||||
|
||||
/// <summary>
|
||||
/// Soundtrack that's visible on the jukebox list.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed class JukeboxPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// User friendly name to use in UI.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string Name = string.Empty;
|
||||
|
||||
[DataField(required: true)]
|
||||
public SoundPathSpecifier Path = default!;
|
||||
}
|
||||
11
Content.Shared/Audio/Jukebox/JukeboxUi.cs
Normal file
11
Content.Shared/Audio/Jukebox/JukeboxUi.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Audio.Jukebox;
|
||||
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum JukeboxUiKey : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
8
Content.Shared/Audio/Jukebox/SharedJukeboxSystem.cs
Normal file
8
Content.Shared/Audio/Jukebox/SharedJukeboxSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Shared.Audio.Jukebox;
|
||||
|
||||
public abstract class SharedJukeboxSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
}
|
||||
22
Resources/Audio/Jukebox/attributions.yml
Normal file
22
Resources/Audio/Jukebox/attributions.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
- files: ["sector11.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "-Sector11 by MashedByMachines. Converted to mono OGG."
|
||||
source: "https://www.newgrounds.com/audio/listen/312622"
|
||||
|
||||
- files: ["mod.flip-flap.ogg"]
|
||||
license: "Custom"
|
||||
copyright: "Flip Flap by X-ceed is licensed under a short but clear license (see flip-flap.txt in Audio/Lobby) and is free for non-commercial use. Converted to mono OGG."
|
||||
source: "http://aminet.net/package/mods/xceed/Flipflap"
|
||||
|
||||
- files: ["title3.ogg"]
|
||||
license: "CC-BY-NC-SA-3.0"
|
||||
copyright: "Title3 by Cuboos. It is a remix of the song 'Tintin on the Moon'. Converted to mono OGG."
|
||||
source: "https://www.youtube.com/watch?v=YKVmXn-Gv0M"
|
||||
|
||||
- files:
|
||||
- "constellations.ogg"
|
||||
- "drifting.ogg"
|
||||
- "starlight.ogg"
|
||||
license: "CC-BY-3.0"
|
||||
copyright: "Constellations by Qwertyquerty. Converted to mono OGG."
|
||||
source: "https://www.youtube.com/channel/UCPYbhBUGhH7n_G4HLK2YipQ"
|
||||
BIN
Resources/Audio/Jukebox/constellations.ogg
Normal file
BIN
Resources/Audio/Jukebox/constellations.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Jukebox/drifting.ogg
Normal file
BIN
Resources/Audio/Jukebox/drifting.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Jukebox/flip-flap.ogg
Normal file
BIN
Resources/Audio/Jukebox/flip-flap.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Jukebox/sector11.ogg
Normal file
BIN
Resources/Audio/Jukebox/sector11.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Jukebox/starlight.ogg
Normal file
BIN
Resources/Audio/Jukebox/starlight.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Jukebox/title3.ogg
Normal file
BIN
Resources/Audio/Jukebox/title3.ogg
Normal file
Binary file not shown.
5
Resources/Locale/en-US/jukebox/jukebox-menu.ftl
Normal file
5
Resources/Locale/en-US/jukebox/jukebox-menu.ftl
Normal file
@@ -0,0 +1,5 @@
|
||||
jukebox-menu-title = Jukebox
|
||||
jukebox-menu-selectedsong = Selected Song:
|
||||
jukebox-menu-buttonplay = Play
|
||||
jukebox-menu-buttonpause = Pause
|
||||
jukebox-menu-buttonstop = Stop
|
||||
35
Resources/Prototypes/Catalog/Jukebox/Standard.yml
Normal file
35
Resources/Prototypes/Catalog/Jukebox/Standard.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
- type: jukebox
|
||||
id: FlipFlap
|
||||
name: X-CEED - Flip Flap
|
||||
path:
|
||||
path: /Audio/Jukebox/flip-flap.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: Tintin
|
||||
name: Jeroen Tel - Tintin on the Moon
|
||||
path:
|
||||
path: /Audio/Jukebox/title3.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: Thunderdome
|
||||
name: MashedByMachines - Sector 11
|
||||
path:
|
||||
path: /Audio/Jukebox/sector11.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: Constellations
|
||||
name: Qwertyquerty - Constellations
|
||||
path:
|
||||
path: /Audio/Jukebox/constellations.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: Drifting
|
||||
name: Qwertyquerty - Drifting
|
||||
path:
|
||||
path: /Audio/Jukebox/drifting.ogg
|
||||
|
||||
- type: jukebox
|
||||
id: starlight
|
||||
name: Qwertyquerty - Starlight
|
||||
path:
|
||||
path: /Audio/Jukebox/starlight.ogg
|
||||
@@ -1370,3 +1370,17 @@
|
||||
Manipulator: 3
|
||||
materialRequirements:
|
||||
Glass: 1
|
||||
|
||||
- type: entity
|
||||
parent: BaseMachineCircuitboard
|
||||
id: JukeboxCircuitBoard
|
||||
name: jukebox machine board
|
||||
description: A machine printed circuit board for a jukebox.
|
||||
components:
|
||||
- type: MachineBoard
|
||||
prototype: Jukebox
|
||||
materialRequirements:
|
||||
WoodPlank: 5
|
||||
Steel: 2
|
||||
Glass: 5
|
||||
Cable: 2
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
- type: entity
|
||||
id: Jukebox
|
||||
name: jukebox
|
||||
parent: [ BaseMachinePowered, ConstructibleMachine ]
|
||||
description: A machine capable of playing a wide variety of tunes. Enjoyment not guaranteed.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Structures/Machines/jukebox.rsi
|
||||
layers:
|
||||
- state: "off"
|
||||
map: ["enum.JukeboxVisualLayers.Base"]
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: Jukebox
|
||||
onState: on
|
||||
offState: off
|
||||
selectState: select
|
||||
- type: Machine
|
||||
board: JukeboxCircuitBoard
|
||||
- type: Appearance
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 100
|
||||
- type: ExtensionCableReceiver
|
||||
- type: ActivatableUI
|
||||
key: enum.JukeboxUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.JukeboxUiKey.Key
|
||||
type: JukeboxBoundUserInterface
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 75
|
||||
behaviors:
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
SheetSteel1:
|
||||
min: 1
|
||||
max: 2
|
||||
- type: Physics
|
||||
bodyType: Static
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.25,-0.45,0.25,0.45"
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- MachineLayer
|
||||
density: 200
|
||||
@@ -446,6 +446,7 @@
|
||||
- TelecomServerCircuitboard
|
||||
- MassMediaCircuitboard
|
||||
- ReagentGrinderIndustrialMachineCircuitboard
|
||||
- JukeboxCircuitBoard
|
||||
- type: MaterialStorage
|
||||
whitelist:
|
||||
tags:
|
||||
|
||||
@@ -965,3 +965,11 @@
|
||||
Steel: 100
|
||||
Glass: 900
|
||||
Gold: 100
|
||||
|
||||
- type: latheRecipe
|
||||
id: JukeboxCircuitBoard
|
||||
result: JukeboxCircuitBoard
|
||||
completetime: 4
|
||||
materials:
|
||||
Steel: 100
|
||||
Glass: 900
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
- BorgModuleClowning
|
||||
- DawInstrumentMachineCircuitboard
|
||||
- MassMediaCircuitboard
|
||||
- JukeboxCircuitBoard
|
||||
|
||||
- type: technology
|
||||
id: RoboticCleanliness
|
||||
|
||||
31
Resources/Textures/Structures/Machines/jukebox.rsi/meta.json
Normal file
31
Resources/Textures/Structures/Machines/jukebox.rsi/meta.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/tgstation/tgstation at f349b842c84f500399bd5673e5e34a6bc45b001a, direct dmi link https://github.com/tgstation/tgstation/blob/f349b842c84f500399bd5673e5e34a6bc45b001a/icons/obj/stationobjs.dmi",
|
||||
"states": [
|
||||
{
|
||||
"name": "on"
|
||||
},
|
||||
{
|
||||
"name": "off"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/Structures/Machines/jukebox.rsi/off.png
Normal file
BIN
Resources/Textures/Structures/Machines/jukebox.rsi/off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 770 B |
BIN
Resources/Textures/Structures/Machines/jukebox.rsi/on.png
Normal file
BIN
Resources/Textures/Structures/Machines/jukebox.rsi/on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 673 B |
BIN
Resources/Textures/Structures/Machines/jukebox.rsi/select.png
Normal file
BIN
Resources/Textures/Structures/Machines/jukebox.rsi/select.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Reference in New Issue
Block a user