using LobbyRelaySample.relay;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace LobbyRelaySample
{
///
/// Sets up and runs the entire sample.
///
public class GameManager : MonoBehaviour, IReceiveMessages
{
#region UI elements that observe the local state. These should be assigned the observers in the scene during Start.
[SerializeField]
private List m_GameStateObservers = new List();
[SerializeField]
private List m_LocalLobbyObservers = new List();
[SerializeField]
private List m_LocalUserObservers = new List();
[SerializeField]
private List m_LobbyServiceObservers = new List();
#endregion
private LocalGameState m_localGameState = new LocalGameState();
private LobbyUser m_localUser;
private LocalLobby m_localLobby;
private LobbyServiceData m_lobbyServiceData = new LobbyServiceData();
private LobbyContentHeartbeat m_lobbyContentHeartbeat = new LobbyContentHeartbeat();
private RelayUtpSetup m_relaySetup;
private RelayUtpClient m_relayClient;
private vivox.VivoxSetup m_vivoxSetup = new vivox.VivoxSetup();
[SerializeField]
private List m_vivoxUserHandlers;
/// Rather than a setter, this is usable in-editor. It won't accept an enum, however.
public void SetLobbyColorFilter(int color)
{
m_lobbyColorFilter = (LobbyColor)color;
}
private LobbyColor m_lobbyColorFilter;
#region Setup
private void Awake()
{
// Do some arbitrary operations to instantiate singletons.
#pragma warning disable IDE0059 // Unnecessary assignment of a value
var unused = Locator.Get;
#pragma warning restore IDE0059
Locator.Get.Provide(new Auth.Identity(OnAuthSignIn));
Application.wantsToQuit += OnWantToQuit;
}
private void Start()
{
m_localLobby = new LocalLobby { State = LobbyState.Lobby };
m_localUser = new LobbyUser();
m_localUser.DisplayName = "New Player";
Locator.Get.Messenger.Subscribe(this);
BeginObservers();
}
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);
m_localLobby.AddPlayer(m_localUser); // The local LobbyUser object will be hooked into UI before the LocalLobby is populated during lobby join, so the LocalLobby must know about it already when that happens.
StartVivoxLogin();
}
private void BeginObservers()
{
foreach (var gameStateObs in m_GameStateObservers)
gameStateObs.BeginObserving(m_localGameState);
foreach (var serviceObs in m_LobbyServiceObservers)
serviceObs.BeginObserving(m_lobbyServiceData);
foreach (var lobbyObs in m_LocalLobbyObservers)
lobbyObs.BeginObserving(m_localLobby);
foreach (var userObs in m_LocalUserObservers)
userObs.BeginObserving(m_localUser);
}
#endregion
///
/// Primarily used for UI elements to communicate state changes, this will receive messages from arbitrary providers for user interactions.
///
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.CreateLobbyRequest)
{
LocalLobby.LobbyData createLobbyData = (LocalLobby.LobbyData)msg;
LobbyAsyncRequests.Instance.CreateLobbyAsync(createLobbyData.LobbyName, createLobbyData.MaxPlayerCount, createLobbyData.Private, m_localUser, (r) =>
{ lobby.ToLocalLobby.Convert(r, m_localLobby);
OnCreatedLobby();
},
OnFailedJoin);
}
else if (type == MessageType.JoinLobbyRequest)
{
LocalLobby.LobbyData lobbyInfo = (LocalLobby.LobbyData)msg;
LobbyAsyncRequests.Instance.JoinLobbyAsync(lobbyInfo.LobbyID, lobbyInfo.LobbyCode, m_localUser, (r) =>
{ lobby.ToLocalLobby.Convert(r, m_localLobby);
OnJoinedLobby();
},
OnFailedJoin);
}
else if (type == MessageType.QueryLobbies)
{
m_lobbyServiceData.State = LobbyQueryState.Fetching;
LobbyAsyncRequests.Instance.RetrieveLobbyListAsync(
qr => {
if (qr != null)
OnLobbiesQueried(lobby.ToLocalLobby.Convert(qr));
},
er => {
OnLobbyQueryFailed();
},
m_lobbyColorFilter);
}
else if (type == MessageType.QuickJoin)
{
LobbyAsyncRequests.Instance.QuickJoinLobbyAsync(m_localUser, m_lobbyColorFilter, (r) =>
{ lobby.ToLocalLobby.Convert(r, m_localLobby);
OnJoinedLobby();
},
OnFailedJoin);
}
else if (type == MessageType.RenameRequest)
{
string name = (string)msg;
if (string.IsNullOrWhiteSpace(name))
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Empty Name not allowed."); // Lobby error type, then HTTP error type.
return;
}
m_localUser.DisplayName = (string)msg;
}
else if (type == MessageType.ClientUserApproved)
{ ConfirmApproval();
}
else if (type == MessageType.UserSetEmote)
{ EmoteType emote = (EmoteType)msg;
m_localUser.Emote = emote;
}
else if (type == MessageType.LobbyUserStatus)
{ m_localUser.UserStatus = (UserStatus)msg;
}
else if (type == MessageType.StartCountdown)
{ m_localLobby.State = LobbyState.CountDown;
}
else if (type == MessageType.CancelCountdown)
{ m_localLobby.State = LobbyState.Lobby;
}
else if (type == MessageType.CompleteCountdown)
{ if (m_relayClient is RelayUtpHost)
(m_relayClient as RelayUtpHost).SendInGameState();
}
else if (type == MessageType.ChangeGameState)
{ SetGameState((GameState)msg);
}
else if (type == MessageType.ConfirmInGameState)
{ m_localUser.UserStatus = UserStatus.InGame;
m_localLobby.State = LobbyState.InGame;
}
else if (type == MessageType.EndGame)
{ m_localLobby.State = LobbyState.Lobby;
SetUserLobbyState();
}
}
private void SetGameState(GameState state)
{
bool isLeavingLobby = (state == GameState.Menu || state == GameState.JoinMenu) && m_localGameState.State == GameState.Lobby;
m_localGameState.State = state;
if (isLeavingLobby)
OnLeftLobby();
}
private void OnLobbiesQueried(IEnumerable lobbies)
{
var newLobbyDict = new Dictionary();
foreach (var lobby in lobbies)
newLobbyDict.Add(lobby.LobbyID, lobby);
m_lobbyServiceData.State = LobbyQueryState.Fetched;
m_lobbyServiceData.CurrentLobbies = newLobbyDict;
}
private void OnLobbyQueryFailed()
{
m_lobbyServiceData.State = LobbyQueryState.Error;
}
private void OnCreatedLobby()
{
m_localUser.IsHost = true;
OnJoinedLobby();
}
private void OnJoinedLobby()
{
LobbyAsyncRequests.Instance.BeginTracking(m_localLobby.LobbyID);
m_lobbyContentHeartbeat.BeginTracking(m_localLobby, m_localUser);
SetUserLobbyState();
// The host has the opportunity to reject incoming players, but to do so the player needs to connect to Relay without having game logic available.
// In particular, we should prevent players from joining voice chat until they are approved.
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Connecting);
if (m_localUser.IsHost)
{
StartRelayConnection();
StartVivoxJoin();
}
else
{
StartRelayConnection();
}
}
private void OnLeftLobby()
{
m_localUser.ResetState();
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby.LobbyID, ResetLocalLobby);
m_lobbyContentHeartbeat.EndTracking();
LobbyAsyncRequests.Instance.EndTracking();
m_vivoxSetup.LeaveLobbyChannel();
if (m_relaySetup != null)
{ Component.Destroy(m_relaySetup);
m_relaySetup = null;
}
if (m_relayClient != null)
{ Component.Destroy(m_relayClient);
m_relayClient = null;
}
}
///
/// Back to Join menu if we fail to join for whatever reason.
///
private void OnFailedJoin()
{
SetGameState(GameState.JoinMenu);
}
private void StartVivoxLogin()
{
m_vivoxSetup.Initialize(m_vivoxUserHandlers, OnVivoxLoginComplete);
void OnVivoxLoginComplete(bool didSucceed)
{
if (!didSucceed)
{ Debug.LogError("Vivox login failed! Retrying in 5s...");
StartCoroutine(RetryConnection(StartVivoxLogin, m_localLobby.LobbyID));
return;
}
}
}
private void StartVivoxJoin()
{
m_vivoxSetup.JoinLobbyChannel(m_localLobby.LobbyID, OnVivoxJoinComplete);
void OnVivoxJoinComplete(bool didSucceed)
{
if (!didSucceed)
{ Debug.LogError("Vivox connection failed! Retrying in 5s...");
StartCoroutine(RetryConnection(StartVivoxJoin, m_localLobby.LobbyID));
return;
}
}
}
private void StartRelayConnection()
{
if (m_localUser.IsHost)
m_relaySetup = gameObject.AddComponent();
else
m_relaySetup = gameObject.AddComponent();
m_relaySetup.BeginRelayJoin(m_localLobby, m_localUser, OnRelayConnected);
void OnRelayConnected(bool didSucceed, RelayUtpClient client)
{
Component.Destroy(m_relaySetup);
m_relaySetup = null;
if (!didSucceed)
{ Debug.LogError("Relay connection failed! Retrying in 5s...");
StartCoroutine(RetryConnection(StartRelayConnection, m_localLobby.LobbyID));
return;
}
m_relayClient = client;
if (m_localUser.IsHost)
CompleteRelayConnection();
else
Debug.Log("Client is now waiting for approval...");
}
}
private IEnumerator RetryConnection(Action doConnection, string lobbyId)
{
yield return new WaitForSeconds(5);
if (m_localLobby != null && m_localLobby.LobbyID == lobbyId && !string.IsNullOrEmpty(lobbyId)) // Ensure we didn't leave the lobby during this waiting period.
doConnection?.Invoke();
}
private void ConfirmApproval()
{
if (!m_localUser.IsHost && m_localUser.IsApproved)
{
CompleteRelayConnection();
StartVivoxJoin();
}
}
private void CompleteRelayConnection()
{
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby);
}
private void SetUserLobbyState()
{
SetGameState(GameState.Lobby);
OnReceiveMessage(MessageType.LobbyUserStatus, UserStatus.Lobby);
}
private void ResetLocalLobby()
{
m_localLobby.CopyObserved(new LocalLobby.LobbyData(), new Dictionary());
m_localLobby.AddPlayer(m_localUser); // As before, the local player will need to be plugged into UI before the lobby join actually happens.
m_localLobby.RelayServer = null;
}
#region Teardown
///
/// In builds, if we are in a lobby 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).
///
private IEnumerator LeaveBeforeQuit()
{
ForceLeaveAttempt();
yield return null;
Application.Quit();
}
private bool OnWantToQuit()
{
bool canQuit = string.IsNullOrEmpty(m_localLobby?.LobbyID);
StartCoroutine(LeaveBeforeQuit());
return canQuit;
}
private void OnDestroy()
{
ForceLeaveAttempt();
}
private void ForceLeaveAttempt()
{
Locator.Get.Messenger.Unsubscribe(this);
if (!string.IsNullOrEmpty(m_localLobby?.LobbyID))
{
LobbyAsyncRequests.Instance.LeaveLobbyAsync(m_localLobby?.LobbyID, null);
m_localLobby = null;
}
}
#endregion
}
}