using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Security.Cryptography; using FishNet; using FishNet.Connection; using FishNet.Managing.Scened; using FishNet.Object; using FishNet.Transporting; using log4net; using Unity.VisualScripting; using UnityEngine; using UnityEngine.SceneManagement; public class SSOLobby : NetworkBehaviour { private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); #region Fields [SerializeField] private NetworkObject clientPrefab; public List createdRooms = new(); public Dictionary connectionRooms = new(); #endregion #region Init & Update protected virtual void Awake() { Locator.RegisterService(this); // Register connection state events InstanceFinder.ServerManager.OnServerConnectionState += OnServerStateChanged; InstanceFinder.ServerManager.OnRemoteConnectionState += OnClienStateChanged; InstanceFinder.ClientManager.OnClientConnectionState += OnLocalClientStateChanged; } public override void OnStartClient() { if (clientPrefab == null) { Log.Error("There is no client prefab for the client to spawn."); return; } // // The client gets a client instance upon entering the lobby // NetworkObject nob = Instantiate(clientPrefab); // Scene scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName("OnlineLobby"); // UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(nob.gameObject, scene); // InstanceFinder.ServerManager.Spawn(nob.gameObject, LocalConnection); Locator.GetService().InitClient(this); } public override void OnStartServer() { // TODO: check if the mirror timing issues still persist ChangeSubscription(false); ChangeSubscription(true); } public override void OnStopServer() { base.OnStopServer(); ChangeSubscription(false); } public void ChangeSubscription(bool subscribe) { if (base.NetworkManager == null) return; if (subscribe) { base.NetworkManager.SceneManager.OnLoadEnd += OnClientLoadedScene; base.NetworkManager.SceneManager.OnClientPresenceChangeEnd += OnClientSceneState; } else { base.NetworkManager.SceneManager.OnLoadEnd -= OnClientLoadedScene; base.NetworkManager.SceneManager.OnClientPresenceChangeEnd -= OnClientSceneState; } } #endregion #region Actions public event Action OnServerLoadedScenes; public event Action OnClientCreatedRoom; public event Action OnClientLeftRoom; public event Action OnClientJoinedRoom; public event Action OnClientStarted; // Events for the client public event Action OnRoomCreated; public event Action OnRoomDeleted; public event Action OnRoomStarted; public event Action OnMemberLeft; public event Action OnMemberStarted; public event Action OnMemberJoined; public event Action OnClientLoggedIn; #endregion #region Events private void OnClientSceneState(ClientPresenceChangeEventArgs args) { } private void OnClientLoadedScene(SceneLoadEndEventArgs args) { } private void OnLocalClientStateChanged(ClientConnectionStateArgs args) { } private void OnClienStateChanged(NetworkConnection connection, RemoteConnectionStateArgs args) { } private void OnServerStateChanged(ServerConnectionStateArgs args) { } #endregion #region SignIn [Client] public void SignIn() { ServerSignIn(); } [ServerRpc(RequireOwnership = false)] private void ServerSignIn(NetworkConnection sender = null) { // Assign a username to this new connection bool success = OnSignIn(sender.ClientId, out string username, out string failedReason); // Get client instance from that user ClientInstance.ReturnClientInstance(sender).Username = username; if (success) { TargetSignInSuccess(sender, username); } else { TargetSignInFailed(sender, failedReason); } } // TODO: Username input options and sanitization private bool OnSignIn(int clientId, out string username, out string failedReason) { username = "Ship " + clientId; failedReason = "cannot fail yet"; return true; } [TargetRpc] private void TargetSignInSuccess(NetworkConnection conn, string username) { OnClientLoggedIn?.Invoke(ClientInstance.ReturnClientInstance(conn).NetworkObject); Locator.GetService().SignInSuccess(username); } [TargetRpc] private void TargetSignInFailed(NetworkConnection conn, string failedReason) { Locator.GetService().SignInFailed(failedReason); } #endregion #region Create Room [Client] public void CreateRoom(string roomName = "") { ServerCreateRoom(roomName); } [ServerRpc(RequireOwnership = false)] private void ServerCreateRoom(string roomName, NetworkConnection sender = null) { string failedReason = ""; ClientInstance ci = ClientInstance.ReturnClientInstance(sender); if (ci == null) { failedReason = "Unable to find Client Instance for incoming rpc call."; Log.Error(failedReason); TargetCreateRoomFailed(sender, failedReason); return; } if (roomName == "") { roomName = ci.Username + "'s Room"; } Room match = ReturnRoom(ci.NetworkObject); if (match != null) { failedReason = "User is already in a room."; Log.Error(failedReason); TargetCreateRoomFailed(sender, failedReason); return; } match = ReturnRoom(roomName); if (match != null) { failedReason = "Room was already created."; TargetCreateRoomFailed(sender, failedReason); return; } // TODO: Manage room player count Room room = new Room(roomName, "", true, 2); room.AddMember(ci.NetworkObject); createdRooms.Add(room); connectionRooms[sender] = room; Log.Info($"New room: {roomName} created successfully."); OnClientCreatedRoom?.Invoke(room, ci.NetworkObject); /* RpcUpdateRooms(new Room[] {room}); */ ObserverRoomChange(room, "Create"); TargetCreateRoomSuccess(sender, room); } [TargetRpc] private void TargetCreateRoomSuccess(NetworkConnection conn, Room room) { // Local first object instead OnMemberJoined?.Invoke(ClientManager.Connection.FirstObject); Locator.GetService().OnCreateRoom(room); } [TargetRpc] private void TargetCreateRoomFailed(NetworkConnection conn, string failedReason) { Log.Error(failedReason); } #endregion #region Join Room [Client] public void JoinRoom(string roomName) { ServerJoinRoom(roomName); } [ServerRpc(RequireOwnership = false)] private void ServerJoinRoom(string roomName, NetworkConnection sender = null) { string failedReason = ""; var ci = ClientInstance.ReturnClientInstance(sender); if (ci == null) { failedReason = "Unable to find Client Instance for incoming rpc call."; Log.Error(failedReason); TargetJoindRoomFailed(sender, failedReason); return; } Room room = ReturnRoom(roomName); if (room == null) { failedReason = $"No room named: {roomName} was found."; Log.Error(failedReason); TargetJoindRoomFailed(sender, failedReason); return; } if (ReturnRoom(ci.NetworkObject) != null) { failedReason = "User is already in a room."; Log.Error(failedReason); TargetJoindRoomFailed(sender, failedReason); return; } if (room.MemberIds.Count >= room.maxPlayers) { failedReason = "The room is already full."; Log.Error(failedReason); TargetJoindRoomFailed(sender, failedReason); return; } if (room.isStarted && room.lockOnStart) { failedReason = "Room has already started a game."; Log.Error(failedReason); TargetJoindRoomFailed(sender, failedReason); return; } room.AddMember(ci.NetworkObject); connectionRooms[ci.Owner] = room; OnClientJoinedRoom?.Invoke(room, ci.NetworkObject); foreach (NetworkObject item in room.MemberIds) { TargetMemberJoined(item.Owner, ci.NetworkObject); if (item.Owner != ci.Owner) { TargetMemberJoined(ci.Owner, item); } } TargetJoinRoomSuccess(sender, room); } [TargetRpc] private void TargetJoinRoomSuccess(NetworkConnection conn, Room room) { OnMemberJoined?.Invoke(ClientManager.Connection.FirstObject); Locator.GetService().OnJoinRoom(room); } [TargetRpc] private void TargetJoindRoomFailed(NetworkConnection conn, string failedReason) { Log.Error(failedReason); } #endregion #region Leave Rooom [Client] public void LeaveRoom() { ServerLeaveRoom(); } [ServerRpc(RequireOwnership = false)] private void ServerLeaveRoom(NetworkConnection sender = null) { var ci = ClientInstance.ReturnClientInstance(sender); Room room = RemoveFromRoom(ci.NetworkObject, false); if (room == null) { TargetLeaveRoomFailed(sender); return; } TargetLeaveRoomSuccess(sender); } [TargetRpc] private void TargetLeaveRoomSuccess(NetworkConnection conn) { Locator.GetService().OnLeaveRoom(); } [TargetRpc] private void TargetLeaveRoomFailed(NetworkConnection conn) { Log.Error("Leave room failed."); } #endregion #region Manage Rooms [Server] private Room RemoveFromRoom(NetworkObject clientId, bool clientDisconnected) { Room room = ReturnRoom(clientId); if (room == null) return null; //Let members know someone left foreach (NetworkObject item in room.MemberIds) { if (clientDisconnected && item == clientId) continue; TargetMemberLeft(item.Owner, clientId); } //Remove the member from the room room.RemoveMember(clientId); connectionRooms.Remove(clientId.Owner); OnClientLeftRoom?.Invoke(room, clientId); //If not disconnectiong tell client to unload scenes if (!clientDisconnected) { SceneLookupData[] lookups = SceneLookupData.CreateData(room.Scenes.ToArray()); SceneUnloadData sud = new SceneUnloadData(lookups); if (lookups.Length > 0) InstanceFinder.SceneManager.UnloadConnectionScenes(clientId.Owner, sud); } //If room is empty remove room if (room.MemberIds.Count == 0) { createdRooms.Remove(room); ObserverRoomChange(room, "Delete"); } return room; } public Room ReturnRoom(string roomName) { return createdRooms.FirstOrDefault (r => r.name.Equals(roomName, StringComparison.CurrentCultureIgnoreCase)); } public Room ReturnRoom(NetworkObject clientId) { foreach (Room r in createdRooms) { if (r.MemberIds.Contains(clientId)) { return r; } } return null; } [TargetRpc] private void TargetMemberJoined(NetworkConnection conn, NetworkObject member) { OnMemberJoined?.Invoke(member); Log.Debug("Member joined"); } [TargetRpc] private void TargetMemberLeft(NetworkConnection conn, NetworkObject member) { OnMemberLeft?.Invoke(member); Log.Debug("Member left"); } [ObserversRpc] public void ObserverRoomChange(Room room, string action) { if (action == "Create") { OnRoomCreated?.Invoke(room); } else if (action == "Delete") { OnRoomDeleted?.Invoke(room); } else if (action == "Started") { OnRoomStarted?.Invoke(room); } } #endregion }