using Firebase.Database;
using Firebase.Extensions;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using Newtonsoft.Json;
using System.Linq;
using Google.MiniJSON;
using Firebase.Storage;
using System.Security.Policy;
using System.Threading.Tasks;
public class RoomManager : MonoBehaviour
{
[Header("Waiting For Players Page")]
public GameObject waitingForPlayersPage;
///
/// TextMeshPro that show the value of the current rooom code
///
public TextMeshProUGUI roomCodeLabel;
public AudioClip playerJoinSFX;
public List playerStickers = new List();
[Header("Explanation Page")]
public GameObject explanationPage;
public TextMeshProUGUI explanationCounter;
public float explanationTime;
private DateTime endOfExplanationDate = DateTime.MinValue;
public AudioClip counterSFX;
[Header("Waiting For Proposition Page")]
public WaitForPropositionsPage waitForPropositionsPage;
[Header("Waiting For Proposition Page")]
public VotingPage votingPage;
[Header("Scoring Page")]
public ScoringPage scoringPage;
[Header("Other")]
public PromptList promptList;
private Room myRoom = null;
DatabaseReference realtimeDB;
private void Awake()
{
FirebaseInitializer.Instance.onFirebaseReady += Initialize;
}
private void Start()
{
explanationPage.SetActive(false);
waitForPropositionsPage.gameObject.SetActive(false);
votingPage.gameObject.SetActive(false);
waitingForPlayersPage.SetActive(true);
ResetAllPlayerLabels();
}
private void Update()
{
if (myRoom == null)
return;
//While Explanation State
if (myRoom.currentState == (int)GameState.Explanation && endOfExplanationDate != DateTime.MinValue)
{
TimeSpan duration = endOfExplanationDate - DateTime.Now;
explanationCounter.text = ((int)duration.TotalSeconds).ToString("D1");
if (duration.TotalMilliseconds <= 0)
{
SendRoomState(GameState.MakeProposition);
}
}
}
private void SendRoomState(GameState _newState)
{
//Debug.Log($"sending to RTDB that we are now in the {_newState} state", this);
realtimeDB.Child("rooms").Child(myRoom.code).Child("currentState").SetValueAsync((int)_newState);
}
private void ResetAllPlayerLabels()
{
for (int i = 0; i < playerStickers.Count; i++)
{
playerStickers[i].Initialize($"Waiting for P{i + 1}", i);
}
}
private void OnApplicationQuit()
{
Debug.Log($"delete realtimeDB room {myRoom.code}");
realtimeDB.Child("rooms").Child(myRoom.code).RemoveValueAsync().ContinueWithOnMainThread(task =>
{
Debug.Log($"room {myRoom.code} has been deleted");
myRoom = null;
});
Debug.Log($"delete files of room {myRoom.code}");
DeleteRoomFiles();
}
private void Initialize()
{
FirebaseInitializer.Instance.onFirebaseReady -= Initialize;
realtimeDB = FirebaseDatabase.DefaultInstance.RootReference;
Debug.Log("Realtime DB initialized");
CleanOldRooms();
CreateNewRoom();
}
///
/// Check all the rooms in the server and give back the number already taken
///
private void WhichCodesAreAlreadyUsed(Action> callback_OnCodesChecked)
{
QueryRoomsInDatabase(onlineRooms =>
{
List alreadyUsedCodes = onlineRooms.Select(room => int.Parse(room.code)).ToList();
Debug.Log($"Codes {string.Join(", ", alreadyUsedCodes)} are already used by other parties", this);
callback_OnCodesChecked?.Invoke(alreadyUsedCodes);
});
}
///
/// Automatically called at start of game
///
[ContextMenu("Create New Room")]
public void CreateNewRoom()
{
WhichCodesAreAlreadyUsed(codes =>
{
Room newRoom = new Room(GenerateRandomAvailableCode(codes).ToString("D4"));
myRoom = newRoom;
try
{
string JSON = JsonConvert.SerializeObject(newRoom);
realtimeDB.Child("rooms").Child(newRoom.code).SetRawJsonValueAsync(JSON).ContinueWithOnMainThread(task =>
{
//then subscribe to it
realtimeDB.Child("rooms").Child(newRoom.code).ValueChanged += OnRoomUpdate;
roomCodeLabel.text = myRoom.code;
Debug.Log($"room {myRoom.code} has been created on the server");
});
}
catch (Exception e)
{
Debug.LogException(e);
}
});
}
///
/// Generate a code between 0 and 1000 that is not in the list
///
/// the list of code you don"t want to get
///
private int GenerateRandomAvailableCode(List _impossibleCodes)
{
int random = UnityEngine.Random.Range(0, 1000);
while (_impossibleCodes.Contains(random))
{
Debug.Log($"{random} is already taken, choosing another room code", this);
random = UnityEngine.Random.Range(0, 1000);
}
return random;
}
public void GeneratePrompts()
{
System.Random rnd = new();
List prompts = promptList.prompts.OrderBy(x => rnd.Next()).Take(myRoom.players.Count()).ToList();
List players = myRoom.players.Values.ToList().OrderBy(x => rnd.Next()).ToList();
Dictionary questions = new();
for (int i = 0; i < players.Count(); i++)
{
Dictionary propositions = new();
for (int j = 0; j < 2; j++)
{
propositions.Add(j, new Proposition(players[i + j < players.Count() ? i + j : 0]));
}
questions.Add(i, new Question()
{
index = i,
promptId = prompts[i].id,
propositions = propositions,
creationDate = DateTime.Now.ToOADate(),
});
}
string JSON = JsonConvert.SerializeObject(questions);
realtimeDB.Child("rooms").Child(myRoom.code).Child("questions").SetRawJsonValueAsync(JSON);
}
///
/// Automatically called when something change in your room
///
private void OnRoomUpdate(object sender, ValueChangedEventArgs value)
{
if (value.DatabaseError != null)
{
Debug.LogError(value.DatabaseError.Message);
return;
}
if (value.Snapshot.Value == null)
{
Debug.Log("Trying to update room, but it's empty. Maybe you are exiting the app, so it's ok", this);
return;
}
string JSON = value.Snapshot.GetRawJsonValue();
Debug.Log($"your room has been updated :\n{JSON}");
GameState lastState = (GameState)myRoom.currentState;
try
{
myRoom = JsonConvert.DeserializeObject(JSON);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
//this is done only when entering a new state
if (lastState != (GameState)myRoom.currentState)
{
OnNewGameStateStarted();
}
else
{
//this is done each time something change
switch (myRoom.currentState)
{
case (int)GameState.WaitingForOtherPlayersToJoin:
UpdateConnectedPlayerList(myRoom.GetPlayerList());
break;
case (int)GameState.Explanation:
break;
case (int)GameState.MakeProposition:
waitForPropositionsPage.OnRoomUpdate(myRoom);
break;
default:
break;
}
}
}
///
/// Called when we enter in a new game state
///
private void OnNewGameStateStarted()
{
switch (myRoom.currentState)
{
case (int)GameState.WaitingForOtherPlayersToJoin:
Debug.Log("New State : WaitingForOtherPlayersToJoin");
break;
case (int)GameState.Explanation:
Debug.Log("New State : Explanation");
waitingForPlayersPage.SetActive(false);
explanationPage.SetActive(true);
endOfExplanationDate = DateTime.Now.AddSeconds(explanationTime);
AudioManager.Instance.PlaySFX(counterSFX);
AudioManager.Instance.StopMusic();
//generate all the questions during the explanation
GeneratePrompts();
break;
case (int)GameState.MakeProposition:
Debug.Log("New State : MakeProposition");
AudioManager.Instance.ChangeMusic(MusicTitle.TakingPicture);
waitForPropositionsPage.Initialize(myRoom, () => SendRoomState(GameState.MakeVote));
explanationPage.SetActive(false);
break;
case (int)GameState.MakeVote:
AudioManager.Instance.ChangeMusic(MusicTitle.VotingSession);
votingPage.ShowVotingPage(
realtimeDB.Child("rooms").Child(myRoom.code)
, myRoom.players
, myRoom.questions
, () => SendRoomState(GameState.Score));
break;
case (int)GameState.Score:
Debug.Log("It's score time !");
scoringPage.Initialize(myRoom);
break;
default:
break;
}
}
///
/// Update the player labels on the WaitingForPlayer page
///
///
private void UpdateConnectedPlayerList(List _players)
{
AudioManager.Instance.PlaySFX(playerJoinSFX);
ResetAllPlayerLabels();
//Debug.Log($"players count = {_players.Count}");
List orderedPlayers = _players.OrderBy(x => x.creationDate).ToList();
for (int i = 0; i < orderedPlayers.Count; i++)
{
Debug.Log($"player {i} = {orderedPlayers[i].name}");
playerStickers[i].Initialize(orderedPlayers[i].name, i);
}
}
private void QueryRoomsInDatabase(Action> callback)
{
//Debug.Log("Querying rooms from the database", this);
realtimeDB.Child("rooms").GetValueAsync().ContinueWithOnMainThread(task =>
{
if (task.IsFaulted)
{
Debug.LogException(task.Exception);
callback?.Invoke(new List());
return;
}
DataSnapshot snapshot = task.Result;
if (snapshot.Value != null)
{
string JSON = snapshot.GetRawJsonValue();
Debug.Log($"Found some rooms:\n{JSON}", this);
Dictionary onlineRooms = JsonConvert.DeserializeObject>(JSON);
callback?.Invoke(onlineRooms.Values.ToList());
}
else
{
Debug.Log("No rooms found in the database", this);
callback?.Invoke(new List());
}
});
}
private void CleanOldRooms()
{
QueryRoomsInDatabase(onlineRooms =>
{
DateTime oneHourLater = DateTime.Now.AddHours(1);
foreach (Room r in onlineRooms)
{
if (DateTime.FromOADate(r.creationDate) < oneHourLater)
{
Debug.Log($"Room {r.code} has been created at {DateTime.FromOADate(r.creationDate)} and it's more than one hour old. Deleting it");
realtimeDB.Child("rooms").Child(r.code).RemoveValueAsync().ContinueWithOnMainThread(task =>
{
if (task.IsFaulted)
{
Debug.LogException(task.Exception);
return;
}
Debug.Log($"Room {r.code} has been deleted");
});
}
}
});
}
private async Task DoesPathExistAsync(StorageReference storageReference)
{
try
{
await storageReference.GetMetadataAsync();
return true; // Path exists
}
catch (StorageException ex)
{
if (ex.ErrorCode == StorageException.ErrorObjectNotFound)
{
return false; // Path does not exist
}
else
{
Debug.LogException(ex);
return false; // Handle other errors as needed
}
}
}
private async void DeleteRoomFiles()
{
string roomCode = myRoom.code; //save it in case room has been deleted during you check files
StorageReference roomFolder = FirebaseStorage.DefaultInstance.RootReference.Child(roomCode);
bool pathExists = await DoesPathExistAsync(roomFolder);
if (pathExists)
{
_ = roomFolder.DeleteAsync().ContinueWithOnMainThread(task =>
{
if (task.IsCanceled || task.IsFaulted)
{
Debug.LogException(task.Exception);
}
else
{
Debug.Log($"Files of Room {roomCode} have been deleted");
}
});
}
else
{
Debug.Log($"Path for Room {roomCode} does not exist, nothing to delete");
}
}
}