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.Players; using Robust.Shared.ViewVariables; namespace Robust.Server.Player { /// /// This is the session of a connected client. /// internal sealed class PlayerSession : IPlayerSession { private readonly PlayerManager _playerManager; public readonly PlayerState PlayerState; private readonly HashSet _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 ViewSubscriptions => _viewSubscriptions; public int ViewSubscriptionCount => _viewSubscriptions.Count; [ViewVariables] public INetChannel ConnectedClient { get; } /// [ViewVariables] public EntityUid? AttachedEntity { get; set; } private SessionStatus _status = SessionStatus.Connecting; [ViewVariables] internal string Name { get; set; } /// 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)); } } /// SessionStatus ICommonSession.Status { get => this.Status; set => this.Status = value; } /// public DateTime ConnectedTime { get; private set; } [ViewVariables(VVAccess.ReadWrite)] public int VisibilityMask { get; set; } = 1; /// [ViewVariables] public NetUserId UserId { get; } private readonly PlayerData _data; [ViewVariables] public IPlayerData Data => _data; /// public event EventHandler? PlayerStatusChanged; /// public void AttachToEntity(EntityUid? entity) { DetachFromEntity(); if (entity == null) return; AttachToEntity((EntityUid) entity); } /// public void AttachToEntity(EntityUid uid) { DetachFromEntity(); if (!EntitySystem.Get().Attach(uid, this)) { Logger.Warning($"Couldn't attach player \"{this}\" to entity \"{uid}\"! Did it have a player already attached to it?"); } } /// public void DetachFromEntity() { if (AttachedEntity == null) return; #if EXCEPTION_TOLERANCE if (IoCManager.Resolve().Deleted(AttachedEntity!.Value)) { Logger.Warning($"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; } #endif if (!EntitySystem.Get().Detach(AttachedEntity.Value)) { Logger.Warning($"Couldn't detach player \"{this}\" from entity \"{AttachedEntity}\"! Is it missing an ActorComponent?"); } } /// public void OnConnect() { ConnectedTime = DateTime.UtcNow; Status = SessionStatus.Connected; UpdatePlayerState(); } /// public void OnDisconnect() { Status = SessionStatus.Disconnected; UnsubscribeAllViews(); DetachFromEntity(); UpdatePlayerState(); } /// /// Causes the session to switch from the lobby to the game. /// public void JoinGame() { if (ConnectedClient == null || Status == SessionStatus.InGame) return; Status = SessionStatus.InGame; UpdatePlayerState(); } public LoginType AuthType => ConnectedClient.AuthType; /// 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(); foreach (var eye in _viewSubscriptions) { viewSubscriberSystem.RemoveViewSubscriber(eye, this); } } private void UpdatePlayerState() { PlayerState.Status = Status; PlayerState.Name = Name; PlayerState.ControlledEntity = AttachedEntity; _playerManager.Dirty(); } /// public override string ToString() { return Name; } } }