using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Audio.Jukebox; using Content.Shared.Power; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Components; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; using Robust.Shared.Prototypes; using JukeboxComponent = Content.Shared.Audio.Jukebox.JukeboxComponent; using Robust.Shared.Random; // Frontier namespace Content.Server.Audio.Jukebox; public sealed class JukeboxSystem : SharedJukeboxSystem { [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; // Frontier [Dependency] private readonly TransformSystem _transform = default!; // Frontier [Dependency] private readonly UserInterfaceSystem _userInterface = default!; // Frontier public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnJukeboxSelected); SubscribeLocalEvent(OnJukeboxPlay); SubscribeLocalEvent(OnJukeboxPause); SubscribeLocalEvent(OnJukeboxStop); SubscribeLocalEvent(OnJukeboxSetPlayback); // Frontier SubscribeLocalEvent(OnJukeboxSetTime); SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnComponentStartup); // Frontier SubscribeLocalEvent(OnPowerChanged); } private void OnComponentInit(EntityUid uid, JukeboxComponent component, ComponentInit args) { if (HasComp(uid)) { TryUpdateVisualState(uid, component); } } // Frontier: Shuffle & Repeat private void OnComponentStartup(Entity entity, ref ComponentStartup ev) { UpdateUI(entity); } private void UpdateUI(Entity ent) { var state = new JukeboxInterfaceState(ent.Comp.PlaybackMode); _userInterface.SetUiState(ent.Owner, JukeboxUiKey.Key, state); } // End Frontier: Shuffle & Repeat 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); // Frontier: Shuffling feature. if (component.PlaybackMode == JukeboxPlaybackMode.Shuffle && !component.FirstPlay && _protoManager.TryGetRandom(_random, out var newProto) && newProto is JukeboxPrototype newJukeboxProto) { component.SelectedSongId = newJukeboxProto; } // End Frontier 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; // Frontier: wallmount jukebox, shuffle state if (TryComp(component.AudioStream, out var xform)) _transform.SetLocalPosition(component.AudioStream.Value, component.AudioOffset, xform); component.FirstPlay = false; // End Frontier Dirty(uid, component); } } private void OnJukeboxPause(Entity ent, ref JukeboxPauseMessage args) { Audio.SetState(ent.Comp.AudioStream, AudioState.Paused); } // Frontier: Shuffle & Repeat private void OnJukeboxSetPlayback(Entity ent, ref JukeboxSetPlaybackModeMessage playbackModeMessage) { if (ent.Comp.PlaybackMode != playbackModeMessage.PlaybackMode) { ent.Comp.PlaybackMode = playbackModeMessage.PlaybackMode; UpdateUI(ent); Dirty(ent); } } public AudioState GetAudioState(EntityUid? entity, AudioComponent? component = null) { if (entity == null || !Resolve(entity.Value, ref component, false)) return AudioState.Stopped; // Consider no audio as stopped. return component.State; } // End Frontier: Shuffle & Repeat private void OnJukeboxSetTime(EntityUid uid, JukeboxComponent component, JukeboxSetTimeMessage args) { if (TryComp(args.Actor, out ActorComponent? actorComp)) { var offset = actorComp.PlayerSession.Channel.Ping * 1.5f / 1000f; Audio.SetPlaybackPosition(component.AudioStream, args.SongTime + offset); } } private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) { TryUpdateVisualState(entity); if (!this.IsPowered(entity.Owner, EntityManager)) { Stop(entity); } } private void OnJukeboxStop(Entity entity, ref JukeboxStopMessage args) { Stop(entity); } // Frontier: Modified Stop() function for the Shuffling & Replay features. private void Stop(Entity entity) { //Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped); // No longer needed since we're removing the AudioStream. entity.Comp.AudioStream = Audio.Stop(entity.Comp.AudioStream); entity.Comp.FirstPlay = true; Dirty(entity); } // End Frontier private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args) { // Frontier: allow selecting songs while they're playing bool wasPlaying = Audio.IsPlaying(component.AudioStream); component.SelectedSongId = args.SongId; DirectSetVisualState(uid, JukeboxVisualState.Select); component.Selecting = true; component.SelectAccumulator = 0; component.AudioStream = Audio.Stop(component.AudioStream); component.FirstPlay = true; // Prevent shuffling if (wasPlaying) { var msg = new JukeboxPlayingMessage(); OnJukeboxPlay(uid, component, ref msg); } // End Frontier Dirty(uid, component); } public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); 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); } } // Frontier: Replay feature. Please pitch in if you have better ideas. This is a pretty bad implementation. if (comp.PlaybackMode != JukeboxPlaybackMode.Single && comp.AudioStream != null && GetAudioState(comp.AudioStream) == AudioState.Stopped) { var msg = new JukeboxPlayingMessage(); OnJukeboxPlay(uid, comp, ref msg); } // End Frontier } } 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); } }