From 23df411839151169c68e6959802343fe3bbf85df Mon Sep 17 00:00:00 2001
From: Ed <96445749+TheShuEd@users.noreply.github.com>
Date: Tue, 12 May 2026 16:28:55 +0300
Subject: [PATCH 1/4] file-scoping Player manager
---
Robust.Server/Player/FilterSystem.cs | 20 +-
Robust.Server/Player/PlayerManager.cs | 296 +++++++++++++-------------
2 files changed, 156 insertions(+), 160 deletions(-)
diff --git a/Robust.Server/Player/FilterSystem.cs b/Robust.Server/Player/FilterSystem.cs
index f89ce972912..207e9f6abbe 100644
--- a/Robust.Server/Player/FilterSystem.cs
+++ b/Robust.Server/Player/FilterSystem.cs
@@ -1,20 +1,18 @@
-using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
-namespace Robust.Server.Player
+namespace Robust.Server.Player;
+
+internal sealed class FilterSystem : SharedFilterSystem
{
- internal sealed class FilterSystem : SharedFilterSystem
+ public override Filter FromEntities(Filter filter, params EntityUid[] entities)
{
- public override Filter FromEntities(Filter filter, params EntityUid[] entities)
+ foreach (var uid in entities)
{
- foreach (var uid in entities)
- {
- if (TryComp(uid, out ActorComponent? actor))
- filter.AddPlayer(actor.PlayerSession);
- }
-
- return filter;
+ if (TryComp(uid, out ActorComponent? actor))
+ filter.AddPlayer(actor.PlayerSession);
}
+
+ return filter;
}
}
diff --git a/Robust.Server/Player/PlayerManager.cs b/Robust.Server/Player/PlayerManager.cs
index b46f18a4bc7..07323cfc950 100644
--- a/Robust.Server/Player/PlayerManager.cs
+++ b/Robust.Server/Player/PlayerManager.cs
@@ -16,194 +16,192 @@
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-namespace Robust.Server.Player
+namespace Robust.Server.Player;
+
+///
+/// This class will manage connected player sessions.
+///
+internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManager
{
- ///
- /// This class will manage connected player sessions.
- ///
- internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManager
- {
- private static readonly Gauge PlayerCountMetric = Metrics
- .CreateGauge("robust_player_count", "Number of players on the server.");
+ private static readonly Gauge PlayerCountMetric = Metrics
+ .CreateGauge("robust_player_count", "Number of players on the server.");
- [Dependency] private IBaseServer _baseServer = default!;
- [Dependency] private IGameTiming _timing = default!;
- [Dependency] private IServerNetManager _network = default!;
- [Dependency] private IReflectionManager _reflectionManager = default!;
- [Dependency] private IEntityManager _entityManager = default!;
- [Dependency] private IServerNetConfigurationManager _cfg = default!;
+ [Dependency] private IBaseServer _baseServer = default!;
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private IServerNetManager _network = default!;
+ [Dependency] private IReflectionManager _reflectionManager = default!;
+ [Dependency] private IEntityManager _entityManager = default!;
+ [Dependency] private IServerNetConfigurationManager _cfg = default!;
- public BoundKeyMap KeyMap { get; private set; } = default!;
+ public BoundKeyMap KeyMap { get; private set; } = default!;
- ///
- public override void Initialize(int maxPlayers)
- {
- base.Initialize(maxPlayers);
- KeyMap = new BoundKeyMap(_reflectionManager);
- KeyMap.PopulateKeyFunctionsMap();
+ ///
+ public override void Initialize(int maxPlayers)
+ {
+ base.Initialize(maxPlayers);
+ KeyMap = new BoundKeyMap(_reflectionManager);
+ KeyMap.PopulateKeyFunctionsMap();
- _network.RegisterNetMessage(HandlePlayerListReq);
- _network.RegisterNetMessage();
- _network.RegisterNetMessage();
+ _network.RegisterNetMessage(HandlePlayerListReq);
+ _network.RegisterNetMessage();
+ _network.RegisterNetMessage();
- _network.Connecting += OnConnecting;
- _network.Connected += NewSession;
- _network.Disconnect += EndSession;
- }
+ _network.Connecting += OnConnecting;
+ _network.Connected += NewSession;
+ _network.Disconnect += EndSession;
+ }
- public override void Shutdown()
- {
- base.Shutdown();
- KeyMap = default!;
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ KeyMap = default!;
- _network.Connecting -= OnConnecting;
- _network.Connected -= NewSession;
- _network.Disconnect -= EndSession;
- }
+ _network.Connecting -= OnConnecting;
+ _network.Connected -= NewSession;
+ _network.Disconnect -= EndSession;
+ }
- private Task OnConnecting(NetConnectingArgs args)
+ private Task OnConnecting(NetConnectingArgs args)
+ {
+ if (PlayerCount >= _baseServer.MaxPlayers)
{
- if (PlayerCount >= _baseServer.MaxPlayers)
- {
- args.Deny("The server is full.");
- }
-
- return Task.CompletedTask;
+ args.Deny("The server is full.");
}
- ///
- /// Creates a new session for a client.
- ///
- ///
- ///
- private void NewSession(object? sender, NetChannelArgs args)
- {
- CreateAndAddSession(args.Channel);
- PlayerCountMetric.Set(PlayerCount);
- // Synchronize base time.
- var msgTimeBase = new MsgSyncTimeBase();
- (msgTimeBase.Time, msgTimeBase.Tick) = _timing.TimeBase;
- _network.ServerSendMessage(msgTimeBase, args.Channel);
-
- _cfg.SyncConnectingClient(args.Channel);
- }
+ return Task.CompletedTask;
+ }
- private void EndSession(object? sender, NetChannelArgs args)
- {
- EndSession(args.Channel.UserId);
- }
+ ///
+ /// Creates a new session for a client.
+ ///
+ ///
+ ///
+ private void NewSession(object? sender, NetChannelArgs args)
+ {
+ CreateAndAddSession(args.Channel);
+ PlayerCountMetric.Set(PlayerCount);
+ // Synchronize base time.
+ var msgTimeBase = new MsgSyncTimeBase();
+ (msgTimeBase.Time, msgTimeBase.Tick) = _timing.TimeBase;
+ _network.ServerSendMessage(msgTimeBase, args.Channel);
+
+ _cfg.SyncConnectingClient(args.Channel);
+ }
- ///
- /// Ends a clients session, and disconnects them.
- ///
- internal void EndSession(NetUserId user)
- {
- if (!TryGetSessionById(user, out var session))
- return;
+ private void EndSession(object? sender, NetChannelArgs args)
+ {
+ EndSession(args.Channel.UserId);
+ }
- RemoveSession(session.UserId);
- SetStatus(session, SessionStatus.Disconnected);
- SetAttachedEntity(session, null, out _, true);
+ ///
+ /// Ends a clients session, and disconnects them.
+ ///
+ internal void EndSession(NetUserId user)
+ {
+ if (!TryGetSessionById(user, out var session))
+ return;
- var viewSys = EntManager.System();
- foreach (var eye in session.ViewSubscriptions.ToArray())
- {
- viewSys.RemoveViewSubscriber(eye, session);
- }
+ RemoveSession(session.UserId);
+ SetStatus(session, SessionStatus.Disconnected);
+ SetAttachedEntity(session, null, out _, true);
- PlayerCountMetric.Set(PlayerCount);
- Dirty();
+ var viewSys = EntManager.System();
+ foreach (var eye in session.ViewSubscriptions.ToArray())
+ {
+ viewSys.RemoveViewSubscriber(eye, session);
}
- private void HandlePlayerListReq(MsgPlayerListReq message)
- {
- var channel = message.MsgChannel;
- var session = (CommonSession)GetSessionByChannel(channel);
- session.InitialPlayerListReqDone = true;
+ PlayerCountMetric.Set(PlayerCount);
+ Dirty();
+ }
- if (!session.InitialResourcesDone)
- return;
+ private void HandlePlayerListReq(MsgPlayerListReq message)
+ {
+ var channel = message.MsgChannel;
+ var session = (CommonSession)GetSessionByChannel(channel);
+ session.InitialPlayerListReqDone = true;
- SendPlayerList(channel, session);
- }
+ if (!session.InitialResourcesDone)
+ return;
- public void MarkPlayerResourcesSent(INetChannel channel)
- {
- var session = (CommonSession)GetSessionByChannel(channel);
- session.InitialResourcesDone = true;
+ SendPlayerList(channel, session);
+ }
- if (!session.InitialPlayerListReqDone)
- return;
+ public void MarkPlayerResourcesSent(INetChannel channel)
+ {
+ var session = (CommonSession)GetSessionByChannel(channel);
+ session.InitialResourcesDone = true;
- SendPlayerList(channel, session);
- }
+ if (!session.InitialPlayerListReqDone)
+ return;
- private void SendPlayerList(INetChannel channel, CommonSession session)
- {
- var players = Sessions;
- var netMsg = new MsgPlayerList();
+ SendPlayerList(channel, session);
+ }
- // client session is complete, set their status accordingly.
- // This is done before the packet is built, so that the client
- // can see themselves Connected.
- session.ConnectedTime = DateTime.UtcNow;
- SetStatus(session, SessionStatus.Connected);
+ private void SendPlayerList(INetChannel channel, CommonSession session)
+ {
+ var players = Sessions;
+ var netMsg = new MsgPlayerList();
- var list = new List();
- foreach (var client in players)
- {
- var info = new SessionState
- {
- UserId = client.UserId,
- Name = client.Name,
- Status = client.Status
- };
- list.Add(info);
- }
- netMsg.Plyrs = list;
-
- channel.SendMessage(netMsg);
- }
+ // client session is complete, set their status accordingly.
+ // This is done before the packet is built, so that the client
+ // can see themselves Connected.
+ session.ConnectedTime = DateTime.UtcNow;
+ SetStatus(session, SessionStatus.Connected);
- public override bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
+ var list = new List();
+ foreach (var client in players)
{
- if (!_entityManager.TryGetComponent(uid, out ActorComponent? actor))
+ var info = new SessionState
{
- session = null;
- return false;
- }
-
- session = actor.PlayerSession;
- return true;
+ UserId = client.UserId,
+ Name = client.Name,
+ Status = client.Status
+ };
+ list.Add(info);
}
+ netMsg.Plyrs = list;
- internal ICommonSession AddDummySession(NetUserId user, string name)
+ channel.SendMessage(netMsg);
+ }
+
+ public override bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
+ {
+ if (!_entityManager.TryGetComponent(uid, out ActorComponent? actor))
{
+ session = null;
+ return false;
+ }
+
+ session = actor.PlayerSession;
+ return true;
+ }
+
+ internal ICommonSession AddDummySession(NetUserId user, string name)
+ {
#if FULL_RELEASE
// Lets not make it completely trivial to fake player counts.
throw new NotSupportedException();
#endif
- Lock.EnterWriteLock();
- DummySession session;
- try
- {
- UserIdMap[name] = user;
- if (!PlayerData.TryGetValue(user, out var data))
- PlayerData[user] = data = new(user, name);
-
- session = new DummySession(user, name, data);
- InternalSessions.Add(user, session);
- }
- finally
- {
- Lock.ExitWriteLock();
- }
-
- UpdateState(session);
+ Lock.EnterWriteLock();
+ DummySession session;
+ try
+ {
+ UserIdMap[name] = user;
+ if (!PlayerData.TryGetValue(user, out var data))
+ PlayerData[user] = data = new(user, name);
- return session;
+ session = new DummySession(user, name, data);
+ InternalSessions.Add(user, session);
}
+ finally
+ {
+ Lock.ExitWriteLock();
+ }
+
+ UpdateState(session);
+
+ return session;
}
}
From 08886c757a5e2c680c5e5933381a9b9ef89734fa Mon Sep 17 00:00:00 2001
From: Ed <96445749+TheShuEd@users.noreply.github.com>
Date: Tue, 12 May 2026 16:30:25 +0300
Subject: [PATCH 2/4] readonly playerManager
---
Robust.Client/Player/PlayerManager.cs | 4 ++--
Robust.Server/Player/PlayerManager.cs | 12 ++++++------
Robust.Shared/Player/SharedPlayerManager.cs | 10 +++++-----
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/Robust.Client/Player/PlayerManager.cs b/Robust.Client/Player/PlayerManager.cs
index 0bc24f35cce..e47d329e837 100644
--- a/Robust.Client/Player/PlayerManager.cs
+++ b/Robust.Client/Player/PlayerManager.cs
@@ -19,8 +19,8 @@ namespace Robust.Client.Player
///
internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManager
{
- [Dependency] private IClientNetManager _network = default!;
- [Dependency] private IBaseClient _client = default!;
+ [Dependency] private readonly IClientNetManager _network = default!;
+ [Dependency] private readonly IBaseClient _client = default!;
///
/// Received player states that had an unknown .
diff --git a/Robust.Server/Player/PlayerManager.cs b/Robust.Server/Player/PlayerManager.cs
index 07323cfc950..b82081ddb64 100644
--- a/Robust.Server/Player/PlayerManager.cs
+++ b/Robust.Server/Player/PlayerManager.cs
@@ -27,12 +27,12 @@ internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManage
private static readonly Gauge PlayerCountMetric = Metrics
.CreateGauge("robust_player_count", "Number of players on the server.");
- [Dependency] private IBaseServer _baseServer = default!;
- [Dependency] private IGameTiming _timing = default!;
- [Dependency] private IServerNetManager _network = default!;
- [Dependency] private IReflectionManager _reflectionManager = default!;
- [Dependency] private IEntityManager _entityManager = default!;
- [Dependency] private IServerNetConfigurationManager _cfg = default!;
+ [Dependency] private readonly IBaseServer _baseServer = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IServerNetManager _network = default!;
+ [Dependency] private readonly IReflectionManager _reflectionManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IServerNetConfigurationManager _cfg = default!;
public BoundKeyMap KeyMap { get; private set; } = default!;
diff --git a/Robust.Shared/Player/SharedPlayerManager.cs b/Robust.Shared/Player/SharedPlayerManager.cs
index 2a78bb9e7dc..54ab6686fd9 100644
--- a/Robust.Shared/Player/SharedPlayerManager.cs
+++ b/Robust.Shared/Player/SharedPlayerManager.cs
@@ -11,11 +11,11 @@ namespace Robust.Shared.Player;
internal abstract partial class SharedPlayerManager : ISharedPlayerManager
{
- [Dependency] protected IEntityManager EntManager = default!;
- [Dependency] protected IComponentFactory Factory = default!;
- [Dependency] protected ILogManager LogMan = default!;
- [Dependency] protected IGameTiming Timing = default!;
- [Dependency] private INetManager _netMan = default!;
+ [Dependency] protected readonly IEntityManager EntManager = default!;
+ [Dependency] protected readonly IComponentFactory Factory = default!;
+ [Dependency] protected readonly ILogManager LogMan = default!;
+ [Dependency] protected readonly IGameTiming Timing = default!;
+ [Dependency] private readonly INetManager _netMan = default!;
protected ISawmill Sawmill = default!;
From 65bdc7d938380d1d2382cf70229eac82292f433c Mon Sep 17 00:00:00 2001
From: Ed <96445749+TheShuEd@users.noreply.github.com>
Date: Tue, 12 May 2026 16:30:39 +0300
Subject: [PATCH 3/4] Update PlayerManager.cs
---
Robust.Client/Player/PlayerManager.cs | 479 +++++++++++++-------------
1 file changed, 239 insertions(+), 240 deletions(-)
diff --git a/Robust.Client/Player/PlayerManager.cs b/Robust.Client/Player/PlayerManager.cs
index e47d329e837..cdf83d4e0c6 100644
--- a/Robust.Client/Player/PlayerManager.cs
+++ b/Robust.Client/Player/PlayerManager.cs
@@ -10,312 +10,311 @@
using Robust.Shared.Player;
using Robust.Shared.Utility;
-namespace Robust.Client.Player
+namespace Robust.Client.Player;
+
+///
+/// Here's the player controller. This will handle attaching GUIs and input to controllable things.
+/// Why not just attach the inputs directly? It's messy! This makes the whole thing nicely encapsulated.
+/// This class also communicates with the server to let the server control what entity it is attached to.
+///
+internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManager
{
+ [Dependency] private readonly IClientNetManager _network = default!;
+ [Dependency] private readonly IBaseClient _client = default!;
+
///
- /// Here's the player controller. This will handle attaching GUIs and input to controllable things.
- /// Why not just attach the inputs directly? It's messy! This makes the whole thing nicely encapsulated.
- /// This class also communicates with the server to let the server control what entity it is attached to.
+ /// Received player states that had an unknown .
///
- internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManager
- {
- [Dependency] private readonly IClientNetManager _network = default!;
- [Dependency] private readonly IBaseClient _client = default!;
+ private Dictionary _pendingStates = new();
+ private List _pending = new();
- ///
- /// Received player states that had an unknown .
- ///
- private Dictionary _pendingStates = new();
- private List _pending = new();
-
- ///
- public override ICommonSession[] NetworkedSessions
+ ///
+ public override ICommonSession[] NetworkedSessions
+ {
+ get
{
- get
- {
- return LocalSession != null
- ? new[] { LocalSession }
- : Array.Empty();
- }
+ return LocalSession != null
+ ? new[] { LocalSession }
+ : Array.Empty();
}
+ }
- ///
- public override int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? -1;
+ ///
+ public override int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? -1;
- public LocalPlayer? LocalPlayer { get; private set; }
+ public LocalPlayer? LocalPlayer { get; private set; }
- public event Action? LocalStatusChanged;
- public event Action? PlayerListUpdated;
- public event Action? LocalPlayerDetached;
- public event Action? LocalPlayerAttached;
- public event Action<(ICommonSession? Old, ICommonSession? New)>? LocalSessionChanged;
+ public event Action? LocalStatusChanged;
+ public event Action? PlayerListUpdated;
+ public event Action? LocalPlayerDetached;
+ public event Action? LocalPlayerAttached;
+ public event Action<(ICommonSession? Old, ICommonSession? New)>? LocalSessionChanged;
- ///
- public override void Initialize(int maxPlayers)
+ ///
+ public override void Initialize(int maxPlayers)
+ {
+ base.Initialize(maxPlayers);
+ _network.RegisterNetMessage();
+ _network.RegisterNetMessage(HandlePlayerList);
+ PlayerStatusChanged += StatusChanged;
+ }
+
+ private void StatusChanged(object? sender, SessionStatusEventArgs e)
+ {
+ if (e.Session == LocalPlayer?.Session)
+ LocalStatusChanged?.Invoke(e);
+ }
+
+ public void SetupSinglePlayer(string name)
+ {
+ if (LocalSession != null)
+ throw new InvalidOperationException($"Player manager already running?");
+
+ var session = CreateAndAddSession(default, name);
+ session.ClientSide = true;
+ SetLocalSession(session);
+ Startup();
+ PlayerListUpdated?.Invoke();
+ }
+
+ public void SetupMultiplayer(INetChannel channel)
+ {
+ if (LocalSession != null)
+ throw new InvalidOperationException($"Player manager already running?");
+
+ SetLocalSession(CreateAndAddSession(channel));
+ Startup();
+ _network.ClientSendMessage(new MsgPlayerListReq());
+ }
+
+ public void SetLocalSession(ICommonSession? session)
+ {
+ if (session == LocalSession)
+ return;
+
+ var old = LocalSession;
+
+ if (old?.AttachedEntity is { } oldUid)
{
- base.Initialize(maxPlayers);
- _network.RegisterNetMessage();
- _network.RegisterNetMessage(HandlePlayerList);
- PlayerStatusChanged += StatusChanged;
+ LocalSession = null;
+ LocalPlayer = null;
+ Sawmill.Info($"Detaching local player from {EntManager.ToPrettyString(oldUid)}.");
+ EntManager.EventBus.RaiseLocalEvent(oldUid, new LocalPlayerDetachedEvent(oldUid), true);
+ LocalPlayerDetached?.Invoke(oldUid);
}
- private void StatusChanged(object? sender, SessionStatusEventArgs e)
+ LocalSession = session;
+ LocalPlayer = session == null ? null : new LocalPlayer(session);
+ Sawmill.Info($"Changing local session from {old?.ToString() ?? "null"} to {session?.ToString() ?? "null"}.");
+ LocalSessionChanged?.Invoke((old, LocalSession));
+
+ if (session?.AttachedEntity is { } newUid)
{
- if (e.Session == LocalPlayer?.Session)
- LocalStatusChanged?.Invoke(e);
+ Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(newUid)}.");
+ EntManager.EventBus.RaiseLocalEvent(newUid, new LocalPlayerAttachedEvent(newUid), true);
+ LocalPlayerAttached?.Invoke(newUid);
}
+ }
- public void SetupSinglePlayer(string name)
- {
- if (LocalSession != null)
- throw new InvalidOperationException($"Player manager already running?");
+ ///
+ public override void Shutdown()
+ {
+ SetAttachedEntity(LocalSession, null, out _);
+ LocalPlayer = null;
+ LocalSession = null;
+ _pendingStates.Clear();
+ base.Shutdown();
+ PlayerListUpdated?.Invoke();
+ }
- var session = CreateAndAddSession(default, name);
- session.ClientSide = true;
- SetLocalSession(session);
- Startup();
- PlayerListUpdated?.Invoke();
- }
+ public override bool SetAttachedEntity(ICommonSession? session, EntityUid? uid, out ICommonSession? kicked, bool force = false)
+ {
+ kicked = null;
+ if (session == null)
+ return false;
- public void SetupMultiplayer(INetChannel channel)
- {
- if (LocalSession != null)
- throw new InvalidOperationException($"Player manager already running?");
+ if (session.AttachedEntity == uid)
+ return true;
+
+ var old = session.AttachedEntity;
+ if (!base.SetAttachedEntity(session, uid, out kicked, force))
+ return false;
+
+ if (session != LocalSession)
+ return true;
- SetLocalSession(CreateAndAddSession(channel));
- Startup();
- _network.ClientSendMessage(new MsgPlayerListReq());
+ if (old.HasValue)
+ {
+ Sawmill.Info($"Detaching local player from {EntManager.ToPrettyString(old)}.");
+ EntManager.EventBus.RaiseLocalEvent(old.Value, new LocalPlayerDetachedEvent(old.Value), true);
+ LocalPlayerDetached?.Invoke(old.Value);
}
- public void SetLocalSession(ICommonSession? session)
+ if (uid == null)
{
- if (session == LocalSession)
- return;
+ Sawmill.Info($"Local player is no longer attached to any entity.");
+ return true;
+ }
- var old = LocalSession;
+ if (!EntManager.EntityExists(uid))
+ {
+ Sawmill.Error($"Attempted to attach player to non-existent entity {uid}!");
+ return true;
+ }
- if (old?.AttachedEntity is { } oldUid)
- {
- LocalSession = null;
- LocalPlayer = null;
- Sawmill.Info($"Detaching local player from {EntManager.ToPrettyString(oldUid)}.");
- EntManager.EventBus.RaiseLocalEvent(oldUid, new LocalPlayerDetachedEvent(oldUid), true);
- LocalPlayerDetached?.Invoke(oldUid);
- }
+ if (!EntManager.HasComponent(uid.Value))
+ {
+ if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
+ Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
+ var eye = Factory.GetComponent();
+ eye.NetSyncEnabled = false;
+ EntManager.AddComponent(uid.Value, eye);
+ }
- LocalSession = session;
- LocalPlayer = session == null ? null : new LocalPlayer(session);
- Sawmill.Info($"Changing local session from {old?.ToString() ?? "null"} to {session?.ToString() ?? "null"}.");
- LocalSessionChanged?.Invoke((old, LocalSession));
+ Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(uid)}.");
+ EntManager.EventBus.RaiseLocalEvent(uid.Value, new LocalPlayerAttachedEvent(uid.Value), true);
+ LocalPlayerAttached?.Invoke(uid.Value);
+ return true;
+ }
- if (session?.AttachedEntity is { } newUid)
- {
- Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(newUid)}.");
- EntManager.EventBus.RaiseLocalEvent(newUid, new LocalPlayerAttachedEvent(newUid), true);
- LocalPlayerAttached?.Invoke(newUid);
- }
- }
+ public void ApplyPlayerStates(IReadOnlyCollection list)
+ {
+ var dirty = ApplyStates(list, true);
- ///
- public override void Shutdown()
+ if (_pendingStates.Count == 0)
{
- SetAttachedEntity(LocalSession, null, out _);
- LocalPlayer = null;
- LocalSession = null;
+ // This is somewhat inefficient as it might try to re-apply states that failed just a moment ago.
+ _pending.Clear();
+ _pending.AddRange(_pendingStates.Values);
_pendingStates.Clear();
- base.Shutdown();
- PlayerListUpdated?.Invoke();
+ dirty |= ApplyStates(_pending, false);
}
- public override bool SetAttachedEntity(ICommonSession? session, EntityUid? uid, out ICommonSession? kicked, bool force = false)
- {
- kicked = null;
- if (session == null)
- return false;
+ if (dirty)
+ PlayerListUpdated?.Invoke();
+ }
- if (session.AttachedEntity == uid)
- return true;
+ private bool ApplyStates(IReadOnlyCollection list, bool fullList)
+ {
+ if (list.Count == 0)
+ return false;
- var old = session.AttachedEntity;
- if (!base.SetAttachedEntity(session, uid, out kicked, force))
- return false;
+ DebugTools.Assert(_network.IsConnected || _client.RunLevel == ClientRunLevel.SinglePlayerGame // replays use state application.
+ , "Received player state without being connected?");
+ DebugTools.Assert(LocalSession != null, "Received player state before Session finished setup.");
- if (session != LocalSession)
- return true;
+ var state = list.FirstOrDefault(s => s.UserId == LocalSession.UserId);
- if (old.HasValue)
+ bool dirty = false;
+ if (state != null)
+ {
+ dirty = true;
+ if (!EntManager.TryGetEntity(state.ControlledEntity, out var uid)
+ && state.ControlledEntity is { Valid: true })
{
- Sawmill.Info($"Detaching local player from {EntManager.ToPrettyString(old)}.");
- EntManager.EventBus.RaiseLocalEvent(old.Value, new LocalPlayerDetachedEvent(old.Value), true);
- LocalPlayerDetached?.Invoke(old.Value);
+ Sawmill.Error($"Received player state for local player with an unknown net entity!");
+ _pendingStates[state.UserId] = state;
}
-
- if (uid == null)
+ else
{
- Sawmill.Info($"Local player is no longer attached to any entity.");
- return true;
+ _pendingStates.Remove(state.UserId);
}
- if (!EntManager.EntityExists(uid))
- {
- Sawmill.Error($"Attempted to attach player to non-existent entity {uid}!");
- return true;
- }
+ SetAttachedEntity(LocalSession, uid, out _, true);
+ SetStatus(LocalSession, state.Status);
+ }
- if (!EntManager.HasComponent(uid.Value))
- {
- if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
- Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
- var eye = Factory.GetComponent();
- eye.NetSyncEnabled = false;
- EntManager.AddComponent(uid.Value, eye);
- }
+ return UpdatePlayerList(list, fullList) || dirty;
+ }
- Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(uid)}.");
- EntManager.EventBus.RaiseLocalEvent(uid.Value, new LocalPlayerAttachedEvent(uid.Value), true);
- LocalPlayerAttached?.Invoke(uid.Value);
- return true;
- }
+ ///
+ /// Handles the incoming PlayerList message from the server.
+ ///
+ private void HandlePlayerList(MsgPlayerList msg)
+ {
+ ApplyPlayerStates(msg.Plyrs);
+ }
- public void ApplyPlayerStates(IReadOnlyCollection list)
+ ///
+ /// Compares the server player list to the client one, and updates if needed.
+ ///
+ private bool UpdatePlayerList(IEnumerable remotePlayers, bool fullList)
+ {
+ var dirty = false;
+ var users = new List();
+ foreach (var state in remotePlayers)
{
- var dirty = ApplyStates(list, true);
+ users.Add(state.UserId);
- if (_pendingStates.Count == 0)
+ if (!EntManager.TryGetEntity(state.ControlledEntity, out var controlled)
+ && state.ControlledEntity is { Valid: true })
{
- // This is somewhat inefficient as it might try to re-apply states that failed just a moment ago.
- _pending.Clear();
- _pending.AddRange(_pendingStates.Values);
- _pendingStates.Clear();
- dirty |= ApplyStates(_pending, false);
+ _pendingStates[state.UserId] = state;
+ }
+ else
+ {
+ _pendingStates.Remove(state.UserId);
}
- if (dirty)
- PlayerListUpdated?.Invoke();
- }
-
- private bool ApplyStates(IReadOnlyCollection list, bool fullList)
- {
- if (list.Count == 0)
- return false;
-
- DebugTools.Assert(_network.IsConnected || _client.RunLevel == ClientRunLevel.SinglePlayerGame // replays use state application.
- , "Received player state without being connected?");
- DebugTools.Assert(LocalSession != null, "Received player state before Session finished setup.");
-
- var state = list.FirstOrDefault(s => s.UserId == LocalSession.UserId);
-
- bool dirty = false;
- if (state != null)
+ if (!InternalSessions.TryGetValue(state.UserId, out var session))
{
+ // This is a new userid, so we create a new session.
+ DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
+ var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
+ SetStatus(newSession, state.Status);
+ SetAttachedEntity(newSession, controlled, out _, true);
dirty = true;
- if (!EntManager.TryGetEntity(state.ControlledEntity, out var uid)
- && state.ControlledEntity is { Valid: true })
- {
- Sawmill.Error($"Received player state for local player with an unknown net entity!");
- _pendingStates[state.UserId] = state;
- }
- else
- {
- _pendingStates.Remove(state.UserId);
- }
-
- SetAttachedEntity(LocalSession, uid, out _, true);
- SetStatus(LocalSession, state.Status);
+ continue;
}
- return UpdatePlayerList(list, fullList) || dirty;
- }
+ // Check if the data is actually different
+ if (session.Name == state.Name
+ && session.Status == state.Status
+ && session.AttachedEntity == controlled)
+ {
+ continue;
+ }
- ///
- /// Handles the incoming PlayerList message from the server.
- ///
- private void HandlePlayerList(MsgPlayerList msg)
- {
- ApplyPlayerStates(msg.Plyrs);
+ dirty = true;
+ var local = (ICommonSessionInternal)session;
+ local.SetName(state.Name);
+ SetStatus(local, state.Status);
+ SetAttachedEntity(local, controlled, out _, true);
}
- ///
- /// Compares the server player list to the client one, and updates if needed.
- ///
- private bool UpdatePlayerList(IEnumerable remotePlayers, bool fullList)
+ // Remove old users. This only works if the provided state is a list of all players
+ if (fullList)
{
- var dirty = false;
- var users = new List();
- foreach (var state in remotePlayers)
+ foreach (var oldUser in InternalSessions.Keys.ToArray())
{
- users.Add(state.UserId);
-
- if (!EntManager.TryGetEntity(state.ControlledEntity, out var controlled)
- && state.ControlledEntity is { Valid: true })
- {
- _pendingStates[state.UserId] = state;
- }
- else
- {
- _pendingStates.Remove(state.UserId);
- }
-
- if (!InternalSessions.TryGetValue(state.UserId, out var session))
- {
- // This is a new userid, so we create a new session.
- DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
- var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
- SetStatus(newSession, state.Status);
- SetAttachedEntity(newSession, controlled, out _, true);
- dirty = true;
+ if (users.Contains(oldUser))
continue;
- }
- // Check if the data is actually different
- if (session.Name == state.Name
- && session.Status == state.Status
- && session.AttachedEntity == controlled)
- {
+ if (InternalSessions[oldUser].ClientSide)
continue;
- }
+ DebugTools.Assert(oldUser != LocalUser
+ || LocalUser == null
+ || LocalUser == default(NetUserId),
+ "Client is still connected to the server but not in the list of players?");
+ RemoveSession(oldUser);
+ _pendingStates.Remove(oldUser);
dirty = true;
- var local = (ICommonSessionInternal)session;
- local.SetName(state.Name);
- SetStatus(local, state.Status);
- SetAttachedEntity(local, controlled, out _, true);
}
-
- // Remove old users. This only works if the provided state is a list of all players
- if (fullList)
- {
- foreach (var oldUser in InternalSessions.Keys.ToArray())
- {
- if (users.Contains(oldUser))
- continue;
-
- if (InternalSessions[oldUser].ClientSide)
- continue;
-
- DebugTools.Assert(oldUser != LocalUser
- || LocalUser == null
- || LocalUser == default(NetUserId),
- "Client is still connected to the server but not in the list of players?");
- RemoveSession(oldUser);
- _pendingStates.Remove(oldUser);
- dirty = true;
- }
- }
-
- return dirty;
}
- public override bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
- {
- if (LocalEntity == uid)
- {
- session = LocalSession!;
- return true;
- }
+ return dirty;
+ }
- session = null;
- return false;
+ public override bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
+ {
+ if (LocalEntity == uid)
+ {
+ session = LocalSession!;
+ return true;
}
+
+ session = null;
+ return false;
}
}
From 89d1819beb9a738b8ca173974488560085d73545 Mon Sep 17 00:00:00 2001
From: Ed <96445749+TheShuEd@users.noreply.github.com>
Date: Tue, 12 May 2026 16:49:57 +0300
Subject: [PATCH 4/4] revert readonly
---
Robust.Client/Player/PlayerManager.cs | 4 ++--
Robust.Server/Player/PlayerManager.cs | 12 ++++++------
Robust.Shared/Player/SharedPlayerManager.cs | 10 +++++-----
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/Robust.Client/Player/PlayerManager.cs b/Robust.Client/Player/PlayerManager.cs
index cdf83d4e0c6..16d248ea5e6 100644
--- a/Robust.Client/Player/PlayerManager.cs
+++ b/Robust.Client/Player/PlayerManager.cs
@@ -19,8 +19,8 @@ namespace Robust.Client.Player;
///
internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManager
{
- [Dependency] private readonly IClientNetManager _network = default!;
- [Dependency] private readonly IBaseClient _client = default!;
+ [Dependency] private IClientNetManager _network = default!;
+ [Dependency] private IBaseClient _client = default!;
///
/// Received player states that had an unknown .
diff --git a/Robust.Server/Player/PlayerManager.cs b/Robust.Server/Player/PlayerManager.cs
index b82081ddb64..07323cfc950 100644
--- a/Robust.Server/Player/PlayerManager.cs
+++ b/Robust.Server/Player/PlayerManager.cs
@@ -27,12 +27,12 @@ internal sealed partial class PlayerManager : SharedPlayerManager, IPlayerManage
private static readonly Gauge PlayerCountMetric = Metrics
.CreateGauge("robust_player_count", "Number of players on the server.");
- [Dependency] private readonly IBaseServer _baseServer = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IServerNetManager _network = default!;
- [Dependency] private readonly IReflectionManager _reflectionManager = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IServerNetConfigurationManager _cfg = default!;
+ [Dependency] private IBaseServer _baseServer = default!;
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private IServerNetManager _network = default!;
+ [Dependency] private IReflectionManager _reflectionManager = default!;
+ [Dependency] private IEntityManager _entityManager = default!;
+ [Dependency] private IServerNetConfigurationManager _cfg = default!;
public BoundKeyMap KeyMap { get; private set; } = default!;
diff --git a/Robust.Shared/Player/SharedPlayerManager.cs b/Robust.Shared/Player/SharedPlayerManager.cs
index 54ab6686fd9..2a78bb9e7dc 100644
--- a/Robust.Shared/Player/SharedPlayerManager.cs
+++ b/Robust.Shared/Player/SharedPlayerManager.cs
@@ -11,11 +11,11 @@ namespace Robust.Shared.Player;
internal abstract partial class SharedPlayerManager : ISharedPlayerManager
{
- [Dependency] protected readonly IEntityManager EntManager = default!;
- [Dependency] protected readonly IComponentFactory Factory = default!;
- [Dependency] protected readonly ILogManager LogMan = default!;
- [Dependency] protected readonly IGameTiming Timing = default!;
- [Dependency] private readonly INetManager _netMan = default!;
+ [Dependency] protected IEntityManager EntManager = default!;
+ [Dependency] protected IComponentFactory Factory = default!;
+ [Dependency] protected ILogManager LogMan = default!;
+ [Dependency] protected IGameTiming Timing = default!;
+ [Dependency] private INetManager _netMan = default!;
protected ISawmill Sawmill = default!;