using Content.Shared.Movement.Systems; using Content.Shared.Verbs; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Client.Replays.Playback; using Robust.Client.State; using Robust.Shared.Console; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Serialization.Markdown.Mapping; namespace Content.Client.Replay.Spectator; /// /// This system handles spawning replay observer ghosts and maintaining their positions when traveling through time. /// It also blocks most normal interactions, just in case. /// /// /// E.g., if an observer is on a grid, and then jumps forward or backward in time to a point where the grid does not /// exist, where should the observer go? This attempts to maintain their position and eye rotation or just re-spawns /// them as needed. /// public sealed partial class ReplaySpectatorSystem : EntitySystem { [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IConsoleHost _conHost = default!; [Dependency] private readonly IStateManager _stateMan = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedContentEyeSystem _eye = default!; [Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!; private SpectatorData? _spectatorData; public const string SpectateCmd = "replay_spectate"; /// /// User Id that corresponds to the local user in a single-player game. /// public static readonly NetUserId DefaultUser = default; public override void Initialize() { base.Initialize(); SubscribeLocalEvent>(OnGetAlternativeVerbs); SubscribeLocalEvent(OnTerminating); SubscribeLocalEvent(OnDetached); SubscribeLocalEvent(OnParentChanged); InitializeBlockers(); _replayPlayback.BeforeSetTick += OnBeforeSetTick; _replayPlayback.AfterSetTick += OnAfterSetTick; _replayPlayback.ReplayPlaybackStarted += OnPlaybackStarted; _replayPlayback.ReplayPlaybackStopped += OnPlaybackStopped; _replayPlayback.BeforeApplyState += OnBeforeApplyState; } public override void Shutdown() { base.Shutdown(); _replayPlayback.BeforeSetTick -= OnBeforeSetTick; _replayPlayback.AfterSetTick -= OnAfterSetTick; _replayPlayback.ReplayPlaybackStarted -= OnPlaybackStarted; _replayPlayback.ReplayPlaybackStopped -= OnPlaybackStopped; _replayPlayback.BeforeApplyState -= OnBeforeApplyState; } private void OnPlaybackStarted(MappingDataNode yamlMappingNode, List objects) { InitializeMovement(); _conHost.RegisterCommand(SpectateCmd, Loc.GetString("cmd-replay-spectate-desc"), Loc.GetString("cmd-replay-spectate-help"), SpectateCommand, SpectateCompletions); if (_replayPlayback.TryGetRecorderEntity(out var recorder)) SpectateEntity(recorder.Value); else SetSpectatorPosition(default); } private void OnPlaybackStopped() { ShutdownMovement(); _conHost.UnregisterCommand(SpectateCmd); } }