using System;
using System.Collections.Generic;
using Unity.Services.Lobbies.Models;
using UnityEngine;
namespace Unity.BossRoom.UnityServices.Lobbies
{
///
/// A local wrapper around a lobby's remote data, with additional functionality for providing that data to UI elements and tracking local player objects.
///
[Serializable]
public sealed class LocalLobby
{
public event Action changed;
///
/// Create a list of new LocalLobbies from the result of a lobby list query.
///
public static List CreateLocalLobbies(QueryResponse response)
{
var retLst = new List();
foreach (var lobby in response.Results)
{
retLst.Add(Create(lobby));
}
return retLst;
}
public static LocalLobby Create(Lobby lobby)
{
var data = new LocalLobby();
data.ApplyRemoteData(lobby);
return data;
}
Dictionary m_LobbyUsers = new Dictionary();
public Dictionary LobbyUsers => m_LobbyUsers;
public struct LobbyData
{
public string LobbyID { get; set; }
public string LobbyCode { get; set; }
public string RelayJoinCode { get; set; }
public string LobbyName { get; set; }
public bool Private { get; set; }
public int MaxPlayerCount { get; set; }
public LobbyData(LobbyData existing)
{
LobbyID = existing.LobbyID;
LobbyCode = existing.LobbyCode;
RelayJoinCode = existing.RelayJoinCode;
LobbyName = existing.LobbyName;
Private = existing.Private;
MaxPlayerCount = existing.MaxPlayerCount;
}
public LobbyData(string lobbyCode)
{
LobbyID = null;
LobbyCode = lobbyCode;
RelayJoinCode = null;
LobbyName = null;
Private = false;
MaxPlayerCount = -1;
}
}
LobbyData m_Data;
public LobbyData Data => new LobbyData(m_Data);
public void AddUser(LocalLobbyUser user)
{
if (!m_LobbyUsers.ContainsKey(user.ID))
{
DoAddUser(user);
OnChanged();
}
}
void DoAddUser(LocalLobbyUser user)
{
m_LobbyUsers.Add(user.ID, user);
user.changed += OnChangedUser;
}
public void RemoveUser(LocalLobbyUser user)
{
DoRemoveUser(user);
OnChanged();
}
void DoRemoveUser(LocalLobbyUser user)
{
if (!m_LobbyUsers.ContainsKey(user.ID))
{
Debug.LogWarning($"Player {user.DisplayName}({user.ID}) does not exist in lobby: {LobbyID}");
return;
}
m_LobbyUsers.Remove(user.ID);
user.changed -= OnChangedUser;
}
void OnChangedUser(LocalLobbyUser user)
{
OnChanged();
}
void OnChanged()
{
changed?.Invoke(this);
}
public string LobbyID
{
get => m_Data.LobbyID;
set
{
m_Data.LobbyID = value;
OnChanged();
}
}
public string LobbyCode
{
get => m_Data.LobbyCode;
set
{
m_Data.LobbyCode = value;
OnChanged();
}
}
public string RelayJoinCode
{
get => m_Data.RelayJoinCode;
set
{
m_Data.RelayJoinCode = value;
OnChanged();
}
}
public string LobbyName
{
get => m_Data.LobbyName;
set
{
m_Data.LobbyName = value;
OnChanged();
}
}
public bool Private
{
get => m_Data.Private;
set
{
m_Data.Private = value;
OnChanged();
}
}
public int PlayerCount => m_LobbyUsers.Count;
public int MaxPlayerCount
{
get => m_Data.MaxPlayerCount;
set
{
m_Data.MaxPlayerCount = value;
OnChanged();
}
}
public void CopyDataFrom(LobbyData data, Dictionary currUsers)
{
m_Data = data;
if (currUsers == null)
{
m_LobbyUsers = new Dictionary();
}
else
{
List toRemove = new List();
foreach (var oldUser in m_LobbyUsers)
{
if (currUsers.ContainsKey(oldUser.Key))
{
oldUser.Value.CopyDataFrom(currUsers[oldUser.Key]);
}
else
{
toRemove.Add(oldUser.Value);
}
}
foreach (var remove in toRemove)
{
DoRemoveUser(remove);
}
foreach (var currUser in currUsers)
{
if (!m_LobbyUsers.ContainsKey(currUser.Key))
{
DoAddUser(currUser.Value);
}
}
}
OnChanged();
}
public Dictionary GetDataForUnityServices() =>
new Dictionary()
{
{"RelayJoinCode", new DataObject(DataObject.VisibilityOptions.Public, RelayJoinCode)}
};
public void ApplyRemoteData(Lobby lobby)
{
var info = new LobbyData(); // Technically, this is largely redundant after the first assignment, but it won't do any harm to assign it again.
info.LobbyID = lobby.Id;
info.LobbyCode = lobby.LobbyCode;
info.Private = lobby.IsPrivate;
info.LobbyName = lobby.Name;
info.MaxPlayerCount = lobby.MaxPlayers;
if (lobby.Data != null)
{
info.RelayJoinCode = lobby.Data.ContainsKey("RelayJoinCode") ? lobby.Data["RelayJoinCode"].Value : null; // By providing RelayCode through the lobby data with Member visibility, we ensure a client is connected to the lobby before they could attempt a relay connection, preventing timing issues between them.
}
else
{
info.RelayJoinCode = null;
}
var lobbyUsers = new Dictionary();
foreach (var player in lobby.Players)
{
if (player.Data != null)
{
if (LobbyUsers.ContainsKey(player.Id))
{
lobbyUsers.Add(player.Id, LobbyUsers[player.Id]);
continue;
}
}
// If the player isn't connected to Relay, get the most recent data that the lobby knows.
// (If we haven't seen this player yet, a new local representation of the player will have already been added by the LocalLobby.)
var incomingData = new LocalLobbyUser
{
IsHost = lobby.HostId.Equals(player.Id),
DisplayName = player.Data != null && player.Data.ContainsKey("DisplayName") ? player.Data["DisplayName"].Value : default,
ID = player.Id
};
lobbyUsers.Add(incomingData.ID, incomingData);
}
CopyDataFrom(info, lobbyUsers);
}
public void Reset(LocalLobbyUser localUser)
{
CopyDataFrom(new LobbyData(), new Dictionary());
AddUser(localUser);
}
}
}