using System.Collections;
using System;
using System.Collections.Generic;
using LobbyRooms.Relay;
using Player;
using Unity.Services.Relay.Models;
using UnityEngine;
using UnityEngine.SceneManagement; // TODO: Definitely shouldn't need this just for logging?
using Utilities;
namespace LobbyRooms
{
public class GameStateManager : MonoBehaviour, IReceiveMessages
{
[SerializeField]
LogMode m_logMode = LogMode.Critical;
///
/// All these should be assigned the observers in the scene at the start.
///
[SerializeField]
List m_GameStateObservers = new List();
[SerializeField]
List m_LobbyDataObservers = new List();
[SerializeField]
List m_LocalUserObservers = new List();
[SerializeField]
List m_LobbyServiceObservers = new List();
private RoomsContentHeartbeat m_roomsContentHeartbeat = new RoomsContentHeartbeat();
LobbyUser m_localUser;
LobbyData m_lobbyData;
LobbyServiceData m_lobbyServiceData = new LobbyServiceData();
LocalGameState m_localGameState = new LocalGameState();
LobbyReadyCheck m_LobbyReadyCheck;
public void Awake()
{
// Do some arbitrary operations to instantiate singletons.
LogHandler.Get().mode = m_logMode;
#pragma warning disable IDE0059 // Unnecessary assignment of a value
var unused = Locator.Get;
#pragma warning restore IDE0059 // Unnecessary assignment of a value
Locator.Get.Provide(new Auth.Identity(OnAuthSignIn));
m_LobbyReadyCheck = new LobbyReadyCheck(null, 7);
Application.wantsToQuit += OnWantToQuit;
}
private void OnAuthSignIn()
{
Debug.Log("Signed in.");
m_localUser.ID = Locator.Get.Identity.GetSubIdentity(Auth.IIdentityType.Auth).GetContent("id");
m_localUser.DisplayName = NameGenerator.GetName(m_localUser.ID);
}
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.RenameRequest)
{
m_localUser.DisplayName = (string)msg;
}
else if (type == MessageType.CreateRoomRequest)
{
var createRoomData = (LobbyData)msg;
RoomsQuery.Instance.CreateRoomAsync(createRoomData.LobbyName, createRoomData.MaxPlayerCount, createRoomData.Private, (r) =>
{
Rooms.ToLobbyData.Convert(r, m_lobbyData, m_localUser);
OnCreatedRoom();
}, OnFailedJoin); // TODO: Report failure?
}
else if (type == MessageType.JoinRoomRequest)
{
LobbyInfo roomData = (LobbyInfo)msg;
RoomsQuery.Instance.JoinRoomAsync(roomData.RoomID, roomData.RoomCode, (r) =>
{
Rooms.ToLobbyData.Convert(r, m_lobbyData, m_localUser);
OnJoinedRoom();
}, OnFailedJoin); // TODO: Report failure?
}
else if (type == MessageType.QueryRooms)
{
m_lobbyServiceData.State = LobbyServiceState.Fetching;
RoomsQuery.Instance.RetrieveRoomListAsync(
qr =>
{
if (qr != null)
OnRefreshed(Rooms.ToLobbyData.Convert(qr));
}, er =>
{
long errorLong = 0;
if (er != null)
errorLong = er.Status;
OnRefreshFailed(errorLong);
});
}
else if (type == MessageType.ChangeGameState)
{
SetGameState((GameState)msg);
}
else if (type == MessageType.UserSetEmote)
{
var emote = (string)msg;
m_localUser.Emote = emote;
}
else if (type == MessageType.ChangeLobbyUserState)
{
m_localUser.UserStatus = (UserStatus)msg;
}
else if (type == MessageType.Client_EndReadyCountdownAt)
{
m_lobbyData.TargetEndTime = (DateTime)msg;
BeginCountDown();
}
else if (type == MessageType.ToLobby)
{
ToLobby();
}
}
void Start()
{
m_lobbyData = new LobbyData
{
State = LobbyState.Lobby
};
m_localUser = new LobbyUser();
m_localUser.DisplayName = "New Player";
Locator.Get.Messenger.Subscribe(this);
DefaultObserverSetup();
InitObservers();
}
///
/// We find and validate that the scene has all the Observers we expect
///
void DefaultObserverSetup()
{
foreach (var gameStateObs in FindObjectsOfType())
{
if (!gameStateObs.observeOnStart)
continue;
if (!m_GameStateObservers.Contains(gameStateObs))
m_GameStateObservers.Add(gameStateObs);
}
foreach (var lobbyData in FindObjectsOfType())
{
if (!lobbyData.observeOnStart)
continue;
if (!m_LobbyDataObservers.Contains(lobbyData))
m_LobbyDataObservers.Add(lobbyData);
}
foreach (var lobbyUserObs in FindObjectsOfType())
{
if (!lobbyUserObs.observeOnStart)
continue;
if (!m_LocalUserObservers.Contains(lobbyUserObs))
m_LocalUserObservers.Add(lobbyUserObs);
}
foreach (var lobbyServiceObs in FindObjectsOfType())
{
if (!lobbyServiceObs.observeOnStart)
continue;
if (!m_LobbyServiceObservers.Contains(lobbyServiceObs))
m_LobbyServiceObservers.Add(lobbyServiceObs);
}
if (m_GameStateObservers.Count < 4)
Debug.LogWarning($"Scene: {SceneManager.GetActiveScene().name} has less than the default expected Game State Observers, ensure all the observers in the scene that need to watch the gameState are registered in the LocalGameStateObservers List.");
if (m_LobbyDataObservers.Count < 8)
Debug.LogWarning($"Scene: {SceneManager.GetActiveScene().name} has less than the default expected Lobby Data Observers, ensure all the observers in the scene that need to watch the Local Lobby Data are registered in the LobbyDataObservers List.");
if (m_LocalUserObservers.Count < 3)
Debug.LogWarning($"Scene: {SceneManager.GetActiveScene().name} has less than the default expected Local User Observers, ensure all the observers in the scene that need to watch the gameState are registered in the LocalUserObservers List.");
if (m_LobbyServiceObservers.Count < 2)
Debug.LogWarning($"Scene: {SceneManager.GetActiveScene().name} has less than the default expected Lobby Service Observers, ensure all the observers in the scene that need to watch the lobby service state are registered in the LobbyServiceObservers List.");
}
void InitObservers()
{
foreach (var gameStateObs in m_GameStateObservers)
{
if (gameStateObs == null)
{
Debug.LogError("Missing a gameStateObserver, please make sure all GameStateObservers in the scene are registered here.");
continue;
}
gameStateObs.BeginObserving(m_localGameState);
}
foreach (var lobbyObs in m_LobbyDataObservers)
{
if (lobbyObs == null)
{
Debug.LogError("Missing a gameStateObserver, please make sure all GameStateObservers in the scene are registered here.");
continue;
}
lobbyObs.BeginObserving(m_lobbyData);
}
foreach (var userObs in m_LocalUserObservers)
{
if (userObs == null)
{
Debug.LogError("Missing a gameStateObserver, please make sure all GameStateObservers in the scene are registered here.");
continue;
}
userObs.BeginObserving(m_localUser);
}
foreach (var serviceObs in m_LobbyServiceObservers)
{
if (serviceObs == null)
{
Debug.LogError("Missing a gameStateObserver, please make sure all GameStateObservers in the scene are registered here.");
continue;
}
serviceObs.BeginObserving(m_lobbyServiceData);
}
}
void SetGameState(GameState state)
{
bool isLeavingRoom = (state == GameState.Menu || state == GameState.JoinMenu) && m_localGameState.State == GameState.Lobby;
m_localGameState.State = state;
if (isLeavingRoom)
OnLeftRoom();
}
void OnRefreshed(IEnumerable lobbies)
{
var newLobbyDict = new Dictionary();
foreach (var lobby in lobbies)
{
newLobbyDict.Add(lobby.RoomID, lobby);
}
m_lobbyServiceData.State = LobbyServiceState.Fetched;
m_lobbyServiceData.CurrentLobbies = newLobbyDict;
}
void OnRefreshFailed(long errorCode)
{
m_lobbyServiceData.lastErrorCode = errorCode;
m_lobbyServiceData.State = LobbyServiceState.Error;
}
void OnCreatedRoom()
{
OnJoinedRoom();
RelayInterface.AllocateAsync(m_lobbyData.MaxPlayerCount, OnGotRelayAllocation);
}
void OnGotRelayAllocation(Allocation allocationID)
{
RelayInterface.GetJoinCodeAsync(allocationID.AllocationId, OnGotRelayCode);
}
void OnGotRelayCode(string relayCode)
{
m_lobbyData.RelayCode = relayCode;
}
void OnJoinedRoom()
{
RoomsQuery.Instance.BeginTracking(m_lobbyData.RoomID);
m_roomsContentHeartbeat.BeginTracking(m_lobbyData, m_localUser);
SetUserLobbyState();
Dictionary displayNameData = new Dictionary();
displayNameData.Add("DisplayName", m_localUser.DisplayName);
RoomsQuery.Instance.UpdatePlayerDataAsync(displayNameData, null);
}
void OnLeftRoom()
{
m_localUser.Emote = null;
RoomsQuery.Instance.LeaveRoomAsync(m_lobbyData.RoomID, ResetLobbyData);
m_roomsContentHeartbeat.EndTracking();
RoomsQuery.Instance.EndTracking();
}
///
/// Back to Join menu if we fail to join for whatever reason.
///
void OnFailedJoin()
{
SetGameState(GameState.JoinMenu);
}
void BeginCountDown()
{
// Only start the countdown once.
if (m_lobbyData.State == LobbyState.CountDown)
return;
m_lobbyData.CountDownTime = m_lobbyData.TargetEndTime.Subtract(DateTime.Now).Seconds;
m_lobbyData.State = LobbyState.CountDown;
StartCoroutine(CountDown());
}
IEnumerator CountDown()
{
m_LobbyReadyCheck.EndCheckingForReady();
while (m_lobbyData.CountDownTime > 0)
{
yield return new WaitForSeconds(0.2f);
if (m_lobbyData.State != LobbyState.CountDown)
yield break;
m_lobbyData.CountDownTime = m_lobbyData.TargetEndTime.Subtract(DateTime.Now).Seconds;
}
m_localUser.UserStatus = UserStatus.Connecting;
m_lobbyData.State = LobbyState.InGame;
RelayInterface.JoinAsync(m_lobbyData.RelayCode, OnJoinedGame);
}
void OnJoinedGame(JoinAllocation joinData)
{
m_localUser.UserStatus = UserStatus.Connected;
var ip = joinData.RelayServer.IpV4;
var port = joinData.RelayServer.Port;
m_lobbyData.RelayServer = new ServerAddress(ip, port);
}
void ToLobby()
{
m_lobbyData.State = LobbyState.Lobby;
m_lobbyData.CountDownTime = 0;
m_lobbyData.RelayServer = null;
SetUserLobbyState();
}
void SetUserLobbyState()
{
SetGameState(GameState.Lobby);
m_localUser.UserStatus = UserStatus.Lobby;
if (m_localUser.IsHost)
m_LobbyReadyCheck.BeginCheckingForReady();
}
void ResetLobbyData()
{
m_lobbyData.CopyObserved(new LobbyInfo(), new Dictionary());
m_lobbyData.CountDownTime = 0;
m_lobbyData.RelayServer = null;
m_LobbyReadyCheck.EndCheckingForReady();
}
void OnDestroy()
{
ForceLeaveAttempt();
}
bool OnWantToQuit()
{
bool canQuit = string.IsNullOrEmpty(m_lobbyData?.RoomID);
StartCoroutine(LeaveBeforeQuit());
return canQuit;
}
void ForceLeaveAttempt()
{
Locator.Get.Messenger.Unsubscribe(this);
if (!string.IsNullOrEmpty(m_lobbyData?.RoomID))
{
RoomsQuery.Instance.LeaveRoomAsync(m_lobbyData?.RoomID, null);
m_lobbyData = null;
}
}
///
/// In builds, if we are in a room and try to send a Leave request on application quit, it won't go through if we're quitting on the same frame.
/// So, we need to delay just briefly to let the request happen (though we don't need to wait for the result).
///
IEnumerator LeaveBeforeQuit()
{
ForceLeaveAttempt();
// TEMP: Since we're temporarily (as of 6/31/21) deleting empty rooms when we leave them manually, we'll delay a bit to ensure that happens.
//yield return null;
yield return new WaitForSeconds(0.5f);
Application.Quit();
}
}
}