IPlayerManager refactor (#4518)

This commit is contained in:
Leon Friedrich
2023-10-24 20:18:58 +11:00
committed by GitHub
parent b2d389f184
commit 5a6c4220fc
109 changed files with 1209 additions and 1688 deletions

View File

@@ -1,8 +1,6 @@
using System;
using System.Linq;
using System.Net;
using Robust.Client.Configuration;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -10,13 +8,12 @@ using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -65,12 +62,12 @@ namespace Robust.Client
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_playMan.Initialize();
_playMan.Initialize(0);
_playMan.PlayerListUpdated += OnPlayerListUpdated;
Reset();
}
private void OnPlayerListUpdated(object? sender, EventArgs e)
private void OnPlayerListUpdated()
{
var serverPlayers = _playMan.PlayerCount;
if (_net.ServerChannel != null && GameInfo != null && _net.IsConnected)
@@ -130,9 +127,10 @@ namespace Robust.Client
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
DebugTools.Assert(!_net.IsConnected);
_playMan.Startup();
_playMan.LocalPlayer!.Name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
var name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
_playMan.SetupSinglePlayer(name);
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
_playMan.JoinGame(_playMan.LocalSession!);
GameStartedSetup();
}
@@ -173,22 +171,14 @@ namespace Robust.Client
info.ServerName = serverName;
}
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
info.ServerMaxPlayers = maxPlayers;
var userName = _net.ServerChannel!.UserName;
var userId = _net.ServerChannel.UserId;
var channel = _net.ServerChannel!;
// start up player management
_playMan.Startup();
_playMan.LocalPlayer!.UserId = userId;
_playMan.LocalPlayer.Name = userName;
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
_playMan.SetupMultiplayer(channel);
_playMan.PlayerStatusChanged += OnStatusChanged;
var serverPlayers = _playMan.PlayerCount;
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
_discord.Update(info.ServerName, channel.UserName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
}
@@ -221,6 +211,8 @@ namespace Robust.Client
private void Reset()
{
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
_playMan.PlayerStatusChanged -= OnStatusChanged;
_configManager.ClearReceivedInitialNwVars();
OnRunLevelChanged(ClientRunLevel.Initialize);
}
@@ -263,19 +255,17 @@ namespace Robust.Client
Reset();
}
private void OnLocalStatusChanged(object? obj, StatusEventArgs eventArgs)
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.Session != _playMan.LocalSession)
return;
// player finished fully connecting to the server.
// OldStatus is used here because it can go from connecting-> connected or connecting-> ingame
if (eventArgs.OldStatus == SessionStatus.Connecting)
{
OnPlayerJoinedServer(_playMan.LocalPlayer!.Session);
}
if (eventArgs.NewStatus == SessionStatus.InGame)
{
OnPlayerJoinedGame(_playMan.LocalPlayer!.Session);
}
if (e.OldStatus == SessionStatus.Connecting)
OnPlayerJoinedServer(e.Session);
else if (e.NewStatus == SessionStatus.InGame)
OnPlayerJoinedGame(e.Session);
}
private void OnRunLevelChanged(ClientRunLevel newRunLevel)

View File

@@ -37,7 +37,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;

View File

@@ -13,7 +13,7 @@ using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;

View File

@@ -170,7 +170,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel? channel)
{
throw new NotSupportedException();
}

View File

@@ -17,7 +17,6 @@ using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Random;
using Robust.Shared.Replays;
using Robust.Shared.Threading;

View File

@@ -1,7 +1,7 @@
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
@@ -15,8 +15,8 @@ public sealed class EyeSystem : SharedEyeSystem
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EyeComponent, PlayerDetachedEvent>(OnEyeDetached);
SubscribeLocalEvent<EyeComponent, PlayerAttachedEvent>(OnEyeAttached);
SubscribeLocalEvent<EyeComponent, LocalPlayerDetachedEvent>(OnEyeDetached);
SubscribeLocalEvent<EyeComponent, LocalPlayerAttachedEvent>(OnEyeAttached);
SubscribeLocalEvent<EyeComponent, AfterAutoHandleStateEvent>(OnEyeAutoState);
// Make sure this runs *after* entities have been moved by interpolation and movement.
@@ -29,7 +29,7 @@ public sealed class EyeSystem : SharedEyeSystem
UpdateEye(component);
}
private void OnEyeAttached(EntityUid uid, EyeComponent component, PlayerAttachedEvent args)
private void OnEyeAttached(EntityUid uid, EyeComponent component, LocalPlayerAttachedEvent args)
{
// TODO: This probably shouldn't be nullable bruv.
if (component._eye != null)
@@ -41,7 +41,7 @@ public sealed class EyeSystem : SharedEyeSystem
RaiseLocalEvent(uid, ref ev, true);
}
private void OnEyeDetached(EntityUid uid, EyeComponent component, PlayerDetachedEvent args)
private void OnEyeDetached(EntityUid uid, EyeComponent component, LocalPlayerDetachedEvent args)
{
_eyeManager.ClearCurrentEye();
}

View File

@@ -3,16 +3,13 @@ using System.Numerics;
using Robust.Client.GameStates;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -131,7 +128,7 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
SubscribeLocalEvent<LocalPlayerAttachedEvent>(OnAttachedEntityChanged);
_conHost.RegisterCommand("incmd",
"Inserts an input command into the simulation",
@@ -171,11 +168,11 @@ namespace Robust.Client.GameObjects
HandleInputCommand(localPlayer.Session, keyFunction, message);
}
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)
private void OnAttachedEntityChanged(LocalPlayerAttachedEvent message)
{
if (message.AttachedEntity != default) // attach
if (message.Entity != default) // attach
{
SetEntityContextActive(_inputManager, message.AttachedEntity);
SetEntityContextActive(_inputManager, message.Entity);
}
else // detach
{
@@ -227,44 +224,4 @@ namespace Robust.Client.GameObjects
_sawmillInputContext = _logManager.GetSawmill("input.context");
}
}
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public sealed class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.
/// </summary>
public EntityUid AttachedEntity { get; }
/// <summary>
/// Creates a new instance of <see cref="PlayerAttachSysMessage"/>.
/// </summary>
/// <param name="attachedEntity">New entity the player is attached to.</param>
public PlayerAttachSysMessage(EntityUid attachedEntity)
{
AttachedEntity = attachedEntity;
}
}
public sealed class PlayerAttachedEvent : EntityEventArgs
{
public PlayerAttachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}
public sealed class PlayerDetachedEvent : EntityEventArgs
{
public PlayerDetachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}
}

View File

@@ -689,7 +689,7 @@ namespace Robust.Client.GameStates
using (_prof.Group("Player"))
{
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<SessionState>());
}
using (_prof.Group("Callback"))

View File

@@ -1,6 +1,6 @@
using System.Buffers;
using System.Collections.Generic;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
@@ -20,8 +20,8 @@ public sealed partial class PhysicsSystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerAttachedEvent>(OnAttach);
SubscribeLocalEvent<PlayerDetachedEvent>(OnDetach);
SubscribeLocalEvent<LocalPlayerAttachedEvent>(OnAttach);
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnDetach);
SubscribeLocalEvent<PhysicsComponent, JointAddedEvent>(OnJointAdded);
SubscribeLocalEvent<PhysicsComponent, JointRemovedEvent>(OnJointRemoved);
}
@@ -63,12 +63,12 @@ public sealed partial class PhysicsSystem
UpdateIsPredicted(args.Joint.BodyBUid);
}
private void OnAttach(PlayerAttachedEvent ev)
private void OnAttach(LocalPlayerAttachedEvent ev)
{
UpdateIsPredicted(ev.Entity);
}
private void OnDetach(PlayerDetachedEvent ev)
private void OnDetach(LocalPlayerDetachedEvent ev)
{
UpdateIsPredicted(ev.Entity);
}

View File

@@ -8,7 +8,6 @@ using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
@@ -287,23 +286,17 @@ namespace Robust.Client.Placement
}, outsidePrediction: true))
.Register<PlacementManager>();
var localPlayer = PlayerManager.LocalPlayer;
localPlayer!.EntityAttached += OnEntityAttached;
PlayerManager.LocalPlayerDetached += OnDetached;
}
private void TearDownInput()
{
CommandBinds.Unregister<PlacementManager>();
if (PlayerManager.LocalPlayer != null)
{
PlayerManager.LocalPlayer.EntityAttached -= OnEntityAttached;
}
PlayerManager.LocalPlayerDetached -= OnDetached;
}
private void OnEntityAttached(EntityAttachedEventArgs eventArgs)
private void OnDetached(EntityUid obj)
{
// player attached to a new entity, basically disable the editor
Clear();
}

View File

@@ -1,44 +1,71 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
using Robust.Shared.Player;
namespace Robust.Client.Player
namespace Robust.Client.Player;
public interface IPlayerManager : ISharedPlayerManager
{
public interface IPlayerManager : ISharedPlayerManager
{
new IEnumerable<ICommonSession> Sessions { get; }
/// <summary>
/// Invoked when the list of sessions/players gets updated.
/// </summary>
event Action? PlayerListUpdated;
[ViewVariables]
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
/// <summary>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets attached to a new entity. See also <see cref="LocalPlayerAttachedEvent"/>
/// </summary>
event Action<EntityUid>? LocalPlayerAttached;
[ViewVariables]
LocalPlayer? LocalPlayer { get; }
/// <summary>
/// Invoked when <see cref="ISharedPlayerManager.LocalSession"/> gets detached from new entity. See also <see cref="LocalPlayerDetachedEvent"/>
/// </summary>
event Action<EntityUid>? LocalPlayerDetached;
/// <summary>
/// Invoked after LocalPlayer is changed
/// </summary>
event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
void ApplyPlayerStates(IReadOnlyCollection<SessionState> list);
event EventHandler PlayerListUpdated;
/// <summary>
/// Sets up a single player game. This creates a dummy <see cref="ISharedPlayerManager.LocalSession"/> without an
/// <see cref="INetChannel"/>.
/// </summary>
void SetupSinglePlayer(string name);
void Initialize();
void Startup();
void Shutdown();
/// <summary>
/// Sets up the manager for a multiplayer game. This creates a <see cref="ISharedPlayerManager.LocalSession"/>
/// using the given <see cref="INetChannel"/>.
/// </summary>
void SetupMultiplayer(INetChannel channel);
void ApplyPlayerStates(IReadOnlyCollection<PlayerState> list);
}
public sealed class LocalPlayerChangedEventArgs : EventArgs
{
public readonly LocalPlayer? OldPlayer;
public readonly LocalPlayer? NewPlayer;
public LocalPlayerChangedEventArgs(LocalPlayer? oldPlayer, LocalPlayer? newPlayer)
{
OldPlayer = oldPlayer;
NewPlayer = newPlayer;
}
}
[Obsolete("Use LocalSession instead")]
LocalPlayer? LocalPlayer { get;}
}
/// <summary>
/// ECS event that gets raised when the local player gets attached to a new entity. The event is both broadcast and
/// raised directed at the new entity.
/// </summary>
public sealed class LocalPlayerAttachedEvent : EntityEventArgs
{
public LocalPlayerAttachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}
/// <summary>
/// ECS event that gets raised when the local player gets detached from an entity. The event is both broadcast and
/// raised directed at the new entity.
/// </summary>
public sealed class LocalPlayerDetachedEvent : EntityEventArgs
{
public LocalPlayerDetachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}

View File

@@ -1,11 +1,6 @@
using System;
using Robust.Client.GameObjects;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
@@ -15,163 +10,30 @@ namespace Robust.Client.Player
/// </summary>
public sealed class LocalPlayer
{
/// <summary>
/// An entity has been attached to the local player.
/// </summary>
public event Action<EntityAttachedEventArgs>? EntityAttached;
/// <summary>
/// An entity has been detached from the local player.
/// </summary>
public event Action<EntityDetachedEventArgs>? EntityDetached;
public LocalPlayer(ICommonSession session)
{
Session = session;
}
/// <summary>
/// Game entity that the local player is controlling. If this is default, the player is not attached to any
/// entity at all.
/// </summary>
[ViewVariables] public EntityUid? ControlledEntity { get; private set; }
[ViewVariables] public NetUserId UserId { get; set; }
/// <summary>
/// Session of the local client.
/// </summary>
[ViewVariables]
public ICommonSession Session => InternalSession;
public EntityUid? ControlledEntity => Session.AttachedEntity;
internal PlayerSession InternalSession { get; set; } = default!;
[ViewVariables]
public NetUserId UserId => Session.UserId;
/// <summary>
/// OOC name of the local player.
/// </summary>
[ViewVariables]
public string Name { get; set; } = default!;
public string Name => Session.Name;
/// <summary>
/// The status of the client's session has changed.
/// Session of the local client.
/// </summary>
public event EventHandler<StatusEventArgs>? StatusChanged;
/// <summary>
/// Attaches a client to an entity.
/// </summary>
/// <param name="entity">Entity to attach the client to.</param>
public void AttachEntity(EntityUid entity, IEntityManager entMan, IBaseClient client)
{
if (ControlledEntity == entity)
return;
// Detach and cleanup first
DetachEntity();
if (!entMan.EntityExists(entity))
{
Logger.Error($"Attempting to attach player to non-existent entity {entity}!");
return;
}
ControlledEntity = entity;
InternalSession.AttachedEntity = entity;
if (!entMan.TryGetComponent<EyeComponent?>(entity, out var eye))
{
eye = entMan.AddComponent<EyeComponent>(entity);
if (client.RunLevel != ClientRunLevel.SinglePlayerGame)
{
Logger.Warning($"Attaching local player to an entity {entMan.ToPrettyString(entity)} without an eye. This eye will not be netsynced and may cause issues.");
}
eye.NetSyncEnabled = false;
}
EntityAttached?.Invoke(new EntityAttachedEventArgs(entity));
// notify ECS Systems
var eventBus = entMan.EventBus;
eventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(entity));
eventBus.RaiseLocalEvent(entity, new PlayerAttachedEvent(entity), true);
}
/// <summary>
/// Detaches the client from an entity.
/// </summary>
public void DetachEntity()
{
var entMan = IoCManager.Resolve<IEntityManager>();
var previous = ControlledEntity;
ControlledEntity = null;
InternalSession.AttachedEntity = null;
if (previous != null)
{
entMan.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSysMessage(default));
entMan.EventBus.RaiseLocalEvent(previous.Value, new PlayerDetachedEvent(previous.Value), true);
EntityDetached?.Invoke(new EntityDetachedEventArgs(previous.Value));
}
}
/// <summary>
/// Changes the state of the session.
/// </summary>
public void SwitchState(SessionStatus newStatus)
{
SwitchState(Session.Status, newStatus);
}
/// <summary>
/// Changes the state of the session. This overload allows you to spoof the oldStatus, use with caution.
/// </summary>
public void SwitchState(SessionStatus oldStatus, SessionStatus newStatus)
{
var args = new StatusEventArgs(oldStatus, newStatus);
Session.Status = newStatus;
StatusChanged?.Invoke(this, args);
}
}
/// <summary>
/// Event arguments for when the status of a session changes.
/// </summary>
public sealed class StatusEventArgs : EventArgs
{
/// <summary>
/// Status that the session switched from.
/// </summary>
public SessionStatus OldStatus { get; }
/// <summary>
/// Status that the session switched to.
/// </summary>
public SessionStatus NewStatus { get; }
/// <summary>
/// Constructs a new instance of the class.
/// </summary>
public StatusEventArgs(SessionStatus oldStatus, SessionStatus newStatus)
{
OldStatus = oldStatus;
NewStatus = newStatus;
}
}
public sealed class EntityDetachedEventArgs : EventArgs
{
public EntityDetachedEventArgs(EntityUid oldEntity)
{
OldEntity = oldEntity;
}
public EntityUid OldEntity { get; }
}
public sealed class EntityAttachedEventArgs : EventArgs
{
public EntityAttachedEventArgs(EntityUid newEntity)
{
NewEntity = newEntity;
}
public EntityUid NewEntity { get; }
public ICommonSession Session;
}
}

View File

@@ -2,16 +2,13 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
{
@@ -20,154 +17,187 @@ namespace Robust.Client.Player
/// 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.
/// </summary>
public sealed class PlayerManager : IPlayerManager
internal sealed class PlayerManager : SharedPlayerManager, IPlayerManager
{
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly ILogManager _logMan = default!;
/// <summary>
/// Active sessions of connected clients to the server.
/// Received player states that had an unknown <see cref="NetEntity"/>.
/// </summary>
private readonly Dictionary<NetUserId, ICommonSession> _sessions = new();
private Dictionary<NetUserId, SessionState> _pendingStates = new ();
private List<SessionState> _pending = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions
public override ICommonSession[] NetworkedSessions
{
get
{
if (LocalPlayer is not null)
return new[] {LocalPlayer.Session};
return Enumerable.Empty<ICommonSession>();
return LocalSession != null
? new [] { LocalSession }
: Array.Empty<ICommonSession>();
}
}
/// <inheritdoc />
IEnumerable<ICommonSession> ISharedPlayerManager.Sessions => _sessions.Values;
public override int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? -1;
public LocalPlayer? LocalPlayer { get; set; }
public event Action<SessionStatusEventArgs>? LocalStatusChanged;
public event Action? PlayerListUpdated;
public event Action<EntityUid>? LocalPlayerDetached;
public event Action<EntityUid>? LocalPlayerAttached;
/// <inheritdoc />
public int PlayerCount => _sessions.Values.Count;
/// <inheritdoc />
public int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? 0;
public ICommonSession? LocalSession => LocalPlayer?.Session;
/// <inheritdoc />
[ViewVariables]
public LocalPlayer? LocalPlayer
public override void Initialize(int maxPlayers)
{
get => _localPlayer;
private set
{
if (_localPlayer == value) return;
var oldValue = _localPlayer;
_localPlayer = value;
LocalPlayerChanged?.Invoke(new LocalPlayerChangedEventArgs(oldValue, _localPlayer));
}
}
private LocalPlayer? _localPlayer;
private ISawmill _sawmill = default!;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
[ViewVariables]
IEnumerable<ICommonSession> IPlayerManager.Sessions => _sessions.Values;
/// <inheritdoc />
public IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict => _sessions;
/// <inheritdoc />
public event EventHandler? PlayerListUpdated;
/// <inheritdoc />
public void Initialize()
{
_client.RunLevelChanged += OnRunLevelChanged;
_sawmill = _logMan.GetSawmill("player");
base.Initialize(maxPlayers);
_network.RegisterNetMessage<MsgPlayerListReq>();
_network.RegisterNetMessage<MsgPlayerList>(HandlePlayerList);
PlayerStatusChanged += StatusChanged;
}
private void StatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.Session == LocalPlayer?.Session)
LocalStatusChanged?.Invoke(e);
}
/// <inheritdoc />
public void Startup()
public override void Startup()
{
DebugTools.Assert(LocalPlayer == null);
LocalPlayer = new LocalPlayer();
if (LocalSession == null)
throw new InvalidOperationException("LocalSession cannot be null");
var msgList = new MsgPlayerListReq();
// message is empty
_network.ClientSendMessage(msgList);
LocalPlayer = new LocalPlayer(LocalSession);
base.Startup();
}
public void SetupSinglePlayer(string name)
{
if (LocalSession != null)
throw new InvalidOperationException($"Player manager already running?");
LocalSession = CreateAndAddSession(default, name);
Startup();
PlayerListUpdated?.Invoke();
}
public void SetupMultiplayer(INetChannel channel)
{
if (LocalSession != null)
throw new InvalidOperationException($"Player manager already running?");
var session = CreateAndAddSession(channel.UserId, channel.UserName);
session.Channel = channel;
LocalSession = session;
Startup();
_network.ClientSendMessage(new MsgPlayerListReq());
}
/// <inheritdoc />
public void Shutdown()
public override void Shutdown()
{
LocalPlayer?.DetachEntity();
if (LocalSession != null)
SetAttachedEntity(LocalSession, null);
LocalPlayer = null;
_sessions.Clear();
LocalSession = null;
_pendingStates.Clear();
base.Shutdown();
PlayerListUpdated?.Invoke();
}
/// <inheritdoc />
public void ApplyPlayerStates(IReadOnlyCollection<PlayerState> list)
public override void SetAttachedEntity(ICommonSession session, EntityUid? uid)
{
if (session.AttachedEntity == uid)
return;
var old = session.AttachedEntity;
base.SetAttachedEntity(session, uid);
if (session != LocalSession)
return;
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);
}
if (uid == null)
{
Sawmill.Info($"Local player is no longer attached to any entity.");
return;
}
if (!EntManager.EntityExists(uid))
{
Sawmill.Error($"Attempted to attach player to non-existent entity {uid}!");
return;
}
if (!EntManager.EnsureComponent(uid.Value, out EyeComponent eye))
{
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.");
eye.NetSyncEnabled = false;
}
Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(uid)}.");
EntManager.EventBus.RaiseLocalEvent(uid.Value, new LocalPlayerAttachedEvent(uid.Value), true);
LocalPlayerAttached?.Invoke(uid.Value);
}
public void ApplyPlayerStates(IReadOnlyCollection<SessionState> list)
{
var dirty = ApplyStates(list, true);
if (_pendingStates.Count == 0)
{
// 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);
}
if (dirty)
PlayerListUpdated?.Invoke();
}
private bool ApplyStates(IReadOnlyCollection<SessionState> list, bool fullList)
{
if (list.Count == 0)
{
// This happens when the server says "nothing changed!"
return;
}
return false;
DebugTools.Assert(_network.IsConnected || _client.RunLevel == ClientRunLevel.SinglePlayerGame // replays use state application.
, "Received player state without being connected?");
DebugTools.Assert(LocalPlayer != null, "Call Startup()");
DebugTools.Assert(LocalPlayer!.Session != null, "Received player state before Session finished setup.");
DebugTools.Assert(LocalSession != null, "Received player state before Session finished setup.");
var myState = list.FirstOrDefault(s => s.UserId == LocalPlayer.UserId);
var state = list.FirstOrDefault(s => s.UserId == LocalSession.UserId);
if (myState != null)
bool dirty = false;
if (state != null)
{
var uid = _entManager.GetEntity(myState.ControlledEntity);
if (myState.ControlledEntity is {Valid: true} && !_entManager.EntityExists(uid))
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!");
Sawmill.Error($"Received player state for local player with an unknown net entity!");
_pendingStates[state.UserId] = state;
}
else
{
_pendingStates.Remove(state.UserId);
}
UpdateAttachedEntity(uid);
UpdateSessionStatus(myState.Status);
SetAttachedEntity(LocalSession, uid);
SetStatus(LocalSession, state.Status);
}
UpdatePlayerList(list);
}
/// <summary>
/// Compares the server sessionStatus to the client one, and updates if needed.
/// </summary>
private void UpdateSessionStatus(SessionStatus myStateStatus)
{
if (LocalPlayer!.Session.Status != myStateStatus)
LocalPlayer.SwitchState(myStateStatus);
}
/// <summary>
/// Compares the server attachedEntity to the client one, and updates if needed.
/// </summary>
/// <param name="entity">AttachedEntity in the server session.</param>
private void UpdateAttachedEntity(EntityUid? entity)
{
if (LocalPlayer!.ControlledEntity == entity)
{
return;
}
if (entity == null)
{
LocalPlayer.DetachEntity();
return;
}
LocalPlayer.AttachEntity(entity.Value, _entManager, _client);
return UpdatePlayerList(list, fullList) || dirty;
}
/// <summary>
@@ -175,117 +205,87 @@ namespace Robust.Client.Player
/// </summary>
private void HandlePlayerList(MsgPlayerList msg)
{
UpdatePlayerList(msg.Plyrs);
ApplyPlayerStates(msg.Plyrs);
}
/// <summary>
/// Compares the server player list to the client one, and updates if needed.
/// </summary>
private void UpdatePlayerList(IEnumerable<PlayerState> remotePlayers)
private bool UpdatePlayerList(IEnumerable<SessionState> remotePlayers, bool fullList)
{
var dirty = false;
var hitSet = new List<NetUserId>();
var users = new List<NetUserId>();
foreach (var state in remotePlayers)
{
hitSet.Add(state.UserId);
users.Add(state.UserId);
if (_sessions.TryGetValue(state.UserId, out var session))
if (!EntManager.TryGetEntity(state.ControlledEntity, out var controlled)
&& state.ControlledEntity is {Valid: true})
{
var local = (PlayerSession) session;
var controlled = _entManager.GetEntity(state.ControlledEntity);
// Exists, update data.
if (local.Name == state.Name
&& local.Status == state.Status
&& local.Ping == state.Ping
&& local.AttachedEntity == controlled)
{
continue;
}
dirty = true;
local.Name = state.Name;
local.Status = state.Status;
local.Ping = state.Ping;
local.AttachedEntity = controlled;
_pendingStates[state.UserId] = state;
}
else
{
// New, give him a slot.
dirty = true;
var newSession = new PlayerSession(state.UserId)
{
Name = state.Name,
Status = state.Status,
Ping = state.Ping,
AttachedEntity = _entManager.GetEntity(state.ControlledEntity),
};
_sessions.Add(state.UserId, newSession);
if (state.UserId == LocalPlayer!.UserId)
{
LocalPlayer.InternalSession = newSession;
newSession.ConnectedClient = _network.ServerChannel!;
// We just connected to the server, hurray!
LocalPlayer.SwitchState(SessionStatus.Connecting, newSession.Status);
}
_pendingStates.Remove(state.UserId);
}
}
foreach (var existing in _sessions.Keys.ToArray())
{
// clear slot, player left
if (!hitSet.Contains(existing))
if (!InternalSessions.TryGetValue(state.UserId, out var session))
{
DebugTools.Assert(LocalPlayer!.UserId != existing || _client.RunLevel == ClientRunLevel.SinglePlayerGame, // replays apply player states.
"I'm still connected to the server, but i left?");
_sessions.Remove(existing);
// This is a new userid, so we create a new session.
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
var newSession = CreateAndAddSession(state.UserId, state.Name);
newSession.Ping = state.Ping;
newSession.Name = state.Name;
SetStatus(newSession, state.Status);
SetAttachedEntity(newSession, controlled);
dirty = true;
continue;
}
// Check if the data is actually different
if (session.Name == state.Name
&& session.Status == state.Status
&& session.Ping == state.Ping
&& session.AttachedEntity == controlled)
{
continue;
}
dirty = true;
var local = (CommonSession) session;
local.Name = state.Name;
local.Ping = state.Ping;
SetStatus(local, state.Status);
SetAttachedEntity(local, controlled);
}
// 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())
{
// clear slot, player left
if (users.Contains(oldUser))
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;
}
}
if (dirty)
{
PlayerListUpdated?.Invoke(this, EventArgs.Empty);
}
return dirty;
}
private void OnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
public override bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
{
if (e.NewLevel != ClientRunLevel.SinglePlayerGame)
return;
DebugTools.AssertNotNull(LocalPlayer);
// We do some further setup steps for singleplayer here...
// The local player's GUID in singleplayer will always be the default.
var guid = default(NetUserId);
var session = new PlayerSession(guid)
if (LocalEntity == uid)
{
Name = LocalPlayer!.Name,
Ping = 0,
};
LocalPlayer.UserId = guid;
LocalPlayer.InternalSession = session;
// Add the local session to the list.
_sessions.Add(guid, session);
LocalPlayer.SwitchState(SessionStatus.InGame);
PlayerListUpdated?.Invoke(this, EventArgs.Empty);
}
public bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
{
if (LocalPlayer?.ControlledEntity == uid)
{
session = LocalPlayer.Session;
session = LocalSession!;
return true;
}

View File

@@ -1,60 +0,0 @@
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
{
internal sealed class PlayerSession : ICommonSession
{
internal SessionStatus Status { get; set; } = SessionStatus.Connecting;
/// <inheritdoc />
SessionStatus ICommonSession.Status
{
get => this.Status;
set => this.Status = value;
}
/// <inheritdoc />
[ViewVariables]
public EntityUid? AttachedEntity { get; set; }
/// <inheritdoc />
[ViewVariables]
public NetUserId UserId { get; }
[ViewVariables]
internal string Name { get; set; } = "<Unknown>";
/// <inheritdoc />
string ICommonSession.Name
{
get => this.Name;
set => this.Name = value;
}
[ViewVariables]
internal short Ping { get; set; }
/// <inheritdoc />
[ViewVariables]
public INetChannel ConnectedClient { get; internal set; } = null!;
/// <inheritdoc />
short ICommonSession.Ping
{
get => this.Ping;
set => this.Ping = value;
}
/// <summary>
/// Creates an instance of a PlayerSession.
/// </summary>
public PlayerSession(NetUserId user)
{
UserId = user;
}
}
}

View File

@@ -7,7 +7,6 @@ using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Threading.Tasks;
using Robust.Client.Upload.Commands;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Replays;
@@ -101,7 +100,7 @@ public sealed partial class ReplayLoadManager
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
var playerSpan = state0.PlayerStates.Value;
Dictionary<NetUserId, PlayerState> playerStates = new(playerSpan.Count);
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
foreach (var player in playerSpan)
{
playerStates.Add(player.UserId, player);
@@ -391,7 +390,7 @@ public sealed partial class ReplayLoadManager
return new EntityState(newState.NetEntity, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps);
}
private void UpdatePlayerStates(ReadOnlySpan<PlayerState> span, Dictionary<NetUserId, PlayerState> playerStates)
private void UpdatePlayerStates(ReadOnlySpan<SessionState> span, Dictionary<NetUserId, SessionState> playerStates)
{
foreach (var player in span)
{

View File

@@ -9,7 +9,7 @@ using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Replays;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Value;
@@ -138,9 +138,9 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
return (state, detachMsg);
}
private PlayerState GetPlayerState(ICommonSession session)
private SessionState GetPlayerState(ICommonSession session)
{
return new PlayerState
return new SessionState
{
UserId = session.UserId,
Status = session.Status,

View File

@@ -15,7 +15,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;

View File

@@ -28,6 +28,7 @@ using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;

View File

@@ -21,7 +21,7 @@ namespace Robust.Server.Console.Commands
var sb = new StringBuilder();
var players = _players.ServerSessions;
var players = _players.Sessions;
sb.AppendLine($"{"Player Name",20} {"Status",12} {"Playing Time",14} {"Ping",9} {"IP EndPoint",20}");
sb.AppendLine("-------------------------------------------------------------------------------");
@@ -50,8 +50,8 @@ namespace Robust.Server.Console.Commands
{
if (args.Length < 1)
{
var player = shell.Player as IPlayerSession;
var toKickPlayer = player ?? _players.ServerSessions.FirstOrDefault();
var player = shell.Player;
var toKickPlayer = player ?? _players.Sessions.FirstOrDefault();
if (toKickPlayer == null)
{
shell.WriteLine("You need to provide a player to kick.");
@@ -79,7 +79,7 @@ namespace Robust.Server.Console.Commands
{
if (args.Length == 1)
{
var options = _players.ServerSessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
var options = _players.Sessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
return CompletionResult.FromHintOptions(options, "<PlayerIndex>");
}

View File

@@ -1,9 +1,8 @@
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Server.Console.Commands
{
@@ -17,7 +16,7 @@ namespace Robust.Server.Console.Commands
{
var session = shell.Player;
if (session is not IPlayerSession playerSession)
if (session is not { } playerSession)
{
shell.WriteError($"Unable to find {nameof(ICommonSession)} for shell");
return;
@@ -54,7 +53,7 @@ namespace Robust.Server.Console.Commands
{
var session = shell.Player;
if (session is not IPlayerSession playerSession)
if (session is not { } playerSession)
{
shell.WriteError($"Unable to find {nameof(ICommonSession)} for shell");
return;

View File

@@ -1,6 +1,4 @@
using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
@@ -10,27 +8,27 @@ namespace Robust.Server.Console
{
public IConGroupControllerImplementation? Implementation { get; set; }
public bool CanCommand(IPlayerSession session, string cmdName)
public bool CanCommand(ICommonSession session, string cmdName)
{
return Implementation?.CanCommand(session, cmdName) ?? false;
}
public bool CanAdminPlace(IPlayerSession session)
public bool CanAdminPlace(ICommonSession session)
{
return Implementation?.CanAdminPlace(session) ?? false;
}
public bool CanScript(IPlayerSession session)
public bool CanScript(ICommonSession session)
{
return Implementation?.CanScript(session) ?? false;
}
public bool CanAdminMenu(IPlayerSession session)
public bool CanAdminMenu(ICommonSession session)
{
return Implementation?.CanAdminMenu(session) ?? false;
}
public bool CanAdminReloadPrototypes(IPlayerSession session)
public bool CanAdminReloadPrototypes(ICommonSession session)
{
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
}

View File

@@ -1,14 +1,14 @@
using Robust.Server.Player;
using Robust.Shared.Player;
using Robust.Shared.Toolshed;
namespace Robust.Server.Console
{
public interface IConGroupControllerImplementation : IPermissionController
{
bool CanCommand(IPlayerSession session, string cmdName);
bool CanAdminPlace(IPlayerSession session);
bool CanScript(IPlayerSession session);
bool CanAdminMenu(IPlayerSession session);
bool CanAdminReloadPrototypes(IPlayerSession session);
bool CanCommand(ICommonSession session, string cmdName);
bool CanAdminPlace(ICommonSession session);
bool CanScript(ICommonSession session);
bool CanAdminMenu(ICommonSession session);
bool CanAdminReloadPrototypes(ICommonSession session);
}
}

View File

@@ -7,7 +7,7 @@ using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Utility;
@@ -41,7 +41,7 @@ namespace Robust.Server.Console
var msg = new MsgConCmd();
msg.Text = command;
NetManager.ServerSendMessage(msg, ((IPlayerSession)session).ConnectedClient);
NetManager.ServerSendMessage(msg, session.Channel);
}
/// <inheritdoc />
@@ -49,18 +49,12 @@ namespace Robust.Server.Console
{
var msg = new FormattedMessage();
msg.AddText(text);
if (session is IPlayerSession playerSession)
OutputText(playerSession, msg, false);
else
OutputText(null, msg, false);
OutputText(session, msg, false);
}
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
{
if (session is IPlayerSession playerSession)
OutputText(playerSession, msg, false);
else
OutputText(null, msg, false);
OutputText(session, msg, false);
}
/// <inheritdoc />
@@ -68,10 +62,7 @@ namespace Robust.Server.Console
{
var msg = new FormattedMessage();
msg.AddText(text);
if (session is IPlayerSession playerSession)
OutputText(playerSession, msg, true);
else
OutputText(null, msg, true);
OutputText(session, msg, true);
}
public bool IsCmdServer(IConsoleCommand cmd) => true;
@@ -155,7 +146,7 @@ namespace Robust.Server.Console
private bool ShellCanExecute(IConsoleShell shell, string cmdName)
{
return shell.Player == null || _groupController.CanCommand((IPlayerSession)shell.Player, cmdName);
return shell.Player == null || _groupController.CanCommand(shell.Player, cmdName);
}
private void HandleRegistrationRequest(INetChannel senderConnection)
@@ -213,7 +204,7 @@ namespace Robust.Server.Console
ExecuteCommand(session, text);
}
private void OutputText(IPlayerSession? session, FormattedMessage text, bool error)
private void OutputText(ICommonSession? session, FormattedMessage text, bool error)
{
if (session != null)
{

View File

@@ -1,5 +1,5 @@
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
@@ -8,6 +8,6 @@ namespace Robust.Server.GameObjects
public sealed partial class ActorComponent : Component
{
[ViewVariables]
public IPlayerSession PlayerSession { get; internal set; } = default!;
public ICommonSession PlayerSession { get; internal set; } = default!;
}
}

View File

@@ -1,12 +1,12 @@
using System.Collections.Generic;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Robust.Server.GameObjects
{
[RegisterComponent]
internal sealed partial class ViewSubscriberComponent : Component
{
internal readonly HashSet<IPlayerSession> SubscribedSessions = new();
internal readonly HashSet<ICommonSession> SubscribedSessions = new();
}
}

View File

@@ -4,6 +4,7 @@ using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects
@@ -30,7 +31,7 @@ namespace Robust.Server.GameObjects
/// <param name="player">The player to attach to the entity</param>
/// <param name="force">Whether to kick any existing players from the entity</param>
/// <returns>Whether the attach succeeded, or not.</returns>
public bool Attach(EntityUid? uid, IPlayerSession player, bool force = false)
public bool Attach(EntityUid? uid, ICommonSession player, bool force = false)
{
return Attach(uid, player, false, out _);
}
@@ -43,25 +44,26 @@ namespace Robust.Server.GameObjects
/// <param name="force">Whether to kick any existing players from the entity</param>
/// <param name="forceKicked">The player that was forcefully kicked, or null.</param>
/// <returns>Whether the attach succeeded, or not.</returns>
public bool Attach(EntityUid? entity, IPlayerSession player, bool force, out IPlayerSession? forceKicked)
public bool Attach(EntityUid? entity, ICommonSession player, bool force, out ICommonSession? forceKicked)
{
// Null by default.
forceKicked = null;
if (player.AttachedEntity == entity)
{
DebugTools.Assert(entity == null || HasComp<ActorComponent>(entity));
return true;
}
if (entity is not { } uid)
return Detach(player);
// Cannot attach to a deleted, nonexisting or terminating entity.
if (!TryComp(uid, out MetaDataComponent? meta) || meta.EntityLifeStage > EntityLifeStage.MapInitialized)
{
if (TerminatingOrDeleted(uid))
return false;
}
// Check if there was a player attached to the entity already...
if (EntityManager.TryGetComponent(uid, out ActorComponent? actor))
if (TryComp(uid, out ActorComponent? actor))
{
// If we're not forcing the attach, this fails.
if (!force)
@@ -69,22 +71,22 @@ namespace Robust.Server.GameObjects
// Set the event's force-kicked session before detaching it.
forceKicked = actor.PlayerSession;
Detach(uid, actor);
RemComp(uid, actor);
DebugTools.AssertNull(forceKicked.AttachedEntity);
}
// Detach from the currently attached entity.
if (!Detach(player))
return false;
Detach(player);
// We add the actor component.
actor = EntityManager.AddComponent<ActorComponent>(uid);
EntityManager.EnsureComponent<EyeComponent>(uid);
actor.PlayerSession = player;
player.SetAttachedEntity(uid);
_playerManager.SetAttachedEntity(player, uid);
DebugTools.Assert(player.AttachedEntity == entity);
// The player is fully attached now, raise an event!
RaiseLocalEvent(uid, new PlayerAttachedEvent(uid, player, forceKicked), true);
DebugTools.Assert(player.AttachedEntity == entity);
return true;
}
@@ -108,21 +110,33 @@ namespace Robust.Server.GameObjects
/// <param name="player">The player session that will be detached from any attached entities.</param>
/// <returns>Whether the player is now detached from any entities.
/// This returns true if the player wasn't attached to any entity.</returns>
public bool Detach(IPlayerSession player)
public bool Detach(ICommonSession player, ActorComponent? actor = null)
{
var uid = player.AttachedEntity;
return uid == null || Detach(uid.Value);
if (uid == null)
return true;
if (!Resolve(uid.Value, ref actor, false))
{
Log.Error($"Player {player} was attached to a deleted entity?");
((CommonSession) player).AttachedEntity = null;
return true;
}
RemComp(uid.Value, actor);
DebugTools.AssertNull(player.AttachedEntity);
return false;
}
private void OnActorShutdown(EntityUid entity, ActorComponent component, ComponentShutdown args)
{
component.PlayerSession.SetAttachedEntity(null);
_playerManager.SetAttachedEntity(component.PlayerSession, null);
// The player is fully detached now that the component has shut down.
RaiseLocalEvent(entity, new PlayerDetachedEvent(entity, component.PlayerSession), true);
}
public bool TryGetActorFromUserId(NetUserId? userId, [NotNullWhen(true)] out IPlayerSession? actor, [MaybeNullWhen(true)] out EntityUid? actorEntity)
public bool TryGetActorFromUserId(NetUserId? userId, [NotNullWhen(true)] out ICommonSession? actor, out EntityUid? actorEntity)
{
actor = null;
actorEntity = null;
@@ -143,14 +157,14 @@ namespace Robust.Server.GameObjects
public sealed class PlayerAttachedEvent : EntityEventArgs
{
public EntityUid Entity { get; }
public IPlayerSession Player { get; }
public ICommonSession Player { get; }
/// <summary>
/// The player session that was forcefully kicked from the entity, if any.
/// </summary>
public IPlayerSession? Kicked { get; }
public ICommonSession? Kicked { get; }
public PlayerAttachedEvent(EntityUid entity, IPlayerSession player, IPlayerSession? kicked = null)
public PlayerAttachedEvent(EntityUid entity, ICommonSession player, ICommonSession? kicked = null)
{
Entity = entity;
Player = player;
@@ -164,9 +178,9 @@ namespace Robust.Server.GameObjects
public sealed class PlayerDetachedEvent : EntityEventArgs
{
public EntityUid Entity { get; }
public IPlayerSession Player { get; }
public ICommonSession Player { get; }
public PlayerDetachedEvent(EntityUid entity, IPlayerSession player)
public PlayerDetachedEvent(EntityUid entity, ICommonSession player)
{
Entity = entity;
Player = player;

View File

@@ -6,7 +6,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.GameObjects;
[UsedImplicitly]

View File

@@ -5,6 +5,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Robust.Server.GameObjects
{
@@ -15,10 +16,10 @@ namespace Robust.Server.GameObjects
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly Dictionary<IPlayerSession, IPlayerCommandStates> _playerInputs = new();
private readonly Dictionary<ICommonSession, IPlayerCommandStates> _playerInputs = new();
private readonly Dictionary<IPlayerSession, uint> _lastProcessedInputCmd = new();
private readonly Dictionary<ICommonSession, uint> _lastProcessedInputCmd = new();
/// <inheritdoc />
public override void Initialize()
@@ -48,7 +49,7 @@ namespace Robust.Server.GameObjects
if (!Enum.IsDefined(typeof(BoundKeyState), msg.State))
return;
var session = (IPlayerSession) eventArgs.SenderSession;
var session = eventArgs.SenderSession;
if (_lastProcessedInputCmd[session] < msg.InputSequence)
_lastProcessedInputCmd[session] = msg.InputSequence;
@@ -65,12 +66,12 @@ namespace Robust.Server.GameObjects
}
}
public IPlayerCommandStates GetInputStates(IPlayerSession session)
public IPlayerCommandStates GetInputStates(ICommonSession session)
{
return _playerInputs[session];
}
public uint GetLastInputCommand(IPlayerSession session)
public uint GetLastInputCommand(ICommonSession session)
{
return _lastProcessedInputCmd[session];
}

View File

@@ -7,7 +7,7 @@ using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects
@@ -194,7 +194,7 @@ namespace Robust.Server.GameObjects
return bui.SubscribedSessions.Count > 0;
}
public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, IPlayerSession session, UserInterfaceComponent? ui = null)
public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
@@ -218,7 +218,7 @@ namespace Robust.Server.GameObjects
public bool TrySetUiState(EntityUid uid,
Enum uiKey,
BoundUserInterfaceState state,
IPlayerSession? session = null,
ICommonSession? session = null,
UserInterfaceComponent? ui = null,
bool clearOverrides = true)
{
@@ -284,7 +284,7 @@ namespace Robust.Server.GameObjects
#region Open
public bool TryOpen(EntityUid uid, Enum uiKey, IPlayerSession session, UserInterfaceComponent? ui = null)
public bool TryOpen(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
@@ -319,7 +319,7 @@ namespace Robust.Server.GameObjects
#endregion
#region Close
public bool TryClose(EntityUid uid, Enum uiKey, IPlayerSession session, UserInterfaceComponent? ui = null)
public bool TryClose(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;

View File

@@ -1,5 +1,5 @@
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Robust.Server.GameObjects
{
@@ -18,7 +18,7 @@ namespace Robust.Server.GameObjects
/// <summary>
/// Subscribes the session to get PVS updates from the point of view of the specified entity.
/// </summary>
public void AddViewSubscriber(EntityUid uid, IPlayerSession session)
public void AddViewSubscriber(EntityUid uid, ICommonSession session)
{
// If the entity doesn't have the component, it will be added.
var viewSubscriber = EntityManager.EnsureComponent<ViewSubscriberComponent>(uid);
@@ -27,7 +27,7 @@ namespace Robust.Server.GameObjects
return; // Already subscribed, do nothing else.
viewSubscriber.SubscribedSessions.Add(session);
session.AddViewSubscription(uid);
session.ViewSubscriptions.Add(uid);
RaiseLocalEvent(uid, new ViewSubscriberAddedEvent(uid, session), true);
}
@@ -35,7 +35,7 @@ namespace Robust.Server.GameObjects
/// <summary>
/// Unsubscribes the session from getting PVS updates from the point of view of the specified entity.
/// </summary>
public void RemoveViewSubscriber(EntityUid uid, IPlayerSession session)
public void RemoveViewSubscriber(EntityUid uid, ICommonSession session)
{
if(!EntityManager.TryGetComponent(uid, out ViewSubscriberComponent? viewSubscriber))
return; // Entity didn't have any subscriptions, do nothing.
@@ -43,7 +43,7 @@ namespace Robust.Server.GameObjects
if (!viewSubscriber.SubscribedSessions.Remove(session))
return; // Session wasn't subscribed, do nothing.
session.RemoveViewSubscription(uid);
session.ViewSubscriptions.Remove(uid);
RaiseLocalEvent(uid, new ViewSubscriberRemovedEvent(uid, session), true);
}
@@ -51,7 +51,7 @@ namespace Robust.Server.GameObjects
{
foreach (var session in component.SubscribedSessions)
{
session.RemoveViewSubscription(uid);
session.ViewSubscriptions.Remove(uid);
}
}
}
@@ -62,9 +62,9 @@ namespace Robust.Server.GameObjects
public sealed class ViewSubscriberAddedEvent : EntityEventArgs
{
public EntityUid View { get; }
public IPlayerSession Subscriber { get; }
public ICommonSession Subscriber { get; }
public ViewSubscriberAddedEvent(EntityUid view, IPlayerSession subscriber)
public ViewSubscriberAddedEvent(EntityUid view, ICommonSession subscriber)
{
View = view;
Subscriber = subscriber;
@@ -78,9 +78,9 @@ namespace Robust.Server.GameObjects
public sealed class ViewSubscriberRemovedEvent : EntityEventArgs
{
public EntityUid View { get; }
public IPlayerSession Subscriber { get; }
public ICommonSession Subscriber { get; }
public ViewSubscriberRemovedEvent(EntityUid view, IPlayerSession subscriber)
public ViewSubscriberRemovedEvent(EntityUid view, ICommonSession subscriber)
{
View = view;
Subscriber = subscriber;

View File

@@ -1,12 +1,10 @@
using System.Collections.Generic;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
using Robust.Shared.Player;
namespace Robust.Server.GameObjects
{
public interface IServerEntityNetworkManager : IEntityNetworkManager
{
uint GetLastMessageSequence(IPlayerSession session);
uint GetLastMessageSequence(ICommonSession session);
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Prometheus;
using Robust.Server.Player;
@@ -15,6 +14,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Replays;
using Robust.Shared.Timing;
@@ -131,7 +131,7 @@ namespace Robust.Server.GameObjects
private readonly PriorityQueue<MsgEntity> _queue = new(new MessageSequenceComparer());
private readonly Dictionary<IPlayerSession, uint> _lastProcessedSequencesCmd =
private readonly Dictionary<ICommonSession, uint> _lastProcessedSequencesCmd =
new();
private bool _logLateMsgs;
@@ -162,7 +162,7 @@ namespace Robust.Server.GameObjects
EntitiesCount.Set(Entities.Count);
}
public uint GetLastMessageSequence(IPlayerSession session)
public uint GetLastMessageSequence(ICommonSession session)
{
return _lastProcessedSequencesCmd[session];
}

View File

@@ -1,6 +1,6 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Robust.Server.GameStates

View File

@@ -8,7 +8,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;

View File

@@ -1,5 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Server.GameStates;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -68,7 +68,7 @@ namespace Robust.Server.GameStates
return true;
}
public void CleanupDirty(IEnumerable<IPlayerSession> sessions)
public void CleanupDirty(IEnumerable<ICommonSession> sessions)
{
if (!CullingEnabled)
{

View File

@@ -15,7 +15,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -433,7 +433,7 @@ internal sealed partial class PvsSystem : EntitySystem
#endregion
public (List<(int, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(IPlayerSession[] sessions)
public (List<(int, IChunkIndexLocation)> , HashSet<int>[], EntityUid[][] viewers) GetChunks(ICommonSession[] sessions)
{
var playerChunks = new HashSet<int>[sessions.Length];
var viewerEntities = new EntityUid[sessions.Length][];
@@ -702,7 +702,7 @@ internal sealed partial class PvsSystem : EntitySystem
}
internal (List<EntityState>? updates, List<NetEntity>? deletions, List<NetEntity>? leftPvs, GameTick fromTick)
CalculateEntityStates(IPlayerSession session,
CalculateEntityStates(ICommonSession session,
GameTick fromTick,
GameTick toTick,
(Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] chunks,
@@ -710,8 +710,8 @@ internal sealed partial class PvsSystem : EntitySystem
EntityUid[] viewers)
{
DebugTools.Assert(session.Status == SessionStatus.InGame);
var newEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSEntityBudget);
var enteredEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSEntityEnterBudget);
var newEntityBudget = _netConfigManager.GetClientCVar(session.Channel, CVars.NetPVSEntityBudget);
var enteredEntityBudget = _netConfigManager.GetClientCVar(session.Channel, CVars.NetPVSEntityEnterBudget);
var newEntityCount = 0;
var enteredEntityCount = 0;
var sessionData = PlayerData[session];
@@ -1315,11 +1315,11 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
private EntityUid[] GetSessionViewers(ICommonSession session)
{
if (session.Status != SessionStatus.InGame || session is not IPlayerSession sess)
if (session.Status != SessionStatus.InGame)
return Array.Empty<EntityUid>();
// Fast path
if (sess.ViewSubscriptionCount == 0)
if (session.ViewSubscriptions.Count == 0)
{
if (session.AttachedEntity == null)
return Array.Empty<EntityUid>();
@@ -1331,11 +1331,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
if (session.AttachedEntity != null)
viewers.Add(session.AttachedEntity.Value);
foreach (var uid in sess.ViewSubscriptions)
{
viewers.Add(uid);
}
viewers.UnionWith(session.ViewSubscriptions);
return viewers.ToArray();
}
@@ -1425,7 +1421,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
[ByRefEvent]
public struct ExpandPvsEvent
{
public readonly IPlayerSession Session;
public readonly ICommonSession Session;
/// <summary>
/// List of entities that will get added to this session's PVS set.
@@ -1438,7 +1434,7 @@ public struct ExpandPvsEvent
/// </summary>
public List<EntityUid>? RecursiveEntities;
public ExpandPvsEvent(IPlayerSession session)
public ExpandPvsEvent(ICommonSession session)
{
Session = session;
}

View File

@@ -26,7 +26,7 @@ using Prometheus;
using Robust.Server.Replays;
using Robust.Shared.Console;
using Robust.Shared.Map.Components;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Server.GameStates
{
@@ -168,7 +168,7 @@ Oldest acked clients: {string.Join(", ", players)}
/// <inheritdoc />
public void SendGameStateUpdate()
{
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
var players = _playerManager.Sessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
// Update entity positions in PVS chunks/collections
// TODO disable processing if culling is disabled? Need to check if toggling PVS breaks anything.
@@ -224,7 +224,7 @@ Oldest acked clients: {string.Join(", ", players)}
_pvs.CullDeletionHistory(oldestAck);
}
private GameTick SendStates(IPlayerSession[] players, PvsData? pvsData)
private GameTick SendStates(ICommonSession[] players, PvsData? pvsData)
{
var inputSystem = _systemManager.GetEntitySystem<InputSystem>();
var opts = new ParallelOptions {MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount};
@@ -265,7 +265,7 @@ Oldest acked clients: {string.Join(", ", players)}
public (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] ChunkCache;
}
private PvsData? GetPVSData(IPlayerSession[] players)
private PvsData? GetPVSData(ICommonSession[] players)
{
var (chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
const int ChunkBatchSize = 2;
@@ -319,11 +319,11 @@ Oldest acked clients: {string.Join(", ", players)}
private void SendStateUpdate(int i,
PvsThreadResources resources,
InputSystem inputSystem,
IPlayerSession session,
ICommonSession session,
PvsData? pvsData,
ref uint oldestAckValue)
{
var channel = session.ConnectedClient;
var channel = session.Channel;
var sessionData = _pvs.PlayerData[session];
var lastAck = sessionData.LastReceivedAck;
List<NetEntity>? leftPvs = null;

View File

@@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
@@ -15,7 +14,7 @@ using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -92,9 +91,7 @@ namespace Robust.Server.Physics
private void OnDebugRequest(RequestGridNodesMessage msg, EntitySessionEventArgs args)
{
var pSession = (PlayerSession) args.SenderSession;
if (!_conGroup.CanCommand(pSession, ShowGridNodesCommand)) return;
if (!_conGroup.CanCommand(args.SenderSession, ShowGridNodesCommand)) return;
AddDebugSubscriber(args.SenderSession);
}

View File

@@ -1,88 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Enums;
using Robust.Shared.GameStates;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Timing;
namespace Robust.Server.Player
namespace Robust.Server.Player;
/// <summary>
/// Manages each players session when connected to the server.
/// </summary>
public interface IPlayerManager : ISharedPlayerManager
{
/// <summary>
/// Manages each players session when connected to the server.
/// </summary>
public interface IPlayerManager : ISharedPlayerManager
{
BoundKeyMap KeyMap { get; }
/// <summary>
/// Same as the common sessions, but the server version.
/// </summary>
IEnumerable<IPlayerSession> ServerSessions { get; }
/// <summary>
/// Raised when the <see cref="SessionStatus" /> of a <see cref="IPlayerSession" /> is changed.
/// </summary>
event EventHandler<SessionStatusEventArgs> PlayerStatusChanged;
/// <summary>
/// Initializes the manager.
/// </summary>
/// <param name="maxPlayers">Maximum number of players that can connect to this server at one time.</param>
void Initialize(int maxPlayers);
void Shutdown();
bool TryGetSessionByUsername(string username, [NotNullWhen(true)] out IPlayerSession? session);
/// <summary>
/// Returns the client session of the networkId.
/// </summary>
/// <returns></returns>
IPlayerSession GetSessionByUserId(NetUserId index);
IPlayerSession GetSessionByChannel(INetChannel channel);
bool TryGetSessionByChannel(INetChannel channel, [NotNullWhen(true)] out IPlayerSession? session);
bool TryGetSessionById(NetUserId userId, [NotNullWhen(true)] out IPlayerSession? session);
/// <summary>
/// Checks to see if a PlayerIndex is a valid session.
/// </summary>
bool ValidSessionId(NetUserId index);
IPlayerData GetPlayerData(NetUserId userId);
bool TryGetPlayerData(NetUserId userId, [NotNullWhen(true)] out IPlayerData? data);
bool TryGetPlayerDataByUsername(string userName, [NotNullWhen(true)] out IPlayerData? data);
bool HasPlayerData(NetUserId userId);
/// <summary>
/// Tries to get the user ID of the user with the specified username.
/// </summary>
/// <remarks>
/// This only works if this user has already connected once before during this server run.
/// It does still work if the user has since disconnected.
/// </remarks>
bool TryGetUserId(string userName, out NetUserId userId);
IEnumerable<IPlayerData> GetAllPlayerData();
[Obsolete]
void DetachAll();
[Obsolete("Use player Filter or Inline me!")]
List<IPlayerSession> GetPlayersInRange(MapCoordinates worldPos, int range);
[Obsolete("Use player Filter or Inline me!")]
List<IPlayerSession> GetPlayersInRange(EntityCoordinates worldPos, int range);
[Obsolete("Use player Filter or Inline me!")]
List<IPlayerSession> GetPlayersBy(Func<IPlayerSession, bool> predicate);
[Obsolete("Use player Filter or Inline me!")]
List<IPlayerSession> GetAllPlayers();
List<PlayerState>? GetPlayerStates(GameTick fromTick);
}
}
BoundKeyMap KeyMap { get; }
}

View File

@@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.Player
{
public interface IPlayerSession : ICommonSession
{
DateTime ConnectedTime { get; }
event EventHandler<SessionStatusEventArgs> PlayerStatusChanged;
void JoinGame();
LoginType AuthType { get; }
/// <summary>
/// Attaches this player to an entity.
/// NOTE: The content pack almost certainly has an alternative for this.
/// Do not call this directly for most content code.
/// </summary>
/// <param name="entity">The entity to attach to.</param>
[Obsolete("Use ActorSystem.Attach() instead.")]
void AttachToEntity(EntityUid? entity);
/// <summary>
/// Attaches this player to an entity.
/// NOTE: The content pack almost certainly has an alternative for this.
/// Do not call this directly for most content code.
/// </summary>
/// <param name="uid">The entity to attach to.</param>
[Obsolete("Use ActorSystem.Attach() instead.")]
void AttachToEntity(EntityUid uid);
/// <summary>
/// Detaches this player from an entity.
/// NOTE: The content pack almost certainly has an alternative for this.
/// Do not call this directly for most content code.
/// </summary>
[Obsolete("Use ActorSystem.Detach() instead.")]
void DetachFromEntity();
void OnConnect();
void OnDisconnect();
IReadOnlySet<EntityUid> ViewSubscriptions { get; }
int ViewSubscriptionCount { get; }
/// <summary>
/// Persistent data for this player.
/// </summary>
IPlayerData Data { get; }
/// <summary>
/// Internal method to set <see cref="ICommonSession.AttachedEntity"/> and update the player's status.
/// Do NOT use this unless you know what you're doing, you probably want <see cref="AttachToEntity"/>
/// and <see cref="DetachFromEntity"/> instead.
/// </summary>
internal void SetAttachedEntity(EntityUid? entity);
/// <summary>
/// Internal method to add an entity Uid to <see cref="ViewSubscriptions"/>.
/// Do NOT use this outside of <see cref="ViewSubscriberSystem"/>.
/// </summary>
internal void AddViewSubscription(EntityUid eye);
/// <summary>
/// Internal method to remove an entity Uid from <see cref="ViewSubscriptions"/>.
/// Do NOT use this outside of <see cref="ViewSubscriberSystem"/>.
/// </summary>
internal void RemoveViewSubscription(EntityUid eye);
}
}

View File

@@ -1,24 +0,0 @@
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Player
{
sealed class PlayerData : IPlayerData
{
public PlayerData(NetUserId userId, string userName)
{
UserId = userId;
UserName = userName;
}
[ViewVariables]
public NetUserId UserId { get; }
[ViewVariables]
public string UserName { get; }
[ViewVariables]
public object? ContentDataUncast { get; set; }
}
}

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Prometheus;
using Robust.Server.Configuration;
@@ -12,22 +11,19 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Player
{
/// <summary>
/// This class will manage connected player sessions.
/// </summary>
public sealed class PlayerManager : IPlayerManager
internal sealed class PlayerManager : SharedPlayerManager, IPlayerManager
{
private static readonly Gauge PlayerCountMetric = Metrics
.CreateGauge("robust_player_count", "Number of players on the server.");
@@ -41,79 +37,13 @@ namespace Robust.Server.Player
public BoundKeyMap KeyMap { get; private set; } = default!;
private GameTick _lastStateUpdate;
private readonly ReaderWriterLockSlim _sessionsLock = new();
/// <summary>
/// Active sessions of connected clients to the server.
/// </summary>
[ViewVariables]
private readonly Dictionary<NetUserId, PlayerSession> _sessions = new();
[ViewVariables]
private readonly Dictionary<NetUserId, PlayerData> _playerData = new();
[ViewVariables]
private readonly Dictionary<string, NetUserId> _userIdMap = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions => Sessions;
/// <inheritdoc />
public IEnumerable<ICommonSession> Sessions
{
get
{
_sessionsLock.EnterReadLock();
try
{
return _sessions.Values;
}
finally
{
_sessionsLock.ExitReadLock();
}
}
}
public IEnumerable<IPlayerSession> ServerSessions => Sessions.Cast<IPlayerSession>();
/// <inheritdoc />
[ViewVariables]
public int PlayerCount
{
get
{
_sessionsLock.EnterReadLock();
try
{
return _sessions.Count;
}
finally
{
_sessionsLock.ExitReadLock();
}
}
}
/// <inheritdoc />
[ViewVariables]
public int MaxPlayers { get; private set; } = 32;
public ICommonSession? LocalSession => null;
/// <inheritdoc />
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <inheritdoc />
public void Initialize(int maxPlayers)
public override void Initialize(int maxPlayers)
{
base.Initialize(maxPlayers);
KeyMap = new BoundKeyMap(_reflectionManager);
KeyMap.PopulateKeyFunctionsMap();
MaxPlayers = maxPlayers;
_network.RegisterNetMessage<MsgPlayerListReq>(HandlePlayerListReq);
_network.RegisterNetMessage<MsgPlayerList>();
_network.RegisterNetMessage<MsgSyncTimeBase>();
@@ -123,8 +53,9 @@ namespace Robust.Server.Player
_network.Disconnect += EndSession;
}
public void Shutdown()
public override void Shutdown()
{
base.Shutdown();
KeyMap = default!;
_network.Connecting -= OnConnecting;
@@ -132,250 +63,6 @@ namespace Robust.Server.Player
_network.Disconnect -= EndSession;
}
public bool TryGetSessionByUsername(string username, [NotNullWhen(true)] out IPlayerSession? session)
{
if (!_userIdMap.TryGetValue(username, out var userId))
{
session = null;
return false;
}
_sessionsLock.EnterReadLock();
try
{
if (_sessions.TryGetValue(userId, out var iSession))
{
session = iSession;
return true;
}
}
finally
{
_sessionsLock.ExitReadLock();
}
session = null;
return false;
}
IPlayerSession IPlayerManager.GetSessionByChannel(INetChannel channel) => GetSessionByChannel(channel);
public bool TryGetSessionByChannel(INetChannel channel, [NotNullWhen(true)] out IPlayerSession? session)
{
_sessionsLock.EnterReadLock();
try
{
// Should only be one session per client. Returns that session, in theory.
if (_sessions.TryGetValue(channel.UserId, out var concrete))
{
session = concrete;
return true;
}
session = null;
return false;
}
finally
{
_sessionsLock.ExitReadLock();
}
}
private PlayerSession GetSessionByChannel(INetChannel channel)
{
_sessionsLock.EnterReadLock();
try
{
// Should only be one session per client. Returns that session, in theory.
return _sessions[channel.UserId];
}
finally
{
_sessionsLock.ExitReadLock();
}
}
/// <inheritdoc />
public IPlayerSession GetSessionByUserId(NetUserId index)
{
_sessionsLock.EnterReadLock();
try
{
return _sessions[index];
}
finally
{
_sessionsLock.ExitReadLock();
}
}
public bool ValidSessionId(NetUserId index)
{
_sessionsLock.EnterReadLock();
try
{
return _sessions.ContainsKey(index);
}
finally
{
_sessionsLock.ExitReadLock();
}
}
public bool TryGetSessionById(NetUserId userId, [NotNullWhen(true)] out IPlayerSession? session)
{
_sessionsLock.EnterReadLock();
try
{
if (_sessions.TryGetValue(userId, out var playerSession))
{
session = playerSession;
return true;
}
}
finally
{
_sessionsLock.ExitReadLock();
}
session = default;
return false;
}
/// <summary>
/// Causes all sessions to switch from the lobby to the the game.
/// </summary>
[Obsolete]
public void SendJoinGameToAll()
{
_sessionsLock.EnterReadLock();
try
{
foreach (var s in _sessions.Values)
{
s.JoinGame();
}
}
finally
{
_sessionsLock.ExitReadLock();
}
}
public bool TryGetUserId(string userName, out NetUserId userId)
{
return _userIdMap.TryGetValue(userName, out userId);
}
public IEnumerable<IPlayerData> GetAllPlayerData()
{
return _playerData.Values;
}
/// <summary>
/// Causes all sessions to detach from their entity.
/// </summary>
[Obsolete]
public void DetachAll()
{
_sessionsLock.EnterReadLock();
try
{
foreach (var s in _sessions.Values)
{
s.DetachFromEntity();
}
}
finally
{
_sessionsLock.ExitReadLock();
}
}
/// <summary>
/// Gets all players inside of a circle.
/// </summary>
/// <param name="worldPos">Position of the circle in world-space.</param>
/// <param name="range">Radius of the circle in world units.</param>
/// <returns></returns>
[Obsolete("Use player Filter or Inline me!")]
public List<IPlayerSession> GetPlayersInRange(MapCoordinates worldPos, int range)
{
return Filter.Empty()
.AddInRange(worldPos, range)
.Recipients
.Cast<IPlayerSession>()
.ToList();
}
/// <summary>
/// Gets all players inside of a circle.
/// </summary>
/// <param name="worldPos">Position of the circle in world-space.</param>
/// <param name="range">Radius of the circle in world units.</param>
/// <returns></returns>
[Obsolete("Use player Filter or Inline me!")]
public List<IPlayerSession> GetPlayersInRange(EntityCoordinates worldPos, int range)
{
return Filter.Empty()
.AddInRange(worldPos.ToMap(_entityManager), range)
.Recipients
.Cast<IPlayerSession>()
.ToList();
}
[Obsolete("Use player Filter or Inline me!")]
public List<IPlayerSession> GetPlayersBy(Func<IPlayerSession, bool> predicate)
{
return Filter.Empty()
.AddWhere((session => predicate((IPlayerSession)session)))
.Recipients
.Cast<IPlayerSession>()
.ToList();
}
/// <summary>
/// Gets all players in the server.
/// </summary>
/// <returns></returns>
[Obsolete("Use player Filter or Inline me!")]
public List<IPlayerSession> GetAllPlayers()
{
return ServerSessions.ToList();
}
/// <summary>
/// Gets all player states in the server.
/// </summary>
/// <param name="fromTick"></param>
/// <returns></returns>
public List<PlayerState>? GetPlayerStates(GameTick fromTick)
{
if (_lastStateUpdate < fromTick)
{
return null;
}
_sessionsLock.EnterReadLock();
try
{
#if FULL_RELEASE
return _sessions.Values
.Select(s => s.PlayerState)
.ToList();
#else
// Integration tests need to clone data before "sending" it to the client. Otherwise they reference the
// same object.
return _sessions.Values
.Select(s => s.PlayerState.Clone())
.ToList();
#endif
}
finally
{
_sessionsLock.ExitReadLock();
}
}
private Task OnConnecting(NetConnectingArgs args)
{
if (PlayerCount >= _baseServer.MaxPlayers)
@@ -393,30 +80,10 @@ namespace Robust.Server.Player
/// <param name="args"></param>
private void NewSession(object? sender, NetChannelArgs args)
{
if (!_playerData.TryGetValue(args.Channel.UserId, out var data))
{
data = new PlayerData(args.Channel.UserId, args.Channel.UserName);
_playerData.Add(args.Channel.UserId, data);
}
_userIdMap[args.Channel.UserName] = args.Channel.UserId;
var session = new PlayerSession(this, args.Channel, data);
session.PlayerStatusChanged += (_, sessionArgs) => OnPlayerStatusChanged(session, sessionArgs.OldStatus, sessionArgs.NewStatus);
_sessionsLock.EnterWriteLock();
try
{
_sessions.Add(args.Channel.UserId, session);
}
finally
{
_sessionsLock.ExitWriteLock();
}
var session = CreateAndAddSession(args.Channel.UserId, args.Channel.UserName);
session.Channel = args.Channel;
PlayerCountMetric.Set(PlayerCount);
// Synchronize base time.
var msgTimeBase = new MsgSyncTimeBase();
(msgTimeBase.Time, msgTimeBase.Tick) = _timing.TimeBase;
@@ -425,11 +92,6 @@ namespace Robust.Server.Player
_cfg.SyncConnectingClient(args.Channel);
}
private void OnPlayerStatusChanged(IPlayerSession session, SessionStatus oldStatus, SessionStatus newStatus)
{
PlayerStatusChanged?.Invoke(this, new SessionStatusEventArgs(session, oldStatus, newStatus));
}
/// <summary>
/// Ends a clients session, and disconnects them.
/// </summary>
@@ -441,20 +103,19 @@ namespace Robust.Server.Player
}
// make sure nothing got messed up during the life of the session
DebugTools.Assert(session.ConnectedClient == args.Channel);
DebugTools.Assert(session.Channel == args.Channel);
//Detach the entity and (don't)delete it.
session.OnDisconnect();
_sessionsLock.EnterWriteLock();
try
SetStatus(session, SessionStatus.Disconnected);
if (session.AttachedEntity != null)
EntManager.System<ActorSystem>().Detach(session.AttachedEntity.Value);
var viewSys = EntManager.System<ViewSubscriberSystem>();
foreach (var eye in session.ViewSubscriptions.ToArray())
{
_sessions.Remove(session.UserId);
}
finally
{
_sessionsLock.ExitWriteLock();
viewSys.RemoveViewSubscriber(eye, session);
}
RemoveSession(session.UserId);
PlayerCountMetric.Set(PlayerCount);
Dirty();
}
@@ -469,17 +130,18 @@ namespace Robust.Server.Player
// This is done before the packet is built, so that the client
// can see themselves Connected.
var session = GetSessionByChannel(channel);
session.OnConnect();
session.ConnectedTime = DateTime.UtcNow;
SetStatus(session, SessionStatus.Connected);
var list = new List<PlayerState>();
var list = new List<SessionState>();
foreach (var client in players)
{
var info = new PlayerState
var info = new SessionState
{
UserId = client.UserId,
Name = client.Name,
Status = client.Status,
Ping = client.ConnectedClient.Ping
Ping = client.Channel!.Ping
};
list.Add(info);
}
@@ -489,46 +151,7 @@ namespace Robust.Server.Player
channel.SendMessage(netMsg);
}
public void Dirty()
{
_lastStateUpdate = _timing.CurTick;
}
public IPlayerData GetPlayerData(NetUserId userId)
{
return _playerData[userId];
}
public bool TryGetPlayerData(NetUserId userId, [NotNullWhen(true)] out IPlayerData? data)
{
if (_playerData.TryGetValue(userId, out var playerData))
{
data = playerData;
return true;
}
data = default;
return false;
}
public bool TryGetPlayerDataByUsername(string userName, [NotNullWhen(true)] out IPlayerData? data)
{
if (!_userIdMap.TryGetValue(userName, out var userId))
{
data = null;
return false;
}
// PlayerData is initialized together with the _userIdMap so we can trust that it'll be present.
data = _playerData[userId];
return true;
}
public bool HasPlayerData(NetUserId userId)
{
return _playerData.ContainsKey(userId);
}
public bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
public override bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session)
{
if (!_entityManager.TryGetComponent(uid, out ActorComponent? actor))
{
@@ -540,18 +163,4 @@ namespace Robust.Server.Player
return true;
}
}
public sealed class SessionStatusEventArgs : EventArgs
{
public SessionStatusEventArgs(IPlayerSession session, SessionStatus oldStatus, SessionStatus newStatus)
{
Session = session;
OldStatus = oldStatus;
NewStatus = newStatus;
}
public IPlayerSession Session { get; }
public SessionStatus OldStatus { get; }
public SessionStatus NewStatus { get; }
}
}

View File

@@ -1,224 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Player
{
/// <summary>
/// This is the session of a connected client.
/// </summary>
internal sealed class PlayerSession : IPlayerSession
{
private readonly PlayerManager _playerManager;
public readonly PlayerState PlayerState;
private readonly HashSet<EntityUid> _viewSubscriptions = new();
public PlayerSession(PlayerManager playerManager, INetChannel client, PlayerData data)
{
_playerManager = playerManager;
UserId = client.UserId;
Name = client.UserName;
_data = data;
PlayerState = new PlayerState
{
UserId = client.UserId,
};
ConnectedClient = client;
UpdatePlayerState();
}
[ViewVariables] public IReadOnlySet<EntityUid> ViewSubscriptions => _viewSubscriptions;
public int ViewSubscriptionCount => _viewSubscriptions.Count;
[ViewVariables] public INetChannel ConnectedClient { get; }
/// <inheritdoc />
[ViewVariables] public EntityUid? AttachedEntity { get; set; }
private SessionStatus _status = SessionStatus.Connecting;
[ViewVariables]
internal string Name { get; set; }
/// <inheritdoc />
string ICommonSession.Name
{
get => this.Name;
set => this.Name = value;
}
[ViewVariables]
internal short Ping
{
get => ConnectedClient.Ping;
set => throw new NotSupportedException();
}
short ICommonSession.Ping
{
get => this.Ping;
set => this.Ping = value;
}
[ViewVariables]
internal SessionStatus Status
{
get => _status;
set
{
if (_status == value)
return;
var old = _status;
_status = value;
UpdatePlayerState();
PlayerStatusChanged?.Invoke(this, new SessionStatusEventArgs(this, old, value));
}
}
/// <inheritdoc />
SessionStatus ICommonSession.Status
{
get => this.Status;
set => this.Status = value;
}
/// <inheritdoc />
public DateTime ConnectedTime { get; private set; }
[ViewVariables(VVAccess.ReadWrite)]
public int VisibilityMask { get; set; } = 1;
/// <inheritdoc />
[ViewVariables]
public NetUserId UserId { get; }
private readonly PlayerData _data;
[ViewVariables] public IPlayerData Data => _data;
/// <inheritdoc />
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <inheritdoc />
[Obsolete("Use ActorSystem.Attach() instead.")]
public void AttachToEntity(EntityUid? entity)
{
EntitySystem.Get<ActorSystem>().Attach(entity, this);
}
/// <inheritdoc />
[Obsolete("Use ActorSystem.Attach() instead.")]
public void AttachToEntity(EntityUid uid)
{
EntitySystem.Get<ActorSystem>().Attach(uid, this);
}
/// <inheritdoc />
[Obsolete("Use ActorSystem.Detach() instead.")]
public void DetachFromEntity()
{
if (AttachedEntity == null)
return;
if (IoCManager.Resolve<IEntityManager>().Deleted(AttachedEntity!.Value))
{
Logger.Error($"Player \"{this}\" was attached to an entity that was deleted. THIS SHOULD NEVER HAPPEN, BUT DOES.");
// We can't contact ActorSystem because trying to fire an entity event would crash.
// Work around it.
AttachedEntity = null;
UpdatePlayerState();
return;
}
EntitySystem.Get<ActorSystem>().Detach(AttachedEntity.Value);
}
/// <inheritdoc />
public void OnConnect()
{
ConnectedTime = DateTime.UtcNow;
Status = SessionStatus.Connected;
UpdatePlayerState();
}
/// <inheritdoc />
public void OnDisconnect()
{
Status = SessionStatus.Disconnected;
UnsubscribeAllViews();
DetachFromEntity();
UpdatePlayerState();
}
/// <summary>
/// Causes the session to switch from the lobby to the game.
/// </summary>
public void JoinGame()
{
if (ConnectedClient == null || Status == SessionStatus.InGame)
return;
Status = SessionStatus.InGame;
UpdatePlayerState();
}
public LoginType AuthType => ConnectedClient.AuthType;
/// <inheritdoc />
void IPlayerSession.SetAttachedEntity(EntityUid? entity)
{
AttachedEntity = entity;
UpdatePlayerState();
}
void IPlayerSession.AddViewSubscription(EntityUid eye)
{
_viewSubscriptions.Add(eye);
}
void IPlayerSession.RemoveViewSubscription(EntityUid eye)
{
_viewSubscriptions.Remove(eye);
}
private void UnsubscribeAllViews()
{
var viewSubscriberSystem = EntitySystem.Get<ViewSubscriberSystem>();
foreach (var eye in _viewSubscriptions)
{
viewSubscriberSystem.RemoveViewSubscriber(eye, this);
}
}
private void UpdatePlayerState()
{
PlayerState.Status = Status;
PlayerState.Name = Name;
PlayerState.ControlledEntity = IoCManager.Resolve<IEntityManager>().GetNetEntity(AttachedEntity);
_playerManager.Dirty();
}
/// <inheritdoc />
public override string ToString()
{
return Name;
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
@@ -20,6 +19,7 @@ using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Utility;
@@ -38,7 +38,7 @@ namespace Robust.Server.Scripting
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly ILogManager _logManager = default!;
readonly Dictionary<IPlayerSession, Dictionary<int, ScriptInstance>> _instances =
readonly Dictionary<ICommonSession, Dictionary<int, ScriptInstance>> _instances =
new();
private ISawmill _sawmill = default!;

View File

@@ -22,7 +22,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Toolshed;
namespace Robust.Server.Toolshed.Commands.Players;
@@ -17,7 +17,7 @@ public sealed class ActorCommand : ToolshedCommand
}
[CommandImplementation("session")]
public IEnumerable<IPlayerSession> Session([PipedArgument] IEnumerable<EntityUid> input)
public IEnumerable<ICommonSession> Session([PipedArgument] IEnumerable<EntityUid> input)
{
return input.Where(HasComp<ActorComponent>).Select(x => Comp<ActorComponent>(x).PlayerSession);
}

View File

@@ -7,7 +7,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Utility;
@@ -20,18 +20,18 @@ public sealed class PlayerCommand : ToolshedCommand
[Dependency] private readonly IPlayerManager _playerManager = default!;
[CommandImplementation("list")]
public IEnumerable<IPlayerSession> Players()
=> _playerManager.ServerSessions;
public IEnumerable<ICommonSession> Players()
=> _playerManager.Sessions;
[CommandImplementation("self")]
public IPlayerSession Self([CommandInvocationContext] IInvocationContext ctx)
public ICommonSession Self([CommandInvocationContext] IInvocationContext ctx)
{
if (ctx.Session is null)
{
ctx.ReportError(new NotForServerConsoleError());
}
return (IPlayerSession)ctx.Session!;
return ctx.Session!;
}
[CommandImplementation("imm")]
@@ -59,13 +59,13 @@ public sealed class PlayerCommand : ToolshedCommand
}
[CommandImplementation("entity")]
public IEnumerable<EntityUid> GetPlayerEntity([PipedArgument] IEnumerable<IPlayerSession> sessions)
public IEnumerable<EntityUid> GetPlayerEntity([PipedArgument] IEnumerable<ICommonSession> sessions)
{
return sessions.Select(x => x.AttachedEntity).Where(x => x is not null).Cast<EntityUid>();
}
[CommandImplementation("entity")]
public EntityUid GetPlayerEntity([PipedArgument] IPlayerSession sessions)
public EntityUid GetPlayerEntity([PipedArgument] ICommonSession sessions)
{
return sessions.AttachedEntity ?? default;
}

View File

@@ -5,6 +5,7 @@ using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Upload;
using Robust.Shared.ViewVariables;
@@ -17,7 +18,7 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IConGroupController _controller = default!;
public event Action<IPlayerSession, NetworkResourceUploadMessage>? OnResourceUploaded;
public event Action<ICommonSession, NetworkResourceUploadMessage>? OnResourceUploaded;
[ViewVariables] public bool Enabled { get; private set; } = true;
[ViewVariables] public float SizeLimit { get; private set; }

View File

@@ -6,13 +6,13 @@ using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.ViewVariables
@@ -31,6 +31,8 @@ namespace Robust.Server.ViewVariables
private readonly Dictionary<uint, ViewVariablesSession>
_sessions = new();
private readonly Dictionary<NetUserId, List<uint>> _users = new();
private uint _nextSessionId = 1;
public override void Initialize()
@@ -44,6 +46,22 @@ namespace Robust.Server.ViewVariables
_netManager.RegisterNetMessage<MsgViewVariablesDenySession>();
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>();
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>();
_playerManager.PlayerStatusChanged += OnStatusChanged;
}
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Disconnected)
return;
if (!_users.TryGetValue(e.Session.UserId, out var vvSessions))
return;
foreach (var id in vvSessions)
{
_closeSession(id, false);
}
}
private void _msgCloseSession(MsgViewVariablesCloseSession message)
@@ -235,19 +253,13 @@ namespace Robust.Server.ViewVariables
_robustSerializer, _entityManager, Sawmill);
_sessions.Add(sessionId, session);
_users.GetOrNew(session.PlayerUser).Add(sessionId);
var allowMsg = new MsgViewVariablesOpenSession();
allowMsg.RequestId = message.RequestId;
allowMsg.SessionId = session.SessionId;
_netManager.ServerSendMessage(allowMsg, message.MsgChannel);
player.PlayerStatusChanged += (_, args) =>
{
if (args.NewStatus == SessionStatus.Disconnected)
{
_closeSession(session.SessionId, false);
}
};
}
private void _closeSession(uint sessionId, bool sendMsg)

View File

@@ -9,7 +9,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Errors;

View File

@@ -9,7 +9,7 @@ using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Shared.Console.Commands;

View File

@@ -5,7 +5,7 @@ using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map.Components;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

View File

@@ -8,7 +8,7 @@ using Robust.Shared.IoC.Exceptions;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.Utility;

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Shared.Console

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Shared.Console

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Shared.Console

View File

@@ -1,6 +1,6 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.GameObjects
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.GameObjects;

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.GameObjects
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;

View File

@@ -1,5 +1,5 @@
using System;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects

View File

@@ -9,7 +9,7 @@ using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Physics.Components;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
#if EXCEPTION_TOLERANCE

View File

@@ -1,5 +1,5 @@
using System;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.GameObjects;

View File

@@ -8,7 +8,6 @@ using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects

View File

@@ -3,7 +3,6 @@ using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;

View File

@@ -1,5 +1,5 @@
using System;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.GameObjects;

View File

@@ -1,5 +1,5 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Robust.Shared.GameStates

View File

@@ -25,7 +25,7 @@ namespace Robust.Shared.GameStates
GameTick toSequence,
uint lastInput,
NetListAsArray<EntityState> entities,
NetListAsArray<PlayerState> players,
NetListAsArray<SessionState> players,
NetListAsArray<NetEntity> deletions)
{
FromSequence = fromSequence;
@@ -42,7 +42,7 @@ namespace Robust.Shared.GameStates
public readonly uint LastProcessedInput;
public readonly NetListAsArray<EntityState> EntityStates;
public readonly NetListAsArray<PlayerState> PlayerStates;
public readonly NetListAsArray<SessionState> PlayerStates;
public readonly NetListAsArray<NetEntity> EntityDeletions;
}
}

View File

@@ -3,25 +3,33 @@ using System;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.ViewVariables;
#nullable disable
namespace Robust.Shared.GameStates
{
[Serializable, NetSerializable]
public sealed class PlayerState
public sealed class SessionState
{
[ViewVariables]
public NetUserId UserId { get; set; }
[ViewVariables]
public string Name { get; set; }
[ViewVariables]
public SessionStatus Status { get; set; }
[ViewVariables]
public short Ping { get; set; }
[ViewVariables]
public NetEntity? ControlledEntity { get; set; }
public PlayerState Clone()
public SessionState Clone()
{
return new PlayerState
return new SessionState
{
UserId = UserId,
Name = Name,
@@ -30,6 +38,5 @@ namespace Robust.Shared.GameStates
ControlledEntity = ControlledEntity
};
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.Input.Binding
{

View File

@@ -13,15 +13,15 @@ namespace Robust.Shared.Network.Messages
public override MsgGroups MsgGroup => MsgGroups.Core;
public byte PlyCount { get; set; }
public List<PlayerState> Plyrs { get; set; }
public List<SessionState> Plyrs { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
Plyrs = new List<PlayerState>();
Plyrs = new List<SessionState>();
PlyCount = buffer.ReadByte();
for (var i = 0; i < PlyCount; i++)
{
var plyNfo = new PlayerState
var plyNfo = new SessionState
{
UserId = new NetUserId(buffer.ReadGuid()),
Name = buffer.ReadString(),

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
internal sealed class CommonSession : ICommonSession
{
[ViewVariables]
public EntityUid? AttachedEntity { get; set; }
[ViewVariables]
public NetUserId UserId { get; }
[ViewVariables]
public string Name { get; set; } = "<Unknown>";
[ViewVariables]
public short Ping { get; set; }
[ViewVariables]
public DateTime ConnectedTime { get; set; }
[ViewVariables]
public SessionState State { get; } = new();
[ViewVariables]
public SessionStatus Status { get; set; } = SessionStatus.Connecting;
[ViewVariables]
public SessionData Data { get; }
[ViewVariables]
public INetChannel Channel { get; set; } = default!;
[ViewVariables]
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
[ViewVariables]
public int VisibilityMask { get; set; } = 1;
[ViewVariables]
public LoginType AuthType => Channel?.AuthType ?? default;
public override string ToString() => Name;
public CommonSession(NetUserId user, string name, SessionData data)
{
UserId = user;
Name = name;
Data = data;
}
}

View File

@@ -5,8 +5,6 @@ using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Players;
namespace Robust.Shared.Player
{

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
namespace Robust.Shared.Player;
/// <summary>
/// Common info between client and server sessions.
/// </summary>
public interface ICommonSession
{
/// <summary>
/// Status of the session.
/// </summary>
SessionStatus Status { get; }
/// <summary>
/// Entity UID that this session is represented by in the world, if any.
/// </summary>
EntityUid? AttachedEntity { get; }
/// <summary>
/// The UID of this session.
/// </summary>
NetUserId UserId { get; }
/// <summary>
/// Current name of this player.
/// </summary>
string Name { get; set; }
/// <summary>
/// Current connection latency of this session from the server to their client.
/// </summary>
short Ping { get; internal set; }
/// <summary>
/// The current network channel for this session.
/// </summary>
/// <remarks>
/// On the Server every player has a network channel,
/// on the Client only the LocalPlayer has a network channel, and that channel points to the server.
/// </remarks>
INetChannel Channel { get; }
LoginType AuthType { get; }
/// <summary>
/// List of "eyes" to use for PVS range checks.
/// </summary>
HashSet<EntityUid> ViewSubscriptions { get; }
DateTime ConnectedTime { get; set; }
/// <summary>
/// Session state, for sending player lists to clients.
/// </summary>
SessionState State { get; }
/// <summary>
/// Class for storing arbitrary session-specific data that is not lost upon reconnect.
/// </summary>
SessionData Data { get; }
[Obsolete("Just use the Channel field instead.")]
INetChannel ConnectedClient => Channel;
}

View File

@@ -1,23 +0,0 @@
using Robust.Shared.Network;
namespace Robust.Shared.Player
{
/// <summary>
/// Stores player-specific data that is not lost upon reconnect.
/// </summary>
public interface IPlayerData
{
/// <summary>
/// The session ID of the player owning this data.
/// </summary>
NetUserId UserId { get; }
/// <summary>
/// Custom field that content can assign anything to.
/// Go wild.
/// </summary>
object? ContentDataUncast { get; set; }
string UserName { get; }
}
}

View File

@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
public interface ISharedPlayerManager
{
/// <summary>
/// list of connected sessions.
/// </summary>
ICommonSession[] Sessions { get; }
/// <summary>
/// Sessions with a remote endpoint. On the server, this is equivalent to <see cref="Sessions"/>. On the client,
/// this will only ever contain <see cref="LocalSession"/>
/// </summary>
ICommonSession[] NetworkedSessions { get; }
/// <summary>
/// Dictionary mapping connected users to their sessions.
/// </summary>
IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict { get; }
/// <summary>
/// Number of players currently connected to this server.
/// </summary>
int PlayerCount { get; }
/// <summary>
/// Maximum number of players that can connect to this server at one time.
/// </summary>
int MaxPlayers { get; }
/// <summary>
/// Initializes the manager.
/// </summary>
/// <param name="maxPlayers">Maximum number of players that can connect to this server at one time. Does nothing
/// on the client.</param>
void Initialize(int maxPlayers);
void Startup();
void Shutdown();
/// <summary>
/// Indicates that some session's networked data has changed. This will cause an updated player list to be sent to
/// all players.
/// </summary>
void Dirty();
/// <summary>
/// The session of the local player. This will be null on the server.
/// </summary>
[ViewVariables] ICommonSession? LocalSession { get; }
/// <summary>
/// The user Id of the local player. This will be null on the server.
/// </summary>
[ViewVariables] NetUserId? LocalUser { get; }
/// <summary>
/// The entity currently controlled by the local player. This will be null on the server.
/// </summary>
[ViewVariables] EntityUid? LocalEntity { get; }
/// <summary>
/// This gets invoked when a session's <see cref="ICommonSession.Status"/> changes.
/// </summary>
event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <summary>
/// Attempts to resolve a username into a <see cref="NetUserId"/>.
/// </summary>
bool TryGetUserId(string userName, out NetUserId userId);
/// <summary>
/// Attempts to get the session that is currently attached to a given entity.
/// </summary>
bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Attempts to get the session with the given <see cref="NetUserId"/>.
/// </summary>
bool TryGetSessionById(NetUserId user, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Attempts to get the session with the given <see cref="ICommonSession.Name"/>.
/// </summary>
bool TryGetSessionByUsername(string username, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Attempts to get the session that corresponds to the given channel.
/// </summary>
bool TryGetSessionByChannel(INetChannel channel, [NotNullWhen(true)] out ICommonSession? session);
ICommonSession GetSessionByChannel(INetChannel channel) => GetSessionById(channel.UserId);
ICommonSession GetSessionById(NetUserId user);
/// <summary>
/// Check if the given user id has an active session.
/// </summary>
bool ValidSessionId(NetUserId user) => TryGetSessionById(user, out _);
SessionData GetPlayerData(NetUserId userId);
bool TryGetPlayerData(NetUserId userId, [NotNullWhen(true)] out SessionData? data);
bool TryGetPlayerDataByUsername(string userName, [NotNullWhen(true)] out SessionData? data);
bool HasPlayerData(NetUserId userId);
IEnumerable<SessionData> GetAllPlayerData();
List<SessionState>? GetPlayerStates(GameTick fromTick);
void UpdateState(ICommonSession commonSession);
void RemoveSession(ICommonSession session, bool removeData = false);
void RemoveSession(NetUserId user, bool removeData = false);
/// <summary>
/// Updates a session's <see cref="ICommonSession.AttachedEntity"/>
/// </summary>
void SetAttachedEntity(ICommonSession session, EntityUid? uid);
/// <summary>
/// Updates a session's <see cref="ICommonSession.Status"/>
/// </summary>
void SetStatus(ICommonSession session, SessionStatus status);
/// <summary>
/// Set the session's status to <see cref="SessionStatus.InGame"/>.
/// </summary>
void JoinGame(ICommonSession session);
[Obsolete("Use GetSessionById()")]
ICommonSession GetSessionByUserId(NetUserId user) => GetSessionById(user);
[Obsolete("Use TryGetSessionById()")]
bool TryGetSessionByUserId(NetUserId user, [NotNullWhen(true)] out ICommonSession? session)
=> TryGetSessionById(user, out session);
}

View File

@@ -0,0 +1,32 @@
using Robust.Shared.Network;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
/// <summary>
/// Stores session-specific data that is not lost upon reconnect.
/// </summary>
public sealed class SessionData
{
public SessionData(NetUserId userId, string userName)
{
UserId = userId;
UserName = userName;
}
/// <summary>
/// The session ID of the player owning this data.
/// </summary>
[ViewVariables]
public NetUserId UserId { get; }
[ViewVariables]
public string UserName { get; }
/// <summary>
/// Custom field that content can assign anything to.
/// Go wild.
/// </summary>
[ViewVariables]
public object? ContentDataUncast { get; set; }
}

View File

@@ -0,0 +1,18 @@
using System;
using Robust.Shared.Enums;
namespace Robust.Shared.Player;
public sealed class SessionStatusEventArgs : EventArgs
{
public SessionStatusEventArgs(ICommonSession session, SessionStatus oldStatus, SessionStatus newStatus)
{
Session = session;
OldStatus = oldStatus;
NewStatus = newStatus;
}
public readonly ICommonSession Session;
public readonly SessionStatus OldStatus;
public readonly SessionStatus NewStatus;
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Network;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
// This partial class contains code related to player data.
internal abstract partial class SharedPlayerManager
{
[ViewVariables]
protected readonly Dictionary<NetUserId, SessionData> PlayerData = new();
public SessionData GetPlayerData(NetUserId userId)
{
return PlayerData[userId];
}
public bool TryGetPlayerData(NetUserId userId, [NotNullWhen(true)] out SessionData? data)
{
return PlayerData.TryGetValue(userId, out data);
}
public bool TryGetPlayerDataByUsername(string userName, [NotNullWhen(true)] out SessionData? data)
{
data = null;
return UserIdMap.TryGetValue(userName, out var userId) && PlayerData.TryGetValue(userId, out data);
}
public bool HasPlayerData(NetUserId userId)
{
return PlayerData.ContainsKey(userId);
}
public IEnumerable<SessionData> GetAllPlayerData()
{
return PlayerData.Values;
}
}

View File

@@ -0,0 +1,164 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
// This partial class contains code related to getting player sessions via their user ids and names
internal abstract partial class SharedPlayerManager
{
protected readonly ReaderWriterLockSlim Lock = new();
[ViewVariables]
protected readonly Dictionary<NetUserId, ICommonSession> InternalSessions = new();
public IReadOnlyDictionary<NetUserId, ICommonSession> SessionsDict
{
get
{
Lock.EnterReadLock();
try
{
return InternalSessions.ShallowClone();
}
finally
{
Lock.ExitReadLock();
}
}
}
public ICommonSession[] Sessions
{
get
{
Lock.EnterReadLock();
try
{
return InternalSessions.Values.ToArray();
}
finally
{
Lock.ExitReadLock();
}
}
}
public bool TryGetSessionById(NetUserId user, [NotNullWhen(true)] out ICommonSession? session)
{
Lock.EnterReadLock();
try
{
return InternalSessions.TryGetValue(user, out session);
}
finally
{
Lock.ExitReadLock();
}
}
public virtual ICommonSession[] NetworkedSessions => Sessions;
public bool TryGetSessionByUsername(string username, [NotNullWhen(true)] out ICommonSession? session)
{
session = null;
return UserIdMap.TryGetValue(username, out var userId) && TryGetSessionById(userId, out session);
}
public ICommonSession GetSessionByChannel(INetChannel channel)
=> GetSessionById(channel.UserId);
public bool TryGetSessionByChannel(INetChannel channel, [NotNullWhen(true)] out ICommonSession? session)
=> TryGetSessionById(channel.UserId, out session);
public ICommonSession GetSessionById(NetUserId user)
{
if (!TryGetSessionById(user, out var session))
throw new KeyNotFoundException();
return session;
}
public bool ValidSessionId(NetUserId user) => TryGetSessionById(user, out _);
public abstract bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session);
protected virtual CommonSession CreateSession(NetUserId user, string name, SessionData data)
{
return new CommonSession(user, name, data);
}
internal CommonSession CreateAndAddSession(NetUserId user, string name)
{
Lock.EnterWriteLock();
CommonSession session;
try
{
UserIdMap[name] = user;
if (!PlayerData.TryGetValue(user, out var data))
PlayerData[user] = data = new(user, name);
session = CreateSession(user, name, data);
InternalSessions.Add(user, session);
}
finally
{
Lock.ExitWriteLock();
}
UpdateState(session);
return session;
}
public void RemoveSession(ICommonSession session, bool removeData = false)
=> RemoveSession(session.UserId, removeData);
public void RemoveSession(NetUserId user, bool removeData = false)
{
Lock.EnterWriteLock();
try
{
InternalSessions.Remove(user);
if (removeData)
PlayerData.Remove(user);
}
finally
{
Lock.ExitWriteLock();
}
}
public virtual void SetAttachedEntity(ICommonSession session, EntityUid? uid)
{
if (session.AttachedEntity == uid)
return;
((CommonSession) session).AttachedEntity = uid;
UpdateState(session);
}
public void SetStatus(ICommonSession session, SessionStatus status)
{
if (session.Status == status)
return;
var old = session.Status;
((CommonSession) session).Status = status;
UpdateState(session);
PlayerStatusChanged?.Invoke(this, new SessionStatusEventArgs(session, old, status));
}
public void JoinGame(ICommonSession session)
{
// This currently just directly sets the session's status, as this was the old behaviour.
// In future, this should probably check if the session is currently in a valid state.
SetStatus(session, SessionStatus.InGame);
}
}

View File

@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameStates;
using Robust.Shared.Timing;
namespace Robust.Shared.Player;
// This partial class has game-state related code.
internal abstract partial class SharedPlayerManager
{
public void Dirty()
{
LastStateUpdate = Timing.CurTick;
}
public List<SessionState>? GetPlayerStates(GameTick fromTick)
{
if (LastStateUpdate < fromTick)
{
return null;
}
Lock.EnterReadLock();
try
{
#if FULL_RELEASE
return InternalSessions.Values
.Select(s => s.State)
.ToList();
#else
// Integration tests need to clone data before "sending" it to the client. Otherwise they reference the
// same object.
return InternalSessions.Values
.Select(s => s.State.Clone())
.ToList();
#endif
}
finally
{
Lock.ExitReadLock();
}
}
public void UpdateState(ICommonSession session)
{
var state = session.State;
state.UserId = session.UserId;
state.Status = session.Status;
state.Name = session.Name;
state.ControlledEntity = EntManager.GetNetEntity(session.AttachedEntity);
Dirty();
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Player;
internal abstract partial class SharedPlayerManager : ISharedPlayerManager
{
[Dependency] protected readonly IEntityManager EntManager = default!;
[Dependency] protected readonly ILogManager LogMan = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
protected ISawmill Sawmill = default!;
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
[ViewVariables]
public virtual int MaxPlayers { get; protected set; }
[ViewVariables]
public int PlayerCount => InternalSessions.Count;
[ViewVariables]
public ICommonSession? LocalSession { get; set; }
[ViewVariables]
public NetUserId? LocalUser => LocalSession?.UserId;
[ViewVariables]
public EntityUid? LocalEntity => LocalSession?.AttachedEntity;
public GameTick LastStateUpdate;
[ViewVariables]
protected readonly Dictionary<string, NetUserId> UserIdMap = new();
public virtual void Initialize(int maxPlayers)
{
MaxPlayers = maxPlayers;
Sawmill = LogMan.GetSawmill("player");
}
public virtual void Startup()
{
}
public virtual void Shutdown()
{
InternalSessions.Clear();
UserIdMap.Clear();
PlayerData.Clear();
}
public bool TryGetUserId(string userName, out NetUserId userId)
{
return UserIdMap.TryGetValue(userName, out userId);
}
}

View File

@@ -1,48 +0,0 @@
using System;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Robust.Shared.Players
{
/// <summary>
/// Common info between client and server sessions.
/// </summary>
public interface ICommonSession
{
/// <summary>
/// Status of the session.
/// </summary>
SessionStatus Status { get; internal set; }
/// <summary>
/// Entity UID that this session is represented by in the world, if any.
/// </summary>
EntityUid? AttachedEntity { get; }
/// <summary>
/// The UID of this session.
/// </summary>
NetUserId UserId { get; }
/// <summary>
/// Current name of this player.
/// </summary>
string Name { get; internal set; }
/// <summary>
/// Current connection latency of this session from the server to their client.
/// </summary>
short Ping { get; internal set; }
/// <summary>
/// The current network channel for this player.
/// </summary>
/// <remarks>
/// On the Server every player has a network channel,
/// on the Client only the LocalPlayer has a network channel.
/// </remarks>
INetChannel ConnectedClient { get; }
}
}

View File

@@ -1,29 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Players;
public interface ISharedPlayerManager
{
/// <summary>
/// Player sessions with a remote endpoint.
/// </summary>
IEnumerable<ICommonSession> NetworkedSessions { get; }
IEnumerable<ICommonSession> Sessions { get; }
bool TryGetSessionByEntity(EntityUid uid, [NotNullWhen(true)] out ICommonSession? session);
/// <summary>
/// Number of players currently connected to this server.
/// </summary>
int PlayerCount { get; }
/// <summary>
/// Maximum number of players that can connect to this server at one time.
/// </summary>
int MaxPlayers { get; }
ICommonSession? LocalSession { get; }
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Toolshed.Syntax;

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Toolshed.Syntax;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.Toolshed.Commands.Math;

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Shared.Toolshed.Errors;

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Utility;

View File

@@ -1,5 +1,5 @@
using JetBrains.Annotations;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed.Errors;
namespace Robust.Shared.Toolshed;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Utility;

View File

@@ -8,7 +8,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.Toolshed.Invocation;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
using Robust.Shared.Player;
namespace Robust.Shared.ViewVariables;

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Security.AccessControl;
using System.Threading.Tasks;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables.Commands;
namespace Robust.Shared.ViewVariables;

View File

@@ -30,7 +30,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;

View File

@@ -42,8 +42,8 @@ public sealed class DefaultEntityTest : RobustIntegrationTest
await client.WaitRunTicks(1);
}
var session = (IPlayerSession)playerMan.Sessions.First();
await server.WaitPost(() => session.JoinGame());
var session = playerMan.Sessions.First();
await server.WaitPost(() => playerMan.JoinGame(session));
for (int i = 0; i < 10; i++)
{

View File

@@ -5,7 +5,6 @@ using NUnit.Framework;
using Robust.Client.GameStates;
using Robust.Client.Timing;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -87,9 +86,9 @@ public sealed class PvsReEntryTest : RobustIntegrationTest
player = sEntMan.GetNetEntity(playerUid);
// Attach player.
var session = (IPlayerSession) sPlayerMan.Sessions.First();
var session = sPlayerMan.Sessions.First();
sEntMan.System<ActorSystem>().Attach(playerUid, session);
session.JoinGame();
sPlayerMan.JoinGame(session);
});
for (int i = 0; i < 10; i++)

Some files were not shown because too many files have changed in this diff Show More