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.Player; 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!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnJukeboxSelected); SubscribeLocalEvent(OnJukeboxPlay); SubscribeLocalEvent(OnJukeboxPause); SubscribeLocalEvent(OnJukeboxStop); SubscribeLocalEvent(OnJukeboxSetTime); SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnJukeboxAddQueue); SubscribeLocalEvent(OnJukeboxRemoveQueue); SubscribeLocalEvent(OnAudioShutdown); SubscribeLocalEvent(OnPowerChanged); } private void OnComponentInit(EntityUid uid, JukeboxComponent component, ComponentInit args) { if (HasComp(uid)) { TryUpdateVisualState(uid, component); } } private void OnJukeboxPlay(EntityUid uid, JukeboxComponent component, ref JukeboxPlayingMessage args) { OnJukeboxPlay(uid, component); } private void OnJukeboxAddQueue(EntityUid uid, JukeboxComponent component, JukeboxAddQueueMessage args) { if (component.SelectedSongId == null) { component.SelectedSongId = args.SongId; } else { component.SongIdQueue.Enqueue(args.SongId); } Dirty(uid, component); } private void OnJukeboxRemoveQueue(EntityUid uid, JukeboxComponent component, JukeboxRemoveQueueMessage args) { if (args.Index < 0 || args.Index >= component.SongIdQueue.Count) return; var list = new List>(component.SongIdQueue); list.RemoveAt(args.Index); component.SongIdQueue.Clear(); foreach (var entry in list) { component.SongIdQueue.Enqueue(entry); } Dirty(uid, component); } private void OnJukeboxPause(Entity ent, ref JukeboxPauseMessage args) { Audio.SetState(ent.Comp.AudioStream, AudioState.Paused); Dirty(ent); } 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); } private void OnAudioShutdown(Entity ent, ref ComponentShutdown args) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (comp.AudioStream == ent.Owner) { var next = SongQueueDequeue((uid, comp)); if (next == null) { Stop((uid, comp)); continue; } OnJukeboxSelected(uid, comp, next.Value); OnJukeboxPlay(uid, comp); } } } private void Stop(Entity entity) { Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped); Dirty(entity); } private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args) { OnJukeboxSelected(uid, component, args.SongId); } 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); } } } } 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 OnJukeboxPlay(EntityUid uid, JukeboxComponent component) { 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; if (component.AudioStream != null) { AddComp(component.AudioStream.Value); } Dirty(uid, component); } } private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, ProtoId songId) { component.SelectedSongId = songId; DirectSetVisualState(uid, JukeboxVisualState.Select); component.Selecting = true; component.AudioStream = Audio.Stop(component.AudioStream); Dirty(uid, component); } 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); } private ProtoId? SongQueueDequeue(Entity ent) { if (ent.Comp.SongIdQueue.Count == 0) return null; var next = ent.Comp.SongIdQueue.Dequeue(); return next; } }