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.Player; using Robust.Shared.Prototypes; 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(OnJukeboxSelected); SubscribeLocalEvent(OnJukeboxPlay); SubscribeLocalEvent(OnJukeboxPause); SubscribeLocalEvent(OnJukeboxStop); SubscribeLocalEvent(OnJukeboxSetTime); SubscribeLocalEvent(OnComponentInit); SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnPowerChanged); } private void OnComponentInit(Entity ent, ref ComponentInit args) { if (HasComp(ent)) { TryUpdateVisualState(ent.AsNullable()); } } private void OnJukeboxPlay(Entity ent, ref JukeboxPlayingMessage args) { TryPlay(ent.AsNullable()); } private void OnJukeboxPause(Entity ent, ref JukeboxPauseMessage args) { Pause(ent.AsNullable()); } private void OnJukeboxSetTime(Entity ent, ref JukeboxSetTimeMessage args) { if (TryComp(args.Actor, out ActorComponent? actorComp)) { var offset = actorComp.PlayerSession.Channel.Ping * 1.5f / 1000f; SetTime(ent.AsNullable(), args.SongTime + offset); } } private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) { TryUpdateVisualState(entity.AsNullable()); if (!this.IsPowered(entity.Owner, EntityManager)) { Stop(entity.AsNullable()); } } private void OnJukeboxStop(Entity entity, ref JukeboxStopMessage args) { Stop(entity.AsNullable()); } private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args) { SetSelectedTrack((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(Entity ent, ref ComponentShutdown args) { ent.Comp.AudioStream = Audio.Stop(ent.Comp.AudioStream); } private void DirectSetVisualState(EntityUid uid, JukeboxVisualState state) { _appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, state); } private void TryUpdateVisualState(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return; var finalState = JukeboxVisualState.On; if (!this.IsPowered(ent, EntityManager)) { finalState = JukeboxVisualState.Off; } _appearanceSystem.SetData(ent, JukeboxVisuals.VisualState, finalState); } /// /// Set the selected track of the jukebox to the specified prototype. /// public void SetSelectedTrack(Entity ent, ProtoId track) { if (!Resolve(ent, ref ent.Comp)) return; if (!Audio.IsPlaying(ent.Comp.AudioStream)) { ent.Comp.SelectedSongId = track; DirectSetVisualState(ent, JukeboxVisualState.Select); ent.Comp.Selecting = true; ent.Comp.AudioStream = Audio.Stop(ent.Comp.AudioStream); Dirty(ent); } } /// /// Attempts to play the jukebox's current selected track. /// /// false if no track is selected or the track prototype cannot be found, otherwise true. public bool TryPlay(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return false; if (Exists(ent.Comp.AudioStream)) { Audio.SetState(ent.Comp.AudioStream, AudioState.Playing); } else { if (string.IsNullOrEmpty(ent.Comp.SelectedSongId) || !_protoManager.Resolve(ent.Comp.SelectedSongId, out var jukeboxProto)) { return false; } ent.Comp.AudioStream = Audio.PlayPvs(jukeboxProto.Path, ent, AudioParams.Default.WithMaxDistance(10f))?.Entity; Dirty(ent); } return true; } /// /// Stops any track that may currently be playing. /// public void Stop(Entity entity) { if (!Resolve(entity, ref entity.Comp, logMissing: false)) return; Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped); } /// /// Pauses any track that may currently be playing. /// public void Pause(Entity entity) { if (!Resolve(entity, ref entity.Comp, logMissing: false)) return; Audio.SetState(entity.Comp.AudioStream, AudioState.Paused); } /// /// Sets the playback position within the current audio track. /// /// /// If setting based on user input, you may need to compensate for the player's ping. /// public void SetTime(Entity entity, float songTime) { if (!Resolve(entity, ref entity.Comp)) return; Audio.SetPlaybackPosition(entity.Comp.AudioStream, songTime); } }