Space-Smash-Out/Assets/Scripts/Multiplayer/SSOLobby.cs

435 lines
11 KiB
C#

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<Room> createdRooms = new();
public Dictionary<NetworkConnection, Room> connectionRooms = new();
#endregion
#region Init & Update
protected virtual void Awake()
{
Locator.RegisterService<SSOLobby>(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<LobbyManager>().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<Room, SceneLoadEndEventArgs> OnServerLoadedScenes;
public event Action<Room, NetworkObject> OnClientCreatedRoom;
public event Action<Room, NetworkObject> OnClientLeftRoom;
public event Action<Room, NetworkObject> OnClientJoinedRoom;
public event Action<Room, NetworkObject> OnClientStarted;
// Events for the client
public event Action<Room> OnRoomCreated;
public event Action<Room> OnRoomDeleted;
public event Action<Room> OnRoomStarted;
public event Action<NetworkObject> OnMemberLeft;
public event Action<NetworkObject> OnMemberStarted;
public event Action<NetworkObject> OnMemberJoined;
public event Action<NetworkObject> 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<LobbyManager>().SignInSuccess(username);
}
[TargetRpc]
private void TargetSignInFailed(NetworkConnection conn, string failedReason)
{
Locator.GetService<LobbyManager>().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<LobbyManager>().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<LobbyManager>().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<LobbyManager>().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
}