]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Fix replay spectating bugs (#21573)
authorLeon Friedrich <60421075+ElectroJr@users.noreply.github.com>
Sat, 11 Nov 2023 06:45:46 +0000 (17:45 +1100)
committerGitHub <noreply@github.com>
Sat, 11 Nov 2023 06:45:46 +0000 (23:45 -0700)
Content.Client/Replay/ContentReplayPlaybackManager.cs
Content.Client/Replay/Spectator/ReplaySpectatorSystem.Movement.cs
Content.Client/Replay/Spectator/ReplaySpectatorSystem.Position.cs
Content.Client/Replay/Spectator/ReplaySpectatorSystem.Spectate.cs
Content.Client/Replay/Spectator/ReplaySpectatorSystem.cs
Content.Replay/Menu/ReplayMainMenu.cs
Content.Shared/Actions/SharedActionsSystem.cs

index cbb511725523f74de1ac6f935e17cfbce2029263..2b4e8ddc7dca8da51755b701461d625f236d03e8 100644 (file)
@@ -40,6 +40,7 @@ public sealed class ContentReplayPlaybackManager
     [Dependency] private readonly IClientConGroupController _conGrp = default!;
     [Dependency] private readonly IClientAdminManager _adminMan = default!;
     [Dependency] private readonly IPlayerManager _player = default!;
+    [Dependency] private readonly IBaseClient _client = default!;
 
     /// <summary>
     /// UI state to return to when stopping a replay or loading fails.
@@ -87,6 +88,9 @@ public sealed class ContentReplayPlaybackManager
             _stateMan.RequestStateChange<LauncherConnecting>().SetDisconnected();
         else
             _stateMan.RequestStateChange<MainScreen>();
+
+        if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
+            _client.StopSinglePlayer();
     }
 
     private void OnCheckpointReset()
index d48a1eab46676caaa9c21865d993bca4d858e783..e7d01713e590186848e878ec930f660d3734510c 100644 (file)
@@ -44,7 +44,7 @@ public sealed partial class ReplaySpectatorSystem
         if (_replayPlayback.Replay == null)
             return;
 
-        if (_player.LocalPlayer?.ControlledEntity is not { } player)
+        if (_player.LocalEntity is not { } player)
             return;
 
         if (Direction == DirectionFlag.None)
@@ -99,7 +99,7 @@ public sealed partial class ReplaySpectatorSystem
         var worldVec = parentRotation.RotateVec(localVec);
         var speed = CompOrNull<MovementSpeedModifierComponent>(player)?.BaseSprintSpeed ?? DefaultSpeed;
         var delta = worldVec * frameTime * speed;
-        _transform.SetWorldPositionRotation(xform, pos + delta, delta.ToWorldAngle());
+        _transform.SetWorldPositionRotation(player, pos + delta, delta.ToWorldAngle(), xform);
     }
 
     private sealed class MoverHandler : InputCmdHandler
index 6041d87317ce952f46ae097431e67b341630d14f..2ee7e30ec9a239e5d32021c912fd633743f16459 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Shared.Movement.Components;
+using Robust.Shared.GameStates;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Network;
@@ -25,7 +26,7 @@ public sealed partial class ReplaySpectatorSystem
         /// <summary>
         /// The player that was originally controlling <see cref="Entity"/>
         /// </summary>
-        public NetUserId? Controller;
+        public NetUserId Controller;
 
         public (EntityCoordinates Coords, Angle Rot)? Local;
         public (EntityCoordinates Coords, Angle Rot)? World;
@@ -35,27 +36,17 @@ public sealed partial class ReplaySpectatorSystem
     public SpectatorData GetSpectatorData()
     {
         var data = new SpectatorData();
-
-        if (_player.LocalPlayer?.ControlledEntity is not { } player)
+        if (_player.LocalEntity is not { } player)
             return data;
 
-        foreach (var session in _player.Sessions)
-        {
-            if (session.UserId == _player.LocalPlayer?.UserId)
-                continue;
-
-            if (session.AttachedEntity == player)
-            {
-                data.Controller = session.UserId;
-                break;
-            }
-        }
+        data.Controller = _player.LocalUser ?? DefaultUser;
 
         if (!TryComp(player, out TransformComponent? xform) || xform.MapUid == null)
             return data;
 
         data.Local = (xform.Coordinates, xform.LocalRotation);
-        data.World = (new(xform.MapUid.Value, xform.WorldPosition), xform.WorldRotation);
+        var (pos, rot) = _transform.GetWorldPositionRotation(player);
+        data.World = (new(xform.MapUid.Value, pos), rot);
 
         if (TryComp(player, out InputMoverComponent? mover))
             data.Eye = (mover.RelativeEntity, mover.TargetRelativeRotation);
@@ -77,18 +68,54 @@ public sealed partial class ReplaySpectatorSystem
         _spectatorData = null;
     }
 
+    private void OnBeforeApplyState((GameState Current, GameState? Next) args)
+    {
+        // Before applying the game state, we want to check to see if a recorded player session is about to
+        // get attached to the entity that we are currently spectating. If it is, then we switch out local session
+        // to the recorded session. I.e., we switch from spectating the entity to spectating the session.
+        // This is required because having multiple sessions attached to a single entity is not currently supported.
+
+        if (_player.LocalUser != DefaultUser)
+            return; // Already spectating some session.
+
+        if (_player.LocalEntity is not {} uid)
+            return;
+
+        var netEnt = GetNetEntity(uid);
+        if (netEnt.IsClientSide())
+            return;
+
+        foreach (var playerState in args.Current.PlayerStates.Value)
+        {
+            if (playerState.ControlledEntity != netEnt)
+                continue;
+
+            if (!_player.TryGetSessionById(playerState.UserId, out var session))
+                session = _player.CreateAndAddSession(playerState.UserId, playerState.Name);
+
+            _player.SetLocalSession(session);
+            break;
+        }
+    }
+
     public void SetSpectatorPosition(SpectatorData data)
     {
         if (_player.LocalSession == null)
             return;
 
-        if (data.Controller != null
-            && _player.SessionsDict.TryGetValue(data.Controller.Value, out var session)
-            && Exists(session.AttachedEntity)
-            && Transform(session.AttachedEntity.Value).MapID != MapId.Nullspace)
+        if (data.Controller != DefaultUser)
         {
-            _player.SetAttachedEntity(_player.LocalSession, session.AttachedEntity);
-            return;
+            // the "local player" is currently set to some recorded session. As long as that session has an entity, we
+            // do nothing here
+            if (_player.TryGetSessionById(data.Controller, out var session)
+                && Exists(session.AttachedEntity))
+            {
+                _player.SetLocalSession(session);
+                return;
+            }
+
+            // Spectated session is no longer valid - return to the client-side session
+            _player.SetLocalSession(_player.GetSessionById(DefaultUser));
         }
 
         if (Exists(data.Entity) && Transform(data.Entity).MapID != MapId.Nullspace)
@@ -114,7 +141,7 @@ public sealed partial class ReplaySpectatorSystem
         }
         else
         {
-            Logger.Error("Failed to find a suitable observer spawn point");
+            Log.Error("Failed to find a suitable observer spawn point");
             return;
         }
 
@@ -153,7 +180,7 @@ public sealed partial class ReplaySpectatorSystem
 
     private void OnTerminating(EntityUid uid, ReplaySpectatorComponent component, ref EntityTerminatingEvent args)
     {
-        if (uid != _player.LocalPlayer?.ControlledEntity)
+        if (uid != _player.LocalEntity)
             return;
 
         var xform = Transform(uid);
@@ -165,7 +192,7 @@ public sealed partial class ReplaySpectatorSystem
 
     private void OnParentChanged(EntityUid uid, ReplaySpectatorComponent component, ref EntParentChangedMessage args)
     {
-        if (uid != _player.LocalPlayer?.ControlledEntity)
+        if (uid != _player.LocalEntity)
             return;
 
         if (args.Transform.MapUid != null || args.OldMapId == MapId.Nullspace)
index 80a8429055f449af484c3f5677e1f6fd0cd10e1f..27b33b84c528c4cb4149793d5fb91f1e82b5cb9d 100644 (file)
@@ -3,6 +3,7 @@ using Content.Client.Replay.UI;
 using Content.Shared.Verbs;
 using Robust.Shared.Console;
 using Robust.Shared.Map;
+using Robust.Shared.Player;
 using Robust.Shared.Utility;
 
 namespace Content.Client.Replay.Spectator;
@@ -15,19 +16,13 @@ public sealed partial class ReplaySpectatorSystem
         if (_replayPlayback.Replay == null)
             return;
 
-        var verb = new AlternativeVerb
+        ev.Verbs.Add(new AlternativeVerb
         {
             Priority = 100,
-            Act = () =>
-            {
-                SpectateEntity(ev.Target);
-            },
-
+            Act = () => SpectateEntity(ev.Target),
             Text = Loc.GetString("replay-verb-spectate"),
             Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/vv.svg.192dpi.png"))
-        };
-
-        ev.Verbs.Add(verb);
+        });
     }
 
     public void SpectateEntity(EntityUid target)
@@ -35,7 +30,7 @@ public sealed partial class ReplaySpectatorSystem
         if (_player.LocalSession == null)
             return;
 
-        var old = _player.LocalSession.AttachedEntity;
+        var old = _player.LocalEntity;
 
         if (old == target)
         {
@@ -44,8 +39,11 @@ public sealed partial class ReplaySpectatorSystem
             return;
         }
 
-        _player.SetAttachedEntity(_player.LocalSession, target);
         EnsureComp<ReplaySpectatorComponent>(target);
+        if (TryComp(target, out ActorComponent? actor))
+            _player.SetLocalSession(actor.PlayerSession);
+        else
+            _player.SetAttachedEntity(_player.LocalSession, target);
 
         _stateMan.RequestStateChange<ReplaySpectateEntityState>();
         if (old == null)
@@ -59,10 +57,9 @@ public sealed partial class ReplaySpectatorSystem
 
     public TransformComponent SpawnSpectatorGhost(EntityCoordinates coords, bool gridAttach)
     {
-        if (_player.LocalSession == null)
-            throw new InvalidOperationException();
-
-        var old = _player.LocalSession.AttachedEntity;
+        var old = _player.LocalEntity;
+        var session = _player.GetSessionById(DefaultUser);
+        _player.SetLocalSession(session);
 
         var ent = Spawn("ReplayObserver", coords);
         _eye.SetMaxZoom(ent, Vector2.One * 5);
@@ -73,7 +70,7 @@ public sealed partial class ReplaySpectatorSystem
         if (gridAttach)
             _transform.AttachToGridOrMap(ent);
 
-        _player.SetAttachedEntity(_player.LocalSession, ent);
+        _player.SetAttachedEntity(session, ent);
 
         if (old != null)
         {
index c238f5456d25857c878973d8f265fd7d8bf11015..8a3b858720642f757d29533e5db2bd54fcf20c03 100644 (file)
@@ -1,11 +1,11 @@
 using Content.Shared.Movement.Systems;
 using Content.Shared.Verbs;
-using Robust.Client;
 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;
 
@@ -27,13 +27,17 @@ public sealed partial class ReplaySpectatorSystem : EntitySystem
     [Dependency] private readonly IStateManager _stateMan = default!;
     [Dependency] private readonly TransformSystem _transform = default!;
     [Dependency] private readonly SharedMoverController _mover = default!;
-    [Dependency] private readonly IBaseClient _client = default!;
     [Dependency] private readonly SharedContentEyeSystem _eye = default!;
     [Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
 
     private SpectatorData? _spectatorData;
     public const string SpectateCmd = "replay_spectate";
 
+    /// <summary>
+    /// User Id that corresponds to the local user in a single-player game.
+    /// </summary>
+    public static readonly NetUserId DefaultUser = default;
+
     public override void Initialize()
     {
         base.Initialize();
@@ -49,6 +53,7 @@ public sealed partial class ReplaySpectatorSystem : EntitySystem
         _replayPlayback.AfterSetTick += OnAfterSetTick;
         _replayPlayback.ReplayPlaybackStarted += OnPlaybackStarted;
         _replayPlayback.ReplayPlaybackStopped += OnPlaybackStopped;
+        _replayPlayback.BeforeApplyState += OnBeforeApplyState;
     }
 
     public override void Shutdown()
@@ -58,6 +63,7 @@ public sealed partial class ReplaySpectatorSystem : EntitySystem
         _replayPlayback.AfterSetTick -= OnAfterSetTick;
         _replayPlayback.ReplayPlaybackStarted -= OnPlaybackStarted;
         _replayPlayback.ReplayPlaybackStopped -= OnPlaybackStopped;
+        _replayPlayback.BeforeApplyState -= OnBeforeApplyState;
     }
 
     private void OnPlaybackStarted(MappingDataNode yamlMappingNode, List<object> objects)
index 5792c1bc018e248f4cd82cf0db915deb55fcdad1..8bd99f82fb261539ca981d0677c7b05b56f14a51 100644 (file)
@@ -47,7 +47,7 @@ public sealed class ReplayMainScreen : State
         _mainMenuControl.QuitButton.OnPressed += QuitButtonPressed;
         _mainMenuControl.OptionsButton.OnPressed += OptionsButtonPressed;
         _mainMenuControl.FolderButton.OnPressed += OnFolderPressed;
-        _mainMenuControl.LoadButton.OnPressed += OnLoadpressed;
+        _mainMenuControl.LoadButton.OnPressed += OnLoadPressed;
 
         _directory = new ResPath(_cfg.GetCVar(CVars.ReplayDirectory)).ToRootedPath();
         RefreshReplays();
@@ -205,7 +205,7 @@ public sealed class ReplayMainScreen : State
         _resMan.UserData.OpenOsWindow(_directory);
     }
 
-    private void OnLoadpressed(BaseButton.ButtonEventArgs obj)
+    private void OnLoadPressed(BaseButton.ButtonEventArgs obj)
     {
         if (_selected.HasValue)
         {
index 666110575a84e62ba50f03260e2ffa63c952465e..46ef2058ad0c7ebbfa94295f019f05e75f05adfe 100644 (file)
@@ -647,7 +647,9 @@ public abstract class SharedActionsSystem : EntitySystem
 
         if (action.AttachedEntity != performer)
         {
-            DebugTools.Assert(!Resolve(performer, ref comp, false) || !comp.Actions.Contains(actionId.Value));
+            DebugTools.Assert(!Resolve(performer, ref comp, false)
+                              || comp.LifeStage >= ComponentLifeStage.Stopping
+                              || !comp.Actions.Contains(actionId.Value));
 
             if (!GameTiming.ApplyingState)
                 Log.Error($"Attempted to remove an action {ToPrettyString(actionId)} from an entity that it was never attached to: {ToPrettyString(performer)}");