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:
metalgearsloth
2024-04-17 19:27:00 +10:00
committed by GitHub
parent 177d69cb98
commit 2db374988c
27 changed files with 907 additions and 1 deletions

View 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;
}
}

View 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>

View 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 = "---";
}
}
}

View 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);
}
}

View 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);
}
}

View 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
}

View 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!;
}

View 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,
}

View 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!;
}

View 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"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -446,6 +446,7 @@
- TelecomServerCircuitboard
- MassMediaCircuitboard
- ReagentGrinderIndustrialMachineCircuitboard
- JukeboxCircuitBoard
- type: MaterialStorage
whitelist:
tags:

View File

@@ -965,3 +965,11 @@
Steel: 100
Glass: 900
Gold: 100
- type: latheRecipe
id: JukeboxCircuitBoard
result: JukeboxCircuitBoard
completetime: 4
materials:
Steel: 100
Glass: 900

View File

@@ -70,6 +70,7 @@
- BorgModuleClowning
- DawInstrumentMachineCircuitboard
- MassMediaCircuitboard
- JukeboxCircuitBoard
- type: technology
id: RoboticCleanliness

View 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
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB