using Sandbox.Engine; using Sandbox.Internal; namespace Sandbox.Network; internal partial class NetworkSystem { [SkipHotload] private readonly HashSet _connections = []; [SkipHotload] private readonly Dictionary _connectionLookup = new(); public IEnumerable Connections => _connections; /// /// If true then this host is sending assemblies and other files via network tables /// and as such, does not need to load assemblies from the package (if it even exists). /// public bool IsDeveloperHost { get; set; } /// /// Get whether there are any connections still doing the handshake process. /// /// internal bool IsHandshaking() { return _connections.Count != 0 && _connections.Any( c => c.State != Connection.ChannelState.Connected ); } /// /// Find a connection by its unique id. /// /// /// internal Connection FindConnection( Guid id ) { if ( Connection is not null && Connection.Id == id ) return Connection; if ( _connectionLookup.TryGetValue( id, out var cached ) ) return cached; foreach ( var c in _connections ) { if ( c.Id == id ) { _connectionLookup[id] = c; return c; } } return null; } internal void OnConnected( Connection channel ) { Log.Trace( $"{this}: On Connected {channel}" ); _connections.Add( channel ); channel.InitializeSystem( this ); if ( IsHost ) { // If we're the host and have a new connection then start handshaking channel.GenerateConnectionId(); StartHandshake( channel ); } if ( channel.Id != Guid.Empty ) { _connectionLookup[channel.Id] = channel; } } /// /// Start the handshaking process with the specified client . /// /// void StartHandshake( Connection channel ) { channel.HandshakeId = Guid.NewGuid(); var hello = new ServerInfo { ServerName = Networking.ServerName, ServerData = Networking.ServerData, MaxPlayers = Networking.MaxPlayers, MapName = Networking.MapName, EngineVersion = 234, GamePackage = Application.GamePackage?.GetIdent( false, true ) ?? "", MapPackage = Application.MapPackage?.GetIdent( false, true ) ?? "", Host = new ChannelInfo { Id = Connection.Local.Id }, Assigned = new ChannelInfo { Id = channel.Id }, IsDeveloperHost = IsDeveloperHost, HandshakeId = channel.HandshakeId }; channel.SendMessage( hello ); channel.State = Connection.ChannelState.LoadingServerInformation; } /// /// Restart the handshaking process. This could be used if the host changed /// while we're connecting. /// internal void RestartHandshake() { var host = _connections.FirstOrDefault( c => c.IsHost ); if ( host is null ) return; Connection.Local.State = Connection.ChannelState.Unconnected; host.SendMessage( new RestartHandshakeMsg() ); } /// /// Called from the host to add a connection to the ConnectionInfo table /// internal void AddConnection( Connection source, UserInfo data ) { var info = ConnectionInfo.Add( source ); info.Update( data ); OnConnectionInfoUpdated(); } internal void OnDisconnected( Connection source ) { if ( source.State >= Connection.ChannelState.Welcome ) { // We only need to call this if we called OnConnected, which would only // be the case if they got to at least the Welcome state during the handshake. GameSystem?.OnLeave( source ); } _connectionLookup.Remove( source.Id ); _connections.Remove( source ); source.State = Connection.ChannelState.Unconnected; if ( !IsHost ) return; Log.Info( $"{source.DisplayName} [{source.SteamId}] disconnected" ); ConnectionInfo.Remove( source.Id ); OnConnectionInfoUpdated(); } /// /// We were disconnected from the server. This will almost always be when we're on a dedicated server /// and it has closed down. /// internal void OnServerDisconnection( int reasonCode, string reasonString ) { if ( Connection.Local.State == Connection.ChannelState.Unconnected ) return; IGameInstanceDll.Current.Disconnect(); IMenuSystem.ShowServerError( "Disconnected", reasonString ); Log.Warning( $"Disconnecting - {reasonString}" ); } /// /// Called any time we suspect the connection info might have changed, which /// lets us tell the sockets/connections to update any information they hold. /// internal void OnConnectionInfoUpdated() { foreach ( var socket in sockets ) { socket.OnConnectionInfoUpdated( this ); } _connectionLookup.Clear(); } }